Passed
Push — master ( 40eabf...3d61f7 )
by Morris
22:34 queued 08:37
created
lib/private/Security/Bruteforce/Throttler.php 2 patches
Indentation   +299 added lines, -299 removed lines patch added patch discarded remove patch
@@ -54,303 +54,303 @@
 block discarded – undo
54 54
  * @package OC\Security\Bruteforce
55 55
  */
56 56
 class Throttler {
57
-	public const LOGIN_ACTION = 'login';
58
-	public const MAX_DELAY = 25;
59
-	public const MAX_DELAY_MS = 25000; // in milliseconds
60
-	public const MAX_ATTEMPTS = 10;
61
-
62
-	/** @var IDBConnection */
63
-	private $db;
64
-	/** @var ITimeFactory */
65
-	private $timeFactory;
66
-	/** @var ILogger */
67
-	private $logger;
68
-	/** @var IConfig */
69
-	private $config;
70
-
71
-	/**
72
-	 * @param IDBConnection $db
73
-	 * @param ITimeFactory $timeFactory
74
-	 * @param ILogger $logger
75
-	 * @param IConfig $config
76
-	 */
77
-	public function __construct(IDBConnection $db,
78
-								ITimeFactory $timeFactory,
79
-								ILogger $logger,
80
-								IConfig $config) {
81
-		$this->db = $db;
82
-		$this->timeFactory = $timeFactory;
83
-		$this->logger = $logger;
84
-		$this->config = $config;
85
-	}
86
-
87
-	/**
88
-	 * Convert a number of seconds into the appropriate DateInterval
89
-	 *
90
-	 * @param int $expire
91
-	 * @return \DateInterval
92
-	 */
93
-	private function getCutoff(int $expire): \DateInterval {
94
-		$d1 = new \DateTime();
95
-		$d2 = clone $d1;
96
-		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
97
-		return $d2->diff($d1);
98
-	}
99
-
100
-	/**
101
-	 *  Calculate the cut off timestamp
102
-	 *
103
-	 * @param float $maxAgeHours
104
-	 * @return int
105
-	 */
106
-	private function getCutoffTimestamp(float $maxAgeHours = 12.0): int {
107
-		return (new \DateTime())
108
-			->sub($this->getCutoff((int) ($maxAgeHours * 3600)))
109
-			->getTimestamp();
110
-	}
111
-
112
-	/**
113
-	 * Register a failed attempt to bruteforce a security control
114
-	 *
115
-	 * @param string $action
116
-	 * @param string $ip
117
-	 * @param array $metadata Optional metadata logged to the database
118
-	 * @suppress SqlInjectionChecker
119
-	 */
120
-	public function registerAttempt(string $action,
121
-									string $ip,
122
-									array $metadata = []): void {
123
-		// No need to log if the bruteforce protection is disabled
124
-		if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
125
-			return;
126
-		}
127
-
128
-		$ipAddress = new IpAddress($ip);
129
-		$values = [
130
-			'action' => $action,
131
-			'occurred' => $this->timeFactory->getTime(),
132
-			'ip' => (string)$ipAddress,
133
-			'subnet' => $ipAddress->getSubnet(),
134
-			'metadata' => json_encode($metadata),
135
-		];
136
-
137
-		$this->logger->notice(
138
-			sprintf(
139
-				'Bruteforce attempt from "%s" detected for action "%s".',
140
-				$ip,
141
-				$action
142
-			),
143
-			[
144
-				'app' => 'core',
145
-			]
146
-		);
147
-
148
-		$qb = $this->db->getQueryBuilder();
149
-		$qb->insert('bruteforce_attempts');
150
-		foreach ($values as $column => $value) {
151
-			$qb->setValue($column, $qb->createNamedParameter($value));
152
-		}
153
-		$qb->execute();
154
-	}
155
-
156
-	/**
157
-	 * Check if the IP is whitelisted
158
-	 *
159
-	 * @param string $ip
160
-	 * @return bool
161
-	 */
162
-	private function isIPWhitelisted(string $ip): bool {
163
-		if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
164
-			return true;
165
-		}
166
-
167
-		$keys = $this->config->getAppKeys('bruteForce');
168
-		$keys = array_filter($keys, function ($key) {
169
-			return 0 === strpos($key, 'whitelist_');
170
-		});
171
-
172
-		if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
173
-			$type = 4;
174
-		} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
175
-			$type = 6;
176
-		} else {
177
-			return false;
178
-		}
179
-
180
-		$ip = inet_pton($ip);
181
-
182
-		foreach ($keys as $key) {
183
-			$cidr = $this->config->getAppValue('bruteForce', $key, null);
184
-
185
-			$cx = explode('/', $cidr);
186
-			$addr = $cx[0];
187
-			$mask = (int)$cx[1];
188
-
189
-			// Do not compare ipv4 to ipv6
190
-			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
191
-				($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
192
-				continue;
193
-			}
194
-
195
-			$addr = inet_pton($addr);
196
-
197
-			$valid = true;
198
-			for ($i = 0; $i < $mask; $i++) {
199
-				$part = ord($addr[(int)($i/8)]);
200
-				$orig = ord($ip[(int)($i/8)]);
201
-
202
-				$bitmask = 1 << (7 - ($i % 8));
203
-
204
-				$part = $part & $bitmask;
205
-				$orig = $orig & $bitmask;
206
-
207
-				if ($part !== $orig) {
208
-					$valid = false;
209
-					break;
210
-				}
211
-			}
212
-
213
-			if ($valid === true) {
214
-				return true;
215
-			}
216
-		}
217
-
218
-		return false;
219
-	}
220
-
221
-	/**
222
-	 * Get the throttling delay (in milliseconds)
223
-	 *
224
-	 * @param string $ip
225
-	 * @param string $action optionally filter by action
226
-	 * @param float $maxAgeHours
227
-	 * @return int
228
-	 */
229
-	public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int {
230
-		if ($ip === '') {
231
-			return 0;
232
-		}
233
-
234
-		$ipAddress = new IpAddress($ip);
235
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
236
-			return 0;
237
-		}
238
-
239
-		$cutoffTime = $this->getCutoffTimestamp($maxAgeHours);
240
-
241
-		$qb = $this->db->getQueryBuilder();
242
-		$qb->select($qb->func()->count('*', 'attempts'))
243
-			->from('bruteforce_attempts')
244
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
245
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
246
-
247
-		if ($action !== '') {
248
-			$qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
249
-		}
250
-
251
-		$result = $qb->execute();
252
-		$row = $result->fetch();
253
-		$result->closeCursor();
254
-
255
-		return (int) $row['attempts'];
256
-	}
257
-
258
-	/**
259
-	 * Get the throttling delay (in milliseconds)
260
-	 *
261
-	 * @param string $ip
262
-	 * @param string $action optionally filter by action
263
-	 * @return int
264
-	 */
265
-	public function getDelay(string $ip, string $action = ''): int {
266
-		$attempts = $this->getAttempts($ip, $action);
267
-		if ($attempts === 0) {
268
-			return 0;
269
-		}
270
-
271
-		$firstDelay = 0.1;
272
-		if ($attempts > self::MAX_ATTEMPTS) {
273
-			// Don't ever overflow. Just assume the maxDelay time:s
274
-			return self::MAX_DELAY_MS;
275
-		}
276
-
277
-		$delay = $firstDelay * 2**$attempts;
278
-		if ($delay > self::MAX_DELAY) {
279
-			return self::MAX_DELAY_MS;
280
-		}
281
-		return (int) \ceil($delay * 1000);
282
-	}
283
-
284
-	/**
285
-	 * Reset the throttling delay for an IP address, action and metadata
286
-	 *
287
-	 * @param string $ip
288
-	 * @param string $action
289
-	 * @param array $metadata
290
-	 */
291
-	public function resetDelay(string $ip, string $action, array $metadata): void {
292
-		$ipAddress = new IpAddress($ip);
293
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
294
-			return;
295
-		}
296
-
297
-		$cutoffTime = $this->getCutoffTimestamp();
298
-
299
-		$qb = $this->db->getQueryBuilder();
300
-		$qb->delete('bruteforce_attempts')
301
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
302
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())))
303
-			->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)))
304
-			->andWhere($qb->expr()->eq('metadata', $qb->createNamedParameter(json_encode($metadata))));
305
-
306
-		$qb->execute();
307
-	}
308
-
309
-	/**
310
-	 * Reset the throttling delay for an IP address
311
-	 *
312
-	 * @param string $ip
313
-	 */
314
-	public function resetDelayForIP($ip) {
315
-		$cutoffTime = $this->getCutoffTimestamp();
316
-
317
-		$qb = $this->db->getQueryBuilder();
318
-		$qb->delete('bruteforce_attempts')
319
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
320
-			->andWhere($qb->expr()->eq('ip', $qb->createNamedParameter($ip)));
321
-
322
-		$qb->execute();
323
-	}
324
-
325
-	/**
326
-	 * Will sleep for the defined amount of time
327
-	 *
328
-	 * @param string $ip
329
-	 * @param string $action optionally filter by action
330
-	 * @return int the time spent sleeping
331
-	 */
332
-	public function sleepDelay(string $ip, string $action = ''): int {
333
-		$delay = $this->getDelay($ip, $action);
334
-		usleep($delay * 1000);
335
-		return $delay;
336
-	}
337
-
338
-	/**
339
-	 * Will sleep for the defined amount of time unless maximum was reached in the last 30 minutes
340
-	 * In this case a "429 Too Many Request" exception is thrown
341
-	 *
342
-	 * @param string $ip
343
-	 * @param string $action optionally filter by action
344
-	 * @return int the time spent sleeping
345
-	 * @throws MaxDelayReached when reached the maximum
346
-	 */
347
-	public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int {
348
-		$delay = $this->getDelay($ip, $action);
349
-		if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > self::MAX_ATTEMPTS) {
350
-			// If the ip made too many attempts within the last 30 mins we don't execute anymore
351
-			throw new MaxDelayReached('Reached maximum delay');
352
-		}
353
-		usleep($delay * 1000);
354
-		return $delay;
355
-	}
57
+    public const LOGIN_ACTION = 'login';
58
+    public const MAX_DELAY = 25;
59
+    public const MAX_DELAY_MS = 25000; // in milliseconds
60
+    public const MAX_ATTEMPTS = 10;
61
+
62
+    /** @var IDBConnection */
63
+    private $db;
64
+    /** @var ITimeFactory */
65
+    private $timeFactory;
66
+    /** @var ILogger */
67
+    private $logger;
68
+    /** @var IConfig */
69
+    private $config;
70
+
71
+    /**
72
+     * @param IDBConnection $db
73
+     * @param ITimeFactory $timeFactory
74
+     * @param ILogger $logger
75
+     * @param IConfig $config
76
+     */
77
+    public function __construct(IDBConnection $db,
78
+                                ITimeFactory $timeFactory,
79
+                                ILogger $logger,
80
+                                IConfig $config) {
81
+        $this->db = $db;
82
+        $this->timeFactory = $timeFactory;
83
+        $this->logger = $logger;
84
+        $this->config = $config;
85
+    }
86
+
87
+    /**
88
+     * Convert a number of seconds into the appropriate DateInterval
89
+     *
90
+     * @param int $expire
91
+     * @return \DateInterval
92
+     */
93
+    private function getCutoff(int $expire): \DateInterval {
94
+        $d1 = new \DateTime();
95
+        $d2 = clone $d1;
96
+        $d2->sub(new \DateInterval('PT' . $expire . 'S'));
97
+        return $d2->diff($d1);
98
+    }
99
+
100
+    /**
101
+     *  Calculate the cut off timestamp
102
+     *
103
+     * @param float $maxAgeHours
104
+     * @return int
105
+     */
106
+    private function getCutoffTimestamp(float $maxAgeHours = 12.0): int {
107
+        return (new \DateTime())
108
+            ->sub($this->getCutoff((int) ($maxAgeHours * 3600)))
109
+            ->getTimestamp();
110
+    }
111
+
112
+    /**
113
+     * Register a failed attempt to bruteforce a security control
114
+     *
115
+     * @param string $action
116
+     * @param string $ip
117
+     * @param array $metadata Optional metadata logged to the database
118
+     * @suppress SqlInjectionChecker
119
+     */
120
+    public function registerAttempt(string $action,
121
+                                    string $ip,
122
+                                    array $metadata = []): void {
123
+        // No need to log if the bruteforce protection is disabled
124
+        if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
125
+            return;
126
+        }
127
+
128
+        $ipAddress = new IpAddress($ip);
129
+        $values = [
130
+            'action' => $action,
131
+            'occurred' => $this->timeFactory->getTime(),
132
+            'ip' => (string)$ipAddress,
133
+            'subnet' => $ipAddress->getSubnet(),
134
+            'metadata' => json_encode($metadata),
135
+        ];
136
+
137
+        $this->logger->notice(
138
+            sprintf(
139
+                'Bruteforce attempt from "%s" detected for action "%s".',
140
+                $ip,
141
+                $action
142
+            ),
143
+            [
144
+                'app' => 'core',
145
+            ]
146
+        );
147
+
148
+        $qb = $this->db->getQueryBuilder();
149
+        $qb->insert('bruteforce_attempts');
150
+        foreach ($values as $column => $value) {
151
+            $qb->setValue($column, $qb->createNamedParameter($value));
152
+        }
153
+        $qb->execute();
154
+    }
155
+
156
+    /**
157
+     * Check if the IP is whitelisted
158
+     *
159
+     * @param string $ip
160
+     * @return bool
161
+     */
162
+    private function isIPWhitelisted(string $ip): bool {
163
+        if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
164
+            return true;
165
+        }
166
+
167
+        $keys = $this->config->getAppKeys('bruteForce');
168
+        $keys = array_filter($keys, function ($key) {
169
+            return 0 === strpos($key, 'whitelist_');
170
+        });
171
+
172
+        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
173
+            $type = 4;
174
+        } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
175
+            $type = 6;
176
+        } else {
177
+            return false;
178
+        }
179
+
180
+        $ip = inet_pton($ip);
181
+
182
+        foreach ($keys as $key) {
183
+            $cidr = $this->config->getAppValue('bruteForce', $key, null);
184
+
185
+            $cx = explode('/', $cidr);
186
+            $addr = $cx[0];
187
+            $mask = (int)$cx[1];
188
+
189
+            // Do not compare ipv4 to ipv6
190
+            if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
191
+                ($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
192
+                continue;
193
+            }
194
+
195
+            $addr = inet_pton($addr);
196
+
197
+            $valid = true;
198
+            for ($i = 0; $i < $mask; $i++) {
199
+                $part = ord($addr[(int)($i/8)]);
200
+                $orig = ord($ip[(int)($i/8)]);
201
+
202
+                $bitmask = 1 << (7 - ($i % 8));
203
+
204
+                $part = $part & $bitmask;
205
+                $orig = $orig & $bitmask;
206
+
207
+                if ($part !== $orig) {
208
+                    $valid = false;
209
+                    break;
210
+                }
211
+            }
212
+
213
+            if ($valid === true) {
214
+                return true;
215
+            }
216
+        }
217
+
218
+        return false;
219
+    }
220
+
221
+    /**
222
+     * Get the throttling delay (in milliseconds)
223
+     *
224
+     * @param string $ip
225
+     * @param string $action optionally filter by action
226
+     * @param float $maxAgeHours
227
+     * @return int
228
+     */
229
+    public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int {
230
+        if ($ip === '') {
231
+            return 0;
232
+        }
233
+
234
+        $ipAddress = new IpAddress($ip);
235
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
236
+            return 0;
237
+        }
238
+
239
+        $cutoffTime = $this->getCutoffTimestamp($maxAgeHours);
240
+
241
+        $qb = $this->db->getQueryBuilder();
242
+        $qb->select($qb->func()->count('*', 'attempts'))
243
+            ->from('bruteforce_attempts')
244
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
245
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
246
+
247
+        if ($action !== '') {
248
+            $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
249
+        }
250
+
251
+        $result = $qb->execute();
252
+        $row = $result->fetch();
253
+        $result->closeCursor();
254
+
255
+        return (int) $row['attempts'];
256
+    }
257
+
258
+    /**
259
+     * Get the throttling delay (in milliseconds)
260
+     *
261
+     * @param string $ip
262
+     * @param string $action optionally filter by action
263
+     * @return int
264
+     */
265
+    public function getDelay(string $ip, string $action = ''): int {
266
+        $attempts = $this->getAttempts($ip, $action);
267
+        if ($attempts === 0) {
268
+            return 0;
269
+        }
270
+
271
+        $firstDelay = 0.1;
272
+        if ($attempts > self::MAX_ATTEMPTS) {
273
+            // Don't ever overflow. Just assume the maxDelay time:s
274
+            return self::MAX_DELAY_MS;
275
+        }
276
+
277
+        $delay = $firstDelay * 2**$attempts;
278
+        if ($delay > self::MAX_DELAY) {
279
+            return self::MAX_DELAY_MS;
280
+        }
281
+        return (int) \ceil($delay * 1000);
282
+    }
283
+
284
+    /**
285
+     * Reset the throttling delay for an IP address, action and metadata
286
+     *
287
+     * @param string $ip
288
+     * @param string $action
289
+     * @param array $metadata
290
+     */
291
+    public function resetDelay(string $ip, string $action, array $metadata): void {
292
+        $ipAddress = new IpAddress($ip);
293
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
294
+            return;
295
+        }
296
+
297
+        $cutoffTime = $this->getCutoffTimestamp();
298
+
299
+        $qb = $this->db->getQueryBuilder();
300
+        $qb->delete('bruteforce_attempts')
301
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
302
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())))
303
+            ->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)))
304
+            ->andWhere($qb->expr()->eq('metadata', $qb->createNamedParameter(json_encode($metadata))));
305
+
306
+        $qb->execute();
307
+    }
308
+
309
+    /**
310
+     * Reset the throttling delay for an IP address
311
+     *
312
+     * @param string $ip
313
+     */
314
+    public function resetDelayForIP($ip) {
315
+        $cutoffTime = $this->getCutoffTimestamp();
316
+
317
+        $qb = $this->db->getQueryBuilder();
318
+        $qb->delete('bruteforce_attempts')
319
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
320
+            ->andWhere($qb->expr()->eq('ip', $qb->createNamedParameter($ip)));
321
+
322
+        $qb->execute();
323
+    }
324
+
325
+    /**
326
+     * Will sleep for the defined amount of time
327
+     *
328
+     * @param string $ip
329
+     * @param string $action optionally filter by action
330
+     * @return int the time spent sleeping
331
+     */
332
+    public function sleepDelay(string $ip, string $action = ''): int {
333
+        $delay = $this->getDelay($ip, $action);
334
+        usleep($delay * 1000);
335
+        return $delay;
336
+    }
337
+
338
+    /**
339
+     * Will sleep for the defined amount of time unless maximum was reached in the last 30 minutes
340
+     * In this case a "429 Too Many Request" exception is thrown
341
+     *
342
+     * @param string $ip
343
+     * @param string $action optionally filter by action
344
+     * @return int the time spent sleeping
345
+     * @throws MaxDelayReached when reached the maximum
346
+     */
347
+    public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int {
348
+        $delay = $this->getDelay($ip, $action);
349
+        if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > self::MAX_ATTEMPTS) {
350
+            // If the ip made too many attempts within the last 30 mins we don't execute anymore
351
+            throw new MaxDelayReached('Reached maximum delay');
352
+        }
353
+        usleep($delay * 1000);
354
+        return $delay;
355
+    }
356 356
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -93,7 +93,7 @@  discard block
 block discarded – undo
93 93
 	private function getCutoff(int $expire): \DateInterval {
94 94
 		$d1 = new \DateTime();
95 95
 		$d2 = clone $d1;
96
-		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
96
+		$d2->sub(new \DateInterval('PT'.$expire.'S'));
97 97
 		return $d2->diff($d1);
98 98
 	}
99 99
 
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
 		$values = [
130 130
 			'action' => $action,
131 131
 			'occurred' => $this->timeFactory->getTime(),
132
-			'ip' => (string)$ipAddress,
132
+			'ip' => (string) $ipAddress,
133 133
 			'subnet' => $ipAddress->getSubnet(),
134 134
 			'metadata' => json_encode($metadata),
135 135
 		];
@@ -165,7 +165,7 @@  discard block
 block discarded – undo
165 165
 		}
166 166
 
167 167
 		$keys = $this->config->getAppKeys('bruteForce');
168
-		$keys = array_filter($keys, function ($key) {
168
+		$keys = array_filter($keys, function($key) {
169 169
 			return 0 === strpos($key, 'whitelist_');
170 170
 		});
171 171
 
@@ -184,7 +184,7 @@  discard block
 block discarded – undo
184 184
 
185 185
 			$cx = explode('/', $cidr);
186 186
 			$addr = $cx[0];
187
-			$mask = (int)$cx[1];
187
+			$mask = (int) $cx[1];
188 188
 
189 189
 			// Do not compare ipv4 to ipv6
190 190
 			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
@@ -196,8 +196,8 @@  discard block
 block discarded – undo
196 196
 
197 197
 			$valid = true;
198 198
 			for ($i = 0; $i < $mask; $i++) {
199
-				$part = ord($addr[(int)($i/8)]);
200
-				$orig = ord($ip[(int)($i/8)]);
199
+				$part = ord($addr[(int) ($i / 8)]);
200
+				$orig = ord($ip[(int) ($i / 8)]);
201 201
 
202 202
 				$bitmask = 1 << (7 - ($i % 8));
203 203
 
@@ -232,7 +232,7 @@  discard block
 block discarded – undo
232 232
 		}
233 233
 
234 234
 		$ipAddress = new IpAddress($ip);
235
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
235
+		if ($this->isIPWhitelisted((string) $ipAddress)) {
236 236
 			return 0;
237 237
 		}
238 238
 
@@ -290,7 +290,7 @@  discard block
 block discarded – undo
290 290
 	 */
291 291
 	public function resetDelay(string $ip, string $action, array $metadata): void {
292 292
 		$ipAddress = new IpAddress($ip);
293
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
293
+		if ($this->isIPWhitelisted((string) $ipAddress)) {
294 294
 			return;
295 295
 		}
296 296
 
Please login to merge, or discard this patch.