Completed
Pull Request — master (#4630)
by Lukas
42:07 queued 29:58
created
lib/private/Security/Bruteforce/Throttler.php 2 patches
Indentation   +207 added lines, -207 removed lines patch added patch discarded remove patch
@@ -43,211 +43,211 @@
 block discarded – undo
43 43
  * @package OC\Security\Bruteforce
44 44
  */
45 45
 class Throttler {
46
-	const LOGIN_ACTION = 'login';
47
-
48
-	/** @var IDBConnection */
49
-	private $db;
50
-	/** @var ITimeFactory */
51
-	private $timeFactory;
52
-	/** @var ILogger */
53
-	private $logger;
54
-	/** @var IConfig */
55
-	private $config;
56
-
57
-	/**
58
-	 * @param IDBConnection $db
59
-	 * @param ITimeFactory $timeFactory
60
-	 * @param ILogger $logger
61
-	 * @param IConfig $config
62
-	 */
63
-	public function __construct(IDBConnection $db,
64
-								ITimeFactory $timeFactory,
65
-								ILogger $logger,
66
-								IConfig $config) {
67
-		$this->db = $db;
68
-		$this->timeFactory = $timeFactory;
69
-		$this->logger = $logger;
70
-		$this->config = $config;
71
-	}
72
-
73
-	/**
74
-	 * Convert a number of seconds into the appropriate DateInterval
75
-	 *
76
-	 * @param int $expire
77
-	 * @return \DateInterval
78
-	 */
79
-	private function getCutoff($expire) {
80
-		$d1 = new \DateTime();
81
-		$d2 = clone $d1;
82
-		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
83
-		return $d2->diff($d1);
84
-	}
85
-
86
-	/**
87
-	 * Register a failed attempt to bruteforce a security control
88
-	 *
89
-	 * @param string $action
90
-	 * @param string $ip
91
-	 * @param array $metadata Optional metadata logged to the database
92
-	 */
93
-	public function registerAttempt($action,
94
-									$ip,
95
-									array $metadata = []) {
96
-		// No need to log if the bruteforce protection is disabled
97
-		if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
98
-			return;
99
-		}
100
-
101
-		$ipAddress = new IpAddress($ip);
102
-		$values = [
103
-			'action' => $action,
104
-			'occurred' => $this->timeFactory->getTime(),
105
-			'ip' => (string)$ipAddress,
106
-			'subnet' => $ipAddress->getSubnet(),
107
-			'metadata' => json_encode($metadata),
108
-		];
109
-
110
-		$this->logger->notice(
111
-			sprintf(
112
-				'Bruteforce attempt from "%s" detected for action "%s".',
113
-				$ip,
114
-				$action
115
-			),
116
-			[
117
-				'app' => 'core',
118
-			]
119
-		);
120
-
121
-		$qb = $this->db->getQueryBuilder();
122
-		$qb->insert('bruteforce_attempts');
123
-		foreach($values as $column => $value) {
124
-			$qb->setValue($column, $qb->createNamedParameter($value));
125
-		}
126
-		$qb->execute();
127
-	}
128
-
129
-	/**
130
-	 * Check if the IP is whitelisted
131
-	 *
132
-	 * @param string $ip
133
-	 * @return bool
134
-	 */
135
-	private function isIPWhitelisted($ip) {
136
-		if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
137
-			return true;
138
-		}
139
-
140
-		$keys = $this->config->getAppKeys('bruteForce');
141
-		$keys = array_filter($keys, function($key) {
142
-			$regex = '/^whitelist_/S';
143
-			return preg_match($regex, $key) === 1;
144
-		});
145
-
146
-		if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
147
-			$type = 4;
148
-		} else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
149
-			$type = 6;
150
-		} else {
151
-			return false;
152
-		}
153
-
154
-		$ip = inet_pton($ip);
155
-
156
-		foreach ($keys as $key) {
157
-			$cidr = $this->config->getAppValue('bruteForce', $key, null);
158
-
159
-			$cx = explode('/', $cidr);
160
-			$addr = $cx[0];
161
-			$mask = (int)$cx[1];
162
-
163
-			// Do not compare ipv4 to ipv6
164
-			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
165
-				($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
166
-				continue;
167
-			}
168
-
169
-			$addr = inet_pton($addr);
170
-
171
-			$valid = true;
172
-			for($i = 0; $i < $mask; $i++) {
173
-				$part = ord($addr[(int)($i/8)]);
174
-				$orig = ord($ip[(int)($i/8)]);
175
-
176
-				$part = $part & (15 << (1 - ($i % 2)));
177
-				$orig = $orig & (15 << (1 - ($i % 2)));
178
-
179
-				if ($part !== $orig) {
180
-					$valid = false;
181
-					break;
182
-				}
183
-			}
184
-
185
-			if ($valid === true) {
186
-				return true;
187
-			}
188
-		}
189
-
190
-		return false;
191
-
192
-	}
193
-
194
-	/**
195
-	 * Get the throttling delay (in milliseconds)
196
-	 *
197
-	 * @param string $ip
198
-	 * @param string $action optionally filter by action
199
-	 * @return int
200
-	 */
201
-	public function getDelay($ip, $action = '') {
202
-		$ipAddress = new IpAddress($ip);
203
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
204
-			return 0;
205
-		}
206
-
207
-		$cutoffTime = (new \DateTime())
208
-			->sub($this->getCutoff(43200))
209
-			->getTimestamp();
210
-
211
-		$qb = $this->db->getQueryBuilder();
212
-		$qb->select('*')
213
-			->from('bruteforce_attempts')
214
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
215
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
216
-
217
-		if ($action !== '') {
218
-			$qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
219
-		}
220
-
221
-		$attempts = count($qb->execute()->fetchAll());
222
-
223
-		if ($attempts === 0) {
224
-			return 0;
225
-		}
226
-
227
-		$maxDelay = 30;
228
-		$firstDelay = 0.1;
229
-		if ($attempts > (8 * PHP_INT_SIZE - 1))  {
230
-			// Don't ever overflow. Just assume the maxDelay time:s
231
-			$firstDelay = $maxDelay;
232
-		} else {
233
-			$firstDelay *= pow(2, $attempts);
234
-			if ($firstDelay > $maxDelay) {
235
-				$firstDelay = $maxDelay;
236
-			}
237
-		}
238
-		return (int) \ceil($firstDelay * 1000);
239
-	}
240
-
241
-	/**
242
-	 * Will sleep for the defined amount of time
243
-	 *
244
-	 * @param string $ip
245
-	 * @param string $action optionally filter by action
246
-	 * @return int the time spent sleeping
247
-	 */
248
-	public function sleepDelay($ip, $action = '') {
249
-		$delay = $this->getDelay($ip, $action);
250
-		usleep($delay * 1000);
251
-		return $delay;
252
-	}
46
+    const LOGIN_ACTION = 'login';
47
+
48
+    /** @var IDBConnection */
49
+    private $db;
50
+    /** @var ITimeFactory */
51
+    private $timeFactory;
52
+    /** @var ILogger */
53
+    private $logger;
54
+    /** @var IConfig */
55
+    private $config;
56
+
57
+    /**
58
+     * @param IDBConnection $db
59
+     * @param ITimeFactory $timeFactory
60
+     * @param ILogger $logger
61
+     * @param IConfig $config
62
+     */
63
+    public function __construct(IDBConnection $db,
64
+                                ITimeFactory $timeFactory,
65
+                                ILogger $logger,
66
+                                IConfig $config) {
67
+        $this->db = $db;
68
+        $this->timeFactory = $timeFactory;
69
+        $this->logger = $logger;
70
+        $this->config = $config;
71
+    }
72
+
73
+    /**
74
+     * Convert a number of seconds into the appropriate DateInterval
75
+     *
76
+     * @param int $expire
77
+     * @return \DateInterval
78
+     */
79
+    private function getCutoff($expire) {
80
+        $d1 = new \DateTime();
81
+        $d2 = clone $d1;
82
+        $d2->sub(new \DateInterval('PT' . $expire . 'S'));
83
+        return $d2->diff($d1);
84
+    }
85
+
86
+    /**
87
+     * Register a failed attempt to bruteforce a security control
88
+     *
89
+     * @param string $action
90
+     * @param string $ip
91
+     * @param array $metadata Optional metadata logged to the database
92
+     */
93
+    public function registerAttempt($action,
94
+                                    $ip,
95
+                                    array $metadata = []) {
96
+        // No need to log if the bruteforce protection is disabled
97
+        if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
98
+            return;
99
+        }
100
+
101
+        $ipAddress = new IpAddress($ip);
102
+        $values = [
103
+            'action' => $action,
104
+            'occurred' => $this->timeFactory->getTime(),
105
+            'ip' => (string)$ipAddress,
106
+            'subnet' => $ipAddress->getSubnet(),
107
+            'metadata' => json_encode($metadata),
108
+        ];
109
+
110
+        $this->logger->notice(
111
+            sprintf(
112
+                'Bruteforce attempt from "%s" detected for action "%s".',
113
+                $ip,
114
+                $action
115
+            ),
116
+            [
117
+                'app' => 'core',
118
+            ]
119
+        );
120
+
121
+        $qb = $this->db->getQueryBuilder();
122
+        $qb->insert('bruteforce_attempts');
123
+        foreach($values as $column => $value) {
124
+            $qb->setValue($column, $qb->createNamedParameter($value));
125
+        }
126
+        $qb->execute();
127
+    }
128
+
129
+    /**
130
+     * Check if the IP is whitelisted
131
+     *
132
+     * @param string $ip
133
+     * @return bool
134
+     */
135
+    private function isIPWhitelisted($ip) {
136
+        if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
137
+            return true;
138
+        }
139
+
140
+        $keys = $this->config->getAppKeys('bruteForce');
141
+        $keys = array_filter($keys, function($key) {
142
+            $regex = '/^whitelist_/S';
143
+            return preg_match($regex, $key) === 1;
144
+        });
145
+
146
+        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
147
+            $type = 4;
148
+        } else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
149
+            $type = 6;
150
+        } else {
151
+            return false;
152
+        }
153
+
154
+        $ip = inet_pton($ip);
155
+
156
+        foreach ($keys as $key) {
157
+            $cidr = $this->config->getAppValue('bruteForce', $key, null);
158
+
159
+            $cx = explode('/', $cidr);
160
+            $addr = $cx[0];
161
+            $mask = (int)$cx[1];
162
+
163
+            // Do not compare ipv4 to ipv6
164
+            if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
165
+                ($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
166
+                continue;
167
+            }
168
+
169
+            $addr = inet_pton($addr);
170
+
171
+            $valid = true;
172
+            for($i = 0; $i < $mask; $i++) {
173
+                $part = ord($addr[(int)($i/8)]);
174
+                $orig = ord($ip[(int)($i/8)]);
175
+
176
+                $part = $part & (15 << (1 - ($i % 2)));
177
+                $orig = $orig & (15 << (1 - ($i % 2)));
178
+
179
+                if ($part !== $orig) {
180
+                    $valid = false;
181
+                    break;
182
+                }
183
+            }
184
+
185
+            if ($valid === true) {
186
+                return true;
187
+            }
188
+        }
189
+
190
+        return false;
191
+
192
+    }
193
+
194
+    /**
195
+     * Get the throttling delay (in milliseconds)
196
+     *
197
+     * @param string $ip
198
+     * @param string $action optionally filter by action
199
+     * @return int
200
+     */
201
+    public function getDelay($ip, $action = '') {
202
+        $ipAddress = new IpAddress($ip);
203
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
204
+            return 0;
205
+        }
206
+
207
+        $cutoffTime = (new \DateTime())
208
+            ->sub($this->getCutoff(43200))
209
+            ->getTimestamp();
210
+
211
+        $qb = $this->db->getQueryBuilder();
212
+        $qb->select('*')
213
+            ->from('bruteforce_attempts')
214
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
215
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
216
+
217
+        if ($action !== '') {
218
+            $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
219
+        }
220
+
221
+        $attempts = count($qb->execute()->fetchAll());
222
+
223
+        if ($attempts === 0) {
224
+            return 0;
225
+        }
226
+
227
+        $maxDelay = 30;
228
+        $firstDelay = 0.1;
229
+        if ($attempts > (8 * PHP_INT_SIZE - 1))  {
230
+            // Don't ever overflow. Just assume the maxDelay time:s
231
+            $firstDelay = $maxDelay;
232
+        } else {
233
+            $firstDelay *= pow(2, $attempts);
234
+            if ($firstDelay > $maxDelay) {
235
+                $firstDelay = $maxDelay;
236
+            }
237
+        }
238
+        return (int) \ceil($firstDelay * 1000);
239
+    }
240
+
241
+    /**
242
+     * Will sleep for the defined amount of time
243
+     *
244
+     * @param string $ip
245
+     * @param string $action optionally filter by action
246
+     * @return int the time spent sleeping
247
+     */
248
+    public function sleepDelay($ip, $action = '') {
249
+        $delay = $this->getDelay($ip, $action);
250
+        usleep($delay * 1000);
251
+        return $delay;
252
+    }
253 253
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -79,7 +79,7 @@  discard block
 block discarded – undo
79 79
 	private function getCutoff($expire) {
80 80
 		$d1 = new \DateTime();
81 81
 		$d2 = clone $d1;
82
-		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
82
+		$d2->sub(new \DateInterval('PT'.$expire.'S'));
83 83
 		return $d2->diff($d1);
84 84
 	}
85 85
 
@@ -94,7 +94,7 @@  discard block
 block discarded – undo
94 94
 									$ip,
95 95
 									array $metadata = []) {
96 96
 		// No need to log if the bruteforce protection is disabled
97
-		if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
97
+		if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
98 98
 			return;
99 99
 		}
100 100
 
@@ -102,7 +102,7 @@  discard block
 block discarded – undo
102 102
 		$values = [
103 103
 			'action' => $action,
104 104
 			'occurred' => $this->timeFactory->getTime(),
105
-			'ip' => (string)$ipAddress,
105
+			'ip' => (string) $ipAddress,
106 106
 			'subnet' => $ipAddress->getSubnet(),
107 107
 			'metadata' => json_encode($metadata),
108 108
 		];
@@ -120,7 +120,7 @@  discard block
 block discarded – undo
120 120
 
121 121
 		$qb = $this->db->getQueryBuilder();
122 122
 		$qb->insert('bruteforce_attempts');
123
-		foreach($values as $column => $value) {
123
+		foreach ($values as $column => $value) {
124 124
 			$qb->setValue($column, $qb->createNamedParameter($value));
125 125
 		}
126 126
 		$qb->execute();
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
 	 * @return bool
134 134
 	 */
135 135
 	private function isIPWhitelisted($ip) {
136
-		if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
136
+		if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
137 137
 			return true;
138 138
 		}
139 139
 
@@ -158,7 +158,7 @@  discard block
 block discarded – undo
158 158
 
159 159
 			$cx = explode('/', $cidr);
160 160
 			$addr = $cx[0];
161
-			$mask = (int)$cx[1];
161
+			$mask = (int) $cx[1];
162 162
 
163 163
 			// Do not compare ipv4 to ipv6
164 164
 			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
@@ -169,9 +169,9 @@  discard block
 block discarded – undo
169 169
 			$addr = inet_pton($addr);
170 170
 
171 171
 			$valid = true;
172
-			for($i = 0; $i < $mask; $i++) {
173
-				$part = ord($addr[(int)($i/8)]);
174
-				$orig = ord($ip[(int)($i/8)]);
172
+			for ($i = 0; $i < $mask; $i++) {
173
+				$part = ord($addr[(int) ($i / 8)]);
174
+				$orig = ord($ip[(int) ($i / 8)]);
175 175
 
176 176
 				$part = $part & (15 << (1 - ($i % 2)));
177 177
 				$orig = $orig & (15 << (1 - ($i % 2)));
@@ -200,7 +200,7 @@  discard block
 block discarded – undo
200 200
 	 */
201 201
 	public function getDelay($ip, $action = '') {
202 202
 		$ipAddress = new IpAddress($ip);
203
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
203
+		if ($this->isIPWhitelisted((string) $ipAddress)) {
204 204
 			return 0;
205 205
 		}
206 206
 
@@ -226,7 +226,7 @@  discard block
 block discarded – undo
226 226
 
227 227
 		$maxDelay = 30;
228 228
 		$firstDelay = 0.1;
229
-		if ($attempts > (8 * PHP_INT_SIZE - 1))  {
229
+		if ($attempts > (8 * PHP_INT_SIZE - 1)) {
230 230
 			// Don't ever overflow. Just assume the maxDelay time:s
231 231
 			$firstDelay = $maxDelay;
232 232
 		} else {
Please login to merge, or discard this patch.