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