Completed
Push — stable13 ( 61f20e...fb2de4 )
by Roeland
11:34 queued 10s
created
lib/private/Security/Bruteforce/Throttler.php 1 patch
Indentation   +235 added lines, -235 removed lines patch added patch discarded remove patch
@@ -46,239 +46,239 @@
 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
-				$part = $part & (15 << (1 - ($i % 2)));
181
-				$orig = $orig & (15 << (1 - ($i % 2)));
182
-
183
-				if ($part !== $orig) {
184
-					$valid = false;
185
-					break;
186
-				}
187
-			}
188
-
189
-			if ($valid === true) {
190
-				return true;
191
-			}
192
-		}
193
-
194
-		return false;
195
-
196
-	}
197
-
198
-	/**
199
-	 * Get the throttling delay (in milliseconds)
200
-	 *
201
-	 * @param string $ip
202
-	 * @param string $action optionally filter by action
203
-	 * @return int
204
-	 */
205
-	public function getDelay($ip, $action = '') {
206
-		$ipAddress = new IpAddress($ip);
207
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
208
-			return 0;
209
-		}
210
-
211
-		$cutoffTime = (new \DateTime())
212
-			->sub($this->getCutoff(43200))
213
-			->getTimestamp();
214
-
215
-		$qb = $this->db->getQueryBuilder();
216
-		$qb->select('*')
217
-			->from('bruteforce_attempts')
218
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
219
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
220
-
221
-		if ($action !== '') {
222
-			$qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
223
-		}
224
-
225
-		$attempts = count($qb->execute()->fetchAll());
226
-
227
-		if ($attempts === 0) {
228
-			return 0;
229
-		}
230
-
231
-		$maxDelay = 25;
232
-		$firstDelay = 0.1;
233
-		if ($attempts > (8 * PHP_INT_SIZE - 1))  {
234
-			// Don't ever overflow. Just assume the maxDelay time:s
235
-			$firstDelay = $maxDelay;
236
-		} else {
237
-			$firstDelay *= pow(2, $attempts);
238
-			if ($firstDelay > $maxDelay) {
239
-				$firstDelay = $maxDelay;
240
-			}
241
-		}
242
-		return (int) \ceil($firstDelay * 1000);
243
-	}
244
-
245
-	/**
246
-	 * Reset the throttling delay for an IP address, action and metadata
247
-	 *
248
-	 * @param string $ip
249
-	 * @param string $action
250
-	 * @param string $metadata
251
-	 */
252
-	public function resetDelay($ip, $action, $metadata) {
253
-		$ipAddress = new IpAddress($ip);
254
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
255
-			return;
256
-		}
257
-
258
-		$cutoffTime = (new \DateTime())
259
-			->sub($this->getCutoff(43200))
260
-			->getTimestamp();
261
-
262
-		$qb = $this->db->getQueryBuilder();
263
-		$qb->delete('bruteforce_attempts')
264
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
265
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())))
266
-			->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)))
267
-			->andWhere($qb->expr()->eq('metadata', $qb->createNamedParameter(json_encode($metadata))));
268
-
269
-		$qb->execute();
270
-	}
271
-
272
-	/**
273
-	 * Will sleep for the defined amount of time
274
-	 *
275
-	 * @param string $ip
276
-	 * @param string $action optionally filter by action
277
-	 * @return int the time spent sleeping
278
-	 */
279
-	public function sleepDelay($ip, $action = '') {
280
-		$delay = $this->getDelay($ip, $action);
281
-		usleep($delay * 1000);
282
-		return $delay;
283
-	}
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
+                $part = $part & (15 << (1 - ($i % 2)));
181
+                $orig = $orig & (15 << (1 - ($i % 2)));
182
+
183
+                if ($part !== $orig) {
184
+                    $valid = false;
185
+                    break;
186
+                }
187
+            }
188
+
189
+            if ($valid === true) {
190
+                return true;
191
+            }
192
+        }
193
+
194
+        return false;
195
+
196
+    }
197
+
198
+    /**
199
+     * Get the throttling delay (in milliseconds)
200
+     *
201
+     * @param string $ip
202
+     * @param string $action optionally filter by action
203
+     * @return int
204
+     */
205
+    public function getDelay($ip, $action = '') {
206
+        $ipAddress = new IpAddress($ip);
207
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
208
+            return 0;
209
+        }
210
+
211
+        $cutoffTime = (new \DateTime())
212
+            ->sub($this->getCutoff(43200))
213
+            ->getTimestamp();
214
+
215
+        $qb = $this->db->getQueryBuilder();
216
+        $qb->select('*')
217
+            ->from('bruteforce_attempts')
218
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
219
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
220
+
221
+        if ($action !== '') {
222
+            $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
223
+        }
224
+
225
+        $attempts = count($qb->execute()->fetchAll());
226
+
227
+        if ($attempts === 0) {
228
+            return 0;
229
+        }
230
+
231
+        $maxDelay = 25;
232
+        $firstDelay = 0.1;
233
+        if ($attempts > (8 * PHP_INT_SIZE - 1))  {
234
+            // Don't ever overflow. Just assume the maxDelay time:s
235
+            $firstDelay = $maxDelay;
236
+        } else {
237
+            $firstDelay *= pow(2, $attempts);
238
+            if ($firstDelay > $maxDelay) {
239
+                $firstDelay = $maxDelay;
240
+            }
241
+        }
242
+        return (int) \ceil($firstDelay * 1000);
243
+    }
244
+
245
+    /**
246
+     * Reset the throttling delay for an IP address, action and metadata
247
+     *
248
+     * @param string $ip
249
+     * @param string $action
250
+     * @param string $metadata
251
+     */
252
+    public function resetDelay($ip, $action, $metadata) {
253
+        $ipAddress = new IpAddress($ip);
254
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
255
+            return;
256
+        }
257
+
258
+        $cutoffTime = (new \DateTime())
259
+            ->sub($this->getCutoff(43200))
260
+            ->getTimestamp();
261
+
262
+        $qb = $this->db->getQueryBuilder();
263
+        $qb->delete('bruteforce_attempts')
264
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
265
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())))
266
+            ->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)))
267
+            ->andWhere($qb->expr()->eq('metadata', $qb->createNamedParameter(json_encode($metadata))));
268
+
269
+        $qb->execute();
270
+    }
271
+
272
+    /**
273
+     * Will sleep for the defined amount of time
274
+     *
275
+     * @param string $ip
276
+     * @param string $action optionally filter by action
277
+     * @return int the time spent sleeping
278
+     */
279
+    public function sleepDelay($ip, $action = '') {
280
+        $delay = $this->getDelay($ip, $action);
281
+        usleep($delay * 1000);
282
+        return $delay;
283
+    }
284 284
 }
Please login to merge, or discard this patch.