Completed
Pull Request — master (#4336)
by Lukas
17:56 queued 06:34
created
lib/private/Security/RateLimiting/Backend/MemoryCache.php 2 patches
Indentation   +57 added lines, -57 removed lines patch added patch discarded remove patch
@@ -32,69 +32,69 @@
 block discarded – undo
32 32
  * @package OC\Security\RateLimiting\Backend
33 33
  */
34 34
 class MemoryCache implements IBackend {
35
-	/** @var ICache */
36
-	private $cache;
37
-	/** @var ITimeFactory */
38
-	private $timeFactory;
35
+    /** @var ICache */
36
+    private $cache;
37
+    /** @var ITimeFactory */
38
+    private $timeFactory;
39 39
 
40
-	/**
41
-	 * @param ICacheFactory $cacheFactory
42
-	 * @param ITimeFactory $timeFactory
43
-	 */
44
-	public function __construct(ICacheFactory $cacheFactory,
45
-								ITimeFactory $timeFactory) {
46
-		$this->cache = $cacheFactory->create(__CLASS__);
47
-		$this->timeFactory = $timeFactory;
48
-	}
40
+    /**
41
+     * @param ICacheFactory $cacheFactory
42
+     * @param ITimeFactory $timeFactory
43
+     */
44
+    public function __construct(ICacheFactory $cacheFactory,
45
+                                ITimeFactory $timeFactory) {
46
+        $this->cache = $cacheFactory->create(__CLASS__);
47
+        $this->timeFactory = $timeFactory;
48
+    }
49 49
 
50
-	/**
51
-	 * @param string $methodIdentifier
52
-	 * @param string $userIdentifier
53
-	 * @return string
54
-	 */
55
-	private function hash($methodIdentifier, $userIdentifier) {
56
-		return hash('sha512', $methodIdentifier . $userIdentifier);
57
-	}
50
+    /**
51
+     * @param string $methodIdentifier
52
+     * @param string $userIdentifier
53
+     * @return string
54
+     */
55
+    private function hash($methodIdentifier, $userIdentifier) {
56
+        return hash('sha512', $methodIdentifier . $userIdentifier);
57
+    }
58 58
 
59
-	/**
60
-	 * @param string $identifier
61
-	 * @return array
62
-	 */
63
-	private function getExistingAttempts($identifier) {
64
-		$cachedAttempts = json_decode($this->cache->get($identifier), true);
65
-		if(is_array($cachedAttempts)) {
66
-			return $cachedAttempts;
67
-		}
59
+    /**
60
+     * @param string $identifier
61
+     * @return array
62
+     */
63
+    private function getExistingAttempts($identifier) {
64
+        $cachedAttempts = json_decode($this->cache->get($identifier), true);
65
+        if(is_array($cachedAttempts)) {
66
+            return $cachedAttempts;
67
+        }
68 68
 
69
-		return [];
70
-	}
69
+        return [];
70
+    }
71 71
 
72
-	/**
73
-	 * {@inheritDoc}
74
-	 */
75
-	public function getAttempts($methodIdentifier, $userIdentifier, $seconds) {
76
-		$identifier = $this->hash($methodIdentifier, $userIdentifier);
77
-		$existingAttempts = $this->getExistingAttempts($identifier);
72
+    /**
73
+     * {@inheritDoc}
74
+     */
75
+    public function getAttempts($methodIdentifier, $userIdentifier, $seconds) {
76
+        $identifier = $this->hash($methodIdentifier, $userIdentifier);
77
+        $existingAttempts = $this->getExistingAttempts($identifier);
78 78
 
79
-		$count = 0;
80
-		$currentTime = $this->timeFactory->getTime();
81
-		/** @var array $existingAttempts */
82
-		foreach ($existingAttempts as $attempt) {
83
-			if(($attempt + $seconds) > $currentTime) {
84
-				$count++;
85
-			}
86
-		}
79
+        $count = 0;
80
+        $currentTime = $this->timeFactory->getTime();
81
+        /** @var array $existingAttempts */
82
+        foreach ($existingAttempts as $attempt) {
83
+            if(($attempt + $seconds) > $currentTime) {
84
+                $count++;
85
+            }
86
+        }
87 87
 
88
-		return $count;
89
-	}
88
+        return $count;
89
+    }
90 90
 
91
-	/**
92
-	 * {@inheritDoc}
93
-	 */
94
-	public function registerAttempt($methodIdentifier, $userIdentifier, $timestamp) {
95
-		$identifier = $this->hash($methodIdentifier, $userIdentifier);
96
-		$existingAttempts = $this->getExistingAttempts($identifier);
97
-		$existingAttempts[] = (string)$timestamp;
98
-		$this->cache->set($identifier, json_encode($existingAttempts));
99
-	}
91
+    /**
92
+     * {@inheritDoc}
93
+     */
94
+    public function registerAttempt($methodIdentifier, $userIdentifier, $timestamp) {
95
+        $identifier = $this->hash($methodIdentifier, $userIdentifier);
96
+        $existingAttempts = $this->getExistingAttempts($identifier);
97
+        $existingAttempts[] = (string)$timestamp;
98
+        $this->cache->set($identifier, json_encode($existingAttempts));
99
+    }
100 100
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -53,7 +53,7 @@  discard block
 block discarded – undo
53 53
 	 * @return string
54 54
 	 */
55 55
 	private function hash($methodIdentifier, $userIdentifier) {
56
-		return hash('sha512', $methodIdentifier . $userIdentifier);
56
+		return hash('sha512', $methodIdentifier.$userIdentifier);
57 57
 	}
58 58
 
59 59
 	/**
@@ -62,7 +62,7 @@  discard block
 block discarded – undo
62 62
 	 */
63 63
 	private function getExistingAttempts($identifier) {
64 64
 		$cachedAttempts = json_decode($this->cache->get($identifier), true);
65
-		if(is_array($cachedAttempts)) {
65
+		if (is_array($cachedAttempts)) {
66 66
 			return $cachedAttempts;
67 67
 		}
68 68
 
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 		$currentTime = $this->timeFactory->getTime();
81 81
 		/** @var array $existingAttempts */
82 82
 		foreach ($existingAttempts as $attempt) {
83
-			if(($attempt + $seconds) > $currentTime) {
83
+			if (($attempt + $seconds) > $currentTime) {
84 84
 				$count++;
85 85
 			}
86 86
 		}
@@ -94,7 +94,7 @@  discard block
 block discarded – undo
94 94
 	public function registerAttempt($methodIdentifier, $userIdentifier, $timestamp) {
95 95
 		$identifier = $this->hash($methodIdentifier, $userIdentifier);
96 96
 		$existingAttempts = $this->getExistingAttempts($identifier);
97
-		$existingAttempts[] = (string)$timestamp;
97
+		$existingAttempts[] = (string) $timestamp;
98 98
 		$this->cache->set($identifier, json_encode($existingAttempts));
99 99
 	}
100 100
 }
Please login to merge, or discard this patch.
lib/private/Security/RateLimiting/Limiter.php 2 patches
Indentation   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -30,77 +30,77 @@
 block discarded – undo
30 30
 use OCP\IUserSession;
31 31
 
32 32
 class Limiter {
33
-	/** @var IBackend */
34
-	private $backend;
35
-	/** @var ITimeFactory */
36
-	private $timeFactory;
33
+    /** @var IBackend */
34
+    private $backend;
35
+    /** @var ITimeFactory */
36
+    private $timeFactory;
37 37
 
38
-	/**
39
-	 * @param IUserSession $userSession
40
-	 * @param IRequest $request
41
-	 * @param ITimeFactory $timeFactory
42
-	 * @param IBackend $backend
43
-	 */
44
-	public function __construct(IUserSession $userSession,
45
-								IRequest $request,
46
-								ITimeFactory $timeFactory,
47
-								IBackend $backend) {
48
-		$this->backend = $backend;
49
-		$this->timeFactory = $timeFactory;
50
-	}
38
+    /**
39
+     * @param IUserSession $userSession
40
+     * @param IRequest $request
41
+     * @param ITimeFactory $timeFactory
42
+     * @param IBackend $backend
43
+     */
44
+    public function __construct(IUserSession $userSession,
45
+                                IRequest $request,
46
+                                ITimeFactory $timeFactory,
47
+                                IBackend $backend) {
48
+        $this->backend = $backend;
49
+        $this->timeFactory = $timeFactory;
50
+    }
51 51
 
52
-	/**
53
-	 * @param string $methodIdentifier
54
-	 * @param string $userIdentifier
55
-	 * @param int $period
56
-	 * @param int $limit
57
-	 * @throws RateLimitExceededException
58
-	 */
59
-	private function register($methodIdentifier,
60
-							  $userIdentifier,
61
-							  $period,
62
-							  $limit) {
63
-		$existingAttempts = $this->backend->getAttempts($methodIdentifier, $userIdentifier, (int)$period);
64
-		if ($existingAttempts >= (int)$limit) {
65
-			throw new RateLimitExceededException();
66
-		}
52
+    /**
53
+     * @param string $methodIdentifier
54
+     * @param string $userIdentifier
55
+     * @param int $period
56
+     * @param int $limit
57
+     * @throws RateLimitExceededException
58
+     */
59
+    private function register($methodIdentifier,
60
+                                $userIdentifier,
61
+                                $period,
62
+                                $limit) {
63
+        $existingAttempts = $this->backend->getAttempts($methodIdentifier, $userIdentifier, (int)$period);
64
+        if ($existingAttempts >= (int)$limit) {
65
+            throw new RateLimitExceededException();
66
+        }
67 67
 
68
-		$this->backend->registerAttempt($methodIdentifier, $userIdentifier, $this->timeFactory->getTime());
69
-	}
68
+        $this->backend->registerAttempt($methodIdentifier, $userIdentifier, $this->timeFactory->getTime());
69
+    }
70 70
 
71
-	/**
72
-	 * Registers attempt for an anonymous request
73
-	 *
74
-	 * @param string $identifier
75
-	 * @param int $anonLimit
76
-	 * @param int $anonPeriod
77
-	 * @param string $ip
78
-	 * @throws RateLimitExceededException
79
-	 */
80
-	public function registerAnonRequest($identifier,
81
-										$anonLimit,
82
-										$anonPeriod,
83
-										$ip) {
84
-		$ipSubnet = (new IpAddress($ip))->getSubnet();
71
+    /**
72
+     * Registers attempt for an anonymous request
73
+     *
74
+     * @param string $identifier
75
+     * @param int $anonLimit
76
+     * @param int $anonPeriod
77
+     * @param string $ip
78
+     * @throws RateLimitExceededException
79
+     */
80
+    public function registerAnonRequest($identifier,
81
+                                        $anonLimit,
82
+                                        $anonPeriod,
83
+                                        $ip) {
84
+        $ipSubnet = (new IpAddress($ip))->getSubnet();
85 85
 
86
-		$anonHashIdentifier = hash('sha512', 'anon::' . $identifier . $ipSubnet);
87
-		$this->register($identifier, $anonHashIdentifier, $anonPeriod, $anonLimit);
88
-	}
86
+        $anonHashIdentifier = hash('sha512', 'anon::' . $identifier . $ipSubnet);
87
+        $this->register($identifier, $anonHashIdentifier, $anonPeriod, $anonLimit);
88
+    }
89 89
 
90
-	/**
91
-	 * Registers attempt for an authenticated request
92
-	 *
93
-	 * @param string $identifier
94
-	 * @param int $userLimit
95
-	 * @param int $userPeriod
96
-	 * @param IUser $user
97
-	 * @throws RateLimitExceededException
98
-	 */
99
-	public function registerUserRequest($identifier,
100
-										$userLimit,
101
-										$userPeriod,
102
-										IUser $user) {
103
-		$userHashIdentifier = hash('sha512', 'user::' . $identifier . $user->getUID());
104
-		$this->register($identifier, $userHashIdentifier, $userPeriod, $userLimit);
105
-	}
90
+    /**
91
+     * Registers attempt for an authenticated request
92
+     *
93
+     * @param string $identifier
94
+     * @param int $userLimit
95
+     * @param int $userPeriod
96
+     * @param IUser $user
97
+     * @throws RateLimitExceededException
98
+     */
99
+    public function registerUserRequest($identifier,
100
+                                        $userLimit,
101
+                                        $userPeriod,
102
+                                        IUser $user) {
103
+        $userHashIdentifier = hash('sha512', 'user::' . $identifier . $user->getUID());
104
+        $this->register($identifier, $userHashIdentifier, $userPeriod, $userLimit);
105
+    }
106 106
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -60,8 +60,8 @@  discard block
 block discarded – undo
60 60
 							  $userIdentifier,
61 61
 							  $period,
62 62
 							  $limit) {
63
-		$existingAttempts = $this->backend->getAttempts($methodIdentifier, $userIdentifier, (int)$period);
64
-		if ($existingAttempts >= (int)$limit) {
63
+		$existingAttempts = $this->backend->getAttempts($methodIdentifier, $userIdentifier, (int) $period);
64
+		if ($existingAttempts >= (int) $limit) {
65 65
 			throw new RateLimitExceededException();
66 66
 		}
67 67
 
@@ -83,7 +83,7 @@  discard block
 block discarded – undo
83 83
 										$ip) {
84 84
 		$ipSubnet = (new IpAddress($ip))->getSubnet();
85 85
 
86
-		$anonHashIdentifier = hash('sha512', 'anon::' . $identifier . $ipSubnet);
86
+		$anonHashIdentifier = hash('sha512', 'anon::'.$identifier.$ipSubnet);
87 87
 		$this->register($identifier, $anonHashIdentifier, $anonPeriod, $anonLimit);
88 88
 	}
89 89
 
@@ -100,7 +100,7 @@  discard block
 block discarded – undo
100 100
 										$userLimit,
101 101
 										$userPeriod,
102 102
 										IUser $user) {
103
-		$userHashIdentifier = hash('sha512', 'user::' . $identifier . $user->getUID());
103
+		$userHashIdentifier = hash('sha512', 'user::'.$identifier.$user->getUID());
104 104
 		$this->register($identifier, $userHashIdentifier, $userPeriod, $userLimit);
105 105
 	}
106 106
 }
Please login to merge, or discard this patch.
lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -25,7 +25,7 @@
 block discarded – undo
25 25
 use OCP\AppFramework\Http;
26 26
 
27 27
 class RateLimitExceededException extends SecurityException {
28
-	public function __construct() {
29
-		parent::__construct('Rate limit exceeded', Http::STATUS_TOO_MANY_REQUESTS);
30
-	}
28
+    public function __construct() {
29
+        parent::__construct('Rate limit exceeded', Http::STATUS_TOO_MANY_REQUESTS);
30
+    }
31 31
 }
Please login to merge, or discard this patch.
lib/private/Security/Normalizer/IpAddress.php 1 patch
Indentation   +70 added lines, -70 removed lines patch added patch discarded remove patch
@@ -28,79 +28,79 @@
 block discarded – undo
28 28
  * @package OC\Security\Normalizer
29 29
  */
30 30
 class IpAddress {
31
-	/** @var string */
32
-	private $ip;
31
+    /** @var string */
32
+    private $ip;
33 33
 
34
-	/**
35
-	 * @param string $ip IP to normalized
36
-	 */
37
-	public function __construct($ip) {
38
-		$this->ip = $ip;
39
-	}
34
+    /**
35
+     * @param string $ip IP to normalized
36
+     */
37
+    public function __construct($ip) {
38
+        $this->ip = $ip;
39
+    }
40 40
 
41
-	/**
42
-	 * Return the given subnet for an IPv4 address and mask bits
43
-	 *
44
-	 * @param string $ip
45
-	 * @param int $maskBits
46
-	 * @return string
47
-	 */
48
-	private function getIPv4Subnet($ip,
49
-								   $maskBits = 32) {
50
-		$binary = \inet_pton($ip);
51
-		for ($i = 32; $i > $maskBits; $i -= 8) {
52
-			$j = \intdiv($i, 8) - 1;
53
-			$k = (int) \min(8, $i - $maskBits);
54
-			$mask = (0xff - ((pow(2, $k)) - 1));
55
-			$int = \unpack('C', $binary[$j]);
56
-			$binary[$j] = \pack('C', $int[1] & $mask);
57
-		}
58
-		return \inet_ntop($binary).'/'.$maskBits;
59
-	}
41
+    /**
42
+     * Return the given subnet for an IPv4 address and mask bits
43
+     *
44
+     * @param string $ip
45
+     * @param int $maskBits
46
+     * @return string
47
+     */
48
+    private function getIPv4Subnet($ip,
49
+                                    $maskBits = 32) {
50
+        $binary = \inet_pton($ip);
51
+        for ($i = 32; $i > $maskBits; $i -= 8) {
52
+            $j = \intdiv($i, 8) - 1;
53
+            $k = (int) \min(8, $i - $maskBits);
54
+            $mask = (0xff - ((pow(2, $k)) - 1));
55
+            $int = \unpack('C', $binary[$j]);
56
+            $binary[$j] = \pack('C', $int[1] & $mask);
57
+        }
58
+        return \inet_ntop($binary).'/'.$maskBits;
59
+    }
60 60
 
61
-	/**
62
-	 * Return the given subnet for an IPv6 address and mask bits
63
-	 *
64
-	 * @param string $ip
65
-	 * @param int $maskBits
66
-	 * @return string
67
-	 */
68
-	private function getIPv6Subnet($ip, $maskBits = 48) {
69
-		$binary = \inet_pton($ip);
70
-		for ($i = 128; $i > $maskBits; $i -= 8) {
71
-			$j = \intdiv($i, 8) - 1;
72
-			$k = (int) \min(8, $i - $maskBits);
73
-			$mask = (0xff - ((pow(2, $k)) - 1));
74
-			$int = \unpack('C', $binary[$j]);
75
-			$binary[$j] = \pack('C', $int[1] & $mask);
76
-		}
77
-		return \inet_ntop($binary).'/'.$maskBits;
78
-	}
61
+    /**
62
+     * Return the given subnet for an IPv6 address and mask bits
63
+     *
64
+     * @param string $ip
65
+     * @param int $maskBits
66
+     * @return string
67
+     */
68
+    private function getIPv6Subnet($ip, $maskBits = 48) {
69
+        $binary = \inet_pton($ip);
70
+        for ($i = 128; $i > $maskBits; $i -= 8) {
71
+            $j = \intdiv($i, 8) - 1;
72
+            $k = (int) \min(8, $i - $maskBits);
73
+            $mask = (0xff - ((pow(2, $k)) - 1));
74
+            $int = \unpack('C', $binary[$j]);
75
+            $binary[$j] = \pack('C', $int[1] & $mask);
76
+        }
77
+        return \inet_ntop($binary).'/'.$maskBits;
78
+    }
79 79
 
80
-	/**
81
-	 * Gets either the /32 (IPv4) or the /128 (IPv6) subnet of an IP address
82
-	 *
83
-	 * @return string
84
-	 */
85
-	public function getSubnet() {
86
-		if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->ip)) {
87
-			return $this->getIPv4Subnet(
88
-				$this->ip,
89
-				32
90
-			);
91
-		}
92
-		return $this->getIPv6Subnet(
93
-			$this->ip,
94
-			128
95
-		);
96
-	}
80
+    /**
81
+     * Gets either the /32 (IPv4) or the /128 (IPv6) subnet of an IP address
82
+     *
83
+     * @return string
84
+     */
85
+    public function getSubnet() {
86
+        if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $this->ip)) {
87
+            return $this->getIPv4Subnet(
88
+                $this->ip,
89
+                32
90
+            );
91
+        }
92
+        return $this->getIPv6Subnet(
93
+            $this->ip,
94
+            128
95
+        );
96
+    }
97 97
 
98
-	/**
99
-	 * Returns the specified IP address
100
-	 *
101
-	 * @return string
102
-	 */
103
-	public function __toString() {
104
-		return $this->ip;
105
-	}
98
+    /**
99
+     * Returns the specified IP address
100
+     *
101
+     * @return string
102
+     */
103
+    public function __toString() {
104
+        return $this->ip;
105
+    }
106 106
 }
Please login to merge, or discard this patch.
lib/private/Security/Bruteforce/Throttler.php 2 patches
Indentation   +203 added lines, -203 removed lines patch added patch discarded remove patch
@@ -43,207 +43,207 @@
 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
-		$keys = $this->config->getAppKeys('bruteForce');
137
-		$keys = array_filter($keys, function($key) {
138
-			$regex = '/^whitelist_/S';
139
-			return preg_match($regex, $key) === 1;
140
-		});
141
-
142
-		if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
143
-			$type = 4;
144
-		} else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
145
-			$type = 6;
146
-		} else {
147
-			return false;
148
-		}
149
-
150
-		$ip = inet_pton($ip);
151
-
152
-		foreach ($keys as $key) {
153
-			$cidr = $this->config->getAppValue('bruteForce', $key, null);
154
-
155
-			$cx = explode('/', $cidr);
156
-			$addr = $cx[0];
157
-			$mask = (int)$cx[1];
158
-
159
-			// Do not compare ipv4 to ipv6
160
-			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
161
-				($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
162
-				continue;
163
-			}
164
-
165
-			$addr = inet_pton($addr);
166
-
167
-			$valid = true;
168
-			for($i = 0; $i < $mask; $i++) {
169
-				$part = ord($addr[(int)($i/8)]);
170
-				$orig = ord($ip[(int)($i/8)]);
171
-
172
-				$part = $part & (15 << (1 - ($i % 2)));
173
-				$orig = $orig & (15 << (1 - ($i % 2)));
174
-
175
-				if ($part !== $orig) {
176
-					$valid = false;
177
-					break;
178
-				}
179
-			}
180
-
181
-			if ($valid === true) {
182
-				return true;
183
-			}
184
-		}
185
-
186
-		return false;
187
-
188
-	}
189
-
190
-	/**
191
-	 * Get the throttling delay (in milliseconds)
192
-	 *
193
-	 * @param string $ip
194
-	 * @param string $action optionally filter by action
195
-	 * @return int
196
-	 */
197
-	public function getDelay($ip, $action = '') {
198
-		$ipAddress = new IpAddress($ip);
199
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
200
-			return 0;
201
-		}
202
-
203
-		$cutoffTime = (new \DateTime())
204
-			->sub($this->getCutoff(43200))
205
-			->getTimestamp();
206
-
207
-		$qb = $this->db->getQueryBuilder();
208
-		$qb->select('*')
209
-			->from('bruteforce_attempts')
210
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
211
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
212
-
213
-		if ($action !== '') {
214
-			$qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
215
-		}
216
-
217
-		$attempts = count($qb->execute()->fetchAll());
218
-
219
-		if ($attempts === 0) {
220
-			return 0;
221
-		}
222
-
223
-		$maxDelay = 30;
224
-		$firstDelay = 0.1;
225
-		if ($attempts > (8 * PHP_INT_SIZE - 1))  {
226
-			// Don't ever overflow. Just assume the maxDelay time:s
227
-			$firstDelay = $maxDelay;
228
-		} else {
229
-			$firstDelay *= pow(2, $attempts);
230
-			if ($firstDelay > $maxDelay) {
231
-				$firstDelay = $maxDelay;
232
-			}
233
-		}
234
-		return (int) \ceil($firstDelay * 1000);
235
-	}
236
-
237
-	/**
238
-	 * Will sleep for the defined amount of time
239
-	 *
240
-	 * @param string $ip
241
-	 * @param string $action optionally filter by action
242
-	 * @return int the time spent sleeping
243
-	 */
244
-	public function sleepDelay($ip, $action = '') {
245
-		$delay = $this->getDelay($ip, $action);
246
-		usleep($delay * 1000);
247
-		return $delay;
248
-	}
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
+        $keys = $this->config->getAppKeys('bruteForce');
137
+        $keys = array_filter($keys, function($key) {
138
+            $regex = '/^whitelist_/S';
139
+            return preg_match($regex, $key) === 1;
140
+        });
141
+
142
+        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
143
+            $type = 4;
144
+        } else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
145
+            $type = 6;
146
+        } else {
147
+            return false;
148
+        }
149
+
150
+        $ip = inet_pton($ip);
151
+
152
+        foreach ($keys as $key) {
153
+            $cidr = $this->config->getAppValue('bruteForce', $key, null);
154
+
155
+            $cx = explode('/', $cidr);
156
+            $addr = $cx[0];
157
+            $mask = (int)$cx[1];
158
+
159
+            // Do not compare ipv4 to ipv6
160
+            if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
161
+                ($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
162
+                continue;
163
+            }
164
+
165
+            $addr = inet_pton($addr);
166
+
167
+            $valid = true;
168
+            for($i = 0; $i < $mask; $i++) {
169
+                $part = ord($addr[(int)($i/8)]);
170
+                $orig = ord($ip[(int)($i/8)]);
171
+
172
+                $part = $part & (15 << (1 - ($i % 2)));
173
+                $orig = $orig & (15 << (1 - ($i % 2)));
174
+
175
+                if ($part !== $orig) {
176
+                    $valid = false;
177
+                    break;
178
+                }
179
+            }
180
+
181
+            if ($valid === true) {
182
+                return true;
183
+            }
184
+        }
185
+
186
+        return false;
187
+
188
+    }
189
+
190
+    /**
191
+     * Get the throttling delay (in milliseconds)
192
+     *
193
+     * @param string $ip
194
+     * @param string $action optionally filter by action
195
+     * @return int
196
+     */
197
+    public function getDelay($ip, $action = '') {
198
+        $ipAddress = new IpAddress($ip);
199
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
200
+            return 0;
201
+        }
202
+
203
+        $cutoffTime = (new \DateTime())
204
+            ->sub($this->getCutoff(43200))
205
+            ->getTimestamp();
206
+
207
+        $qb = $this->db->getQueryBuilder();
208
+        $qb->select('*')
209
+            ->from('bruteforce_attempts')
210
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
211
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
212
+
213
+        if ($action !== '') {
214
+            $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
215
+        }
216
+
217
+        $attempts = count($qb->execute()->fetchAll());
218
+
219
+        if ($attempts === 0) {
220
+            return 0;
221
+        }
222
+
223
+        $maxDelay = 30;
224
+        $firstDelay = 0.1;
225
+        if ($attempts > (8 * PHP_INT_SIZE - 1))  {
226
+            // Don't ever overflow. Just assume the maxDelay time:s
227
+            $firstDelay = $maxDelay;
228
+        } else {
229
+            $firstDelay *= pow(2, $attempts);
230
+            if ($firstDelay > $maxDelay) {
231
+                $firstDelay = $maxDelay;
232
+            }
233
+        }
234
+        return (int) \ceil($firstDelay * 1000);
235
+    }
236
+
237
+    /**
238
+     * Will sleep for the defined amount of time
239
+     *
240
+     * @param string $ip
241
+     * @param string $action optionally filter by action
242
+     * @return int the time spent sleeping
243
+     */
244
+    public function sleepDelay($ip, $action = '') {
245
+        $delay = $this->getDelay($ip, $action);
246
+        usleep($delay * 1000);
247
+        return $delay;
248
+    }
249 249
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 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();
@@ -154,7 +154,7 @@  discard block
 block discarded – undo
154 154
 
155 155
 			$cx = explode('/', $cidr);
156 156
 			$addr = $cx[0];
157
-			$mask = (int)$cx[1];
157
+			$mask = (int) $cx[1];
158 158
 
159 159
 			// Do not compare ipv4 to ipv6
160 160
 			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
@@ -165,9 +165,9 @@  discard block
 block discarded – undo
165 165
 			$addr = inet_pton($addr);
166 166
 
167 167
 			$valid = true;
168
-			for($i = 0; $i < $mask; $i++) {
169
-				$part = ord($addr[(int)($i/8)]);
170
-				$orig = ord($ip[(int)($i/8)]);
168
+			for ($i = 0; $i < $mask; $i++) {
169
+				$part = ord($addr[(int) ($i / 8)]);
170
+				$orig = ord($ip[(int) ($i / 8)]);
171 171
 
172 172
 				$part = $part & (15 << (1 - ($i % 2)));
173 173
 				$orig = $orig & (15 << (1 - ($i % 2)));
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 	 */
197 197
 	public function getDelay($ip, $action = '') {
198 198
 		$ipAddress = new IpAddress($ip);
199
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
199
+		if ($this->isIPWhitelisted((string) $ipAddress)) {
200 200
 			return 0;
201 201
 		}
202 202
 
@@ -222,7 +222,7 @@  discard block
 block discarded – undo
222 222
 
223 223
 		$maxDelay = 30;
224 224
 		$firstDelay = 0.1;
225
-		if ($attempts > (8 * PHP_INT_SIZE - 1))  {
225
+		if ($attempts > (8 * PHP_INT_SIZE - 1)) {
226 226
 			// Don't ever overflow. Just assume the maxDelay time:s
227 227
 			$firstDelay = $maxDelay;
228 228
 		} else {
Please login to merge, or discard this patch.