Passed
Push — master ( 4d4a22...bfc37a )
by Joas
14:44 queued 14s
created

BruteForceMiddleware   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 40
c 0
b 0
f 0
dl 0
loc 89
rs 10
wmc 15

4 Methods

Rating   Name   Duplication   Size   Complexity  
B afterController() 0 32 7
A afterException() 0 10 3
A beforeController() 0 18 4
A __construct() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2023 Joas Schilling <[email protected]>
7
 * @copyright Copyright (c) 2017 Lukas Reschke <[email protected]>
8
 *
9
 * @author Christoph Wurst <[email protected]>
10
 * @author Joas Schilling <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 *
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
namespace OC\AppFramework\Middleware\Security;
30
31
use OC\AppFramework\Utility\ControllerMethodReflector;
32
use OC\Security\Bruteforce\Throttler;
33
use OCP\AppFramework\Controller;
34
use OCP\AppFramework\Http;
35
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
36
use OCP\AppFramework\Http\Response;
37
use OCP\AppFramework\Http\TooManyRequestsResponse;
38
use OCP\AppFramework\Middleware;
39
use OCP\AppFramework\OCS\OCSException;
40
use OCP\AppFramework\OCSController;
41
use OCP\IRequest;
42
use OCP\Security\Bruteforce\MaxDelayReached;
43
use Psr\Log\LoggerInterface;
44
use ReflectionMethod;
45
46
/**
47
 * Class BruteForceMiddleware performs the bruteforce protection for controllers
48
 * that are annotated with @BruteForceProtection(action=$action) whereas $action
49
 * is the action that should be logged within the database.
50
 *
51
 * @package OC\AppFramework\Middleware\Security
52
 */
53
class BruteForceMiddleware extends Middleware {
54
	public function __construct(
55
		protected ControllerMethodReflector $reflector,
56
		protected Throttler $throttler,
57
		protected IRequest $request,
58
		protected LoggerInterface $logger,
59
	) {
60
	}
61
62
	/**
63
	 * {@inheritDoc}
64
	 */
65
	public function beforeController($controller, $methodName) {
66
		parent::beforeController($controller, $methodName);
67
68
		if ($this->reflector->hasAnnotation('BruteForceProtection')) {
69
			$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
70
			$this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
71
		} else {
72
			$reflectionMethod = new ReflectionMethod($controller, $methodName);
73
			$attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
74
75
			if (!empty($attributes)) {
76
				$remoteAddress = $this->request->getRemoteAddress();
77
78
				foreach ($attributes as $attribute) {
79
					/** @var BruteForceProtection $protection */
80
					$protection = $attribute->newInstance();
81
					$action = $protection->getAction();
82
					$this->throttler->sleepDelayOrThrowOnMax($remoteAddress, $action);
83
				}
84
			}
85
		}
86
	}
87
88
	/**
89
	 * {@inheritDoc}
90
	 */
91
	public function afterController($controller, $methodName, Response $response) {
92
		if ($response->isThrottled()) {
93
			if ($this->reflector->hasAnnotation('BruteForceProtection')) {
94
				$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
95
				$ip = $this->request->getRemoteAddress();
96
				$this->throttler->sleepDelay($ip, $action);
97
				$this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
98
			} else {
99
				$reflectionMethod = new ReflectionMethod($controller, $methodName);
100
				$attributes = $reflectionMethod->getAttributes(BruteForceProtection::class);
101
102
				if (!empty($attributes)) {
103
					$ip = $this->request->getRemoteAddress();
104
					$metaData = $response->getThrottleMetadata();
105
106
					foreach ($attributes as $attribute) {
107
						/** @var BruteForceProtection $protection */
108
						$protection = $attribute->newInstance();
109
						$action = $protection->getAction();
110
111
						if (!isset($metaData['action']) || $metaData['action'] === $action) {
112
							$this->throttler->sleepDelay($ip, $action);
113
							$this->throttler->registerAttempt($action, $ip, $metaData);
114
						}
115
					}
116
				} else {
117
					$this->logger->debug('Response for ' . get_class($controller) . '::' . $methodName . ' got bruteforce throttled but has no annotation nor attribute defined.');
118
				}
119
			}
120
		}
121
122
		return parent::afterController($controller, $methodName, $response);
123
	}
124
125
	/**
126
	 * @param Controller $controller
127
	 * @param string $methodName
128
	 * @param \Exception $exception
129
	 * @throws \Exception
130
	 * @return Response
131
	 */
132
	public function afterException($controller, $methodName, \Exception $exception): Response {
133
		if ($exception instanceof MaxDelayReached) {
134
			if ($controller instanceof OCSController) {
135
				throw new OCSException($exception->getMessage(), Http::STATUS_TOO_MANY_REQUESTS);
136
			}
137
138
			return new TooManyRequestsResponse();
139
		}
140
141
		throw $exception;
142
	}
143
}
144