2017-04-13 16:50:44 -04:00
< ? php
2020-07-09 06:25:57 -04:00
2020-03-19 07:09:57 -04:00
declare ( strict_types = 1 );
2020-08-24 08:54:25 -04:00
2017-04-13 16:50:44 -04:00
/**
2023-02-28 16:26:22 -05:00
* @ copyright Copyright ( c ) 2023 Joas Schilling < coding @ schilljs . com >
2017-04-13 16:50:44 -04:00
* @ copyright Copyright ( c ) 2017 Lukas Reschke < lukas @ statuscode . ch >
*
2020-04-29 05:57:22 -04:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2020-08-24 08:54:25 -04:00
* @ author Joas Schilling < coding @ schilljs . com >
2017-11-06 09:56:42 -05:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
*
2017-04-13 16:50:44 -04:00
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2021-06-04 15:52:51 -04:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2017-04-13 16:50:44 -04:00
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
2019-12-03 13:57:53 -05:00
* along with this program . If not , see < http :// www . gnu . org / licenses />.
2017-04-13 16:50:44 -04:00
*
*/
namespace OC\AppFramework\Middleware\Security ;
use OC\AppFramework\Utility\ControllerMethodReflector ;
use OC\Security\Bruteforce\Throttler ;
2020-03-19 07:09:57 -04:00
use OCP\AppFramework\Controller ;
use OCP\AppFramework\Http ;
2023-02-28 16:26:22 -05:00
use OCP\AppFramework\Http\Attribute\BruteForceProtection ;
2017-04-13 16:50:44 -04:00
use OCP\AppFramework\Http\Response ;
2020-03-19 07:09:57 -04:00
use OCP\AppFramework\Http\TooManyRequestsResponse ;
2017-04-13 16:50:44 -04:00
use OCP\AppFramework\Middleware ;
2020-03-19 07:09:57 -04:00
use OCP\AppFramework\OCS\OCSException ;
use OCP\AppFramework\OCSController ;
2017-04-13 16:50:44 -04:00
use OCP\IRequest ;
2020-03-19 07:09:57 -04:00
use OCP\Security\Bruteforce\MaxDelayReached ;
2023-03-08 06:08:53 -05:00
use Psr\Log\LoggerInterface ;
2023-02-28 16:26:22 -05:00
use ReflectionMethod ;
2017-04-13 16:50:44 -04:00
/**
* Class BruteForceMiddleware performs the bruteforce protection for controllers
* that are annotated with @ BruteForceProtection ( action = $action ) whereas $action
* is the action that should be logged within the database .
*
* @ package OC\AppFramework\Middleware\Security
*/
class BruteForceMiddleware extends Middleware {
2023-03-08 06:08:53 -05:00
public function __construct (
protected ControllerMethodReflector $reflector ,
protected Throttler $throttler ,
protected IRequest $request ,
protected LoggerInterface $logger ,
) {
2017-04-13 16:50:44 -04:00
}
/**
* { @ inheritDoc }
*/
2017-08-01 11:32:03 -04:00
public function beforeController ( $controller , $methodName ) {
2017-04-13 16:50:44 -04:00
parent :: beforeController ( $controller , $methodName );
2020-04-10 08:19:56 -04:00
if ( $this -> reflector -> hasAnnotation ( 'BruteForceProtection' )) {
2017-04-13 16:50:44 -04:00
$action = $this -> reflector -> getAnnotationParameter ( 'BruteForceProtection' , 'action' );
2020-03-19 07:09:57 -04:00
$this -> throttler -> sleepDelayOrThrowOnMax ( $this -> request -> getRemoteAddress (), $action );
2023-02-28 16:26:22 -05:00
} else {
$reflectionMethod = new ReflectionMethod ( $controller , $methodName );
$attributes = $reflectionMethod -> getAttributes ( BruteForceProtection :: class );
if ( ! empty ( $attributes )) {
$remoteAddress = $this -> request -> getRemoteAddress ();
foreach ( $attributes as $attribute ) {
/** @var BruteForceProtection $protection */
$protection = $attribute -> newInstance ();
$action = $protection -> getAction ();
$this -> throttler -> sleepDelayOrThrowOnMax ( $remoteAddress , $action );
}
}
2017-04-13 16:50:44 -04:00
}
}
/**
* { @ inheritDoc }
*/
2017-08-01 11:32:03 -04:00
public function afterController ( $controller , $methodName , Response $response ) {
2023-02-28 16:26:22 -05:00
if ( $response -> isThrottled ()) {
2023-05-11 03:17:30 -04:00
try {
if ( $this -> reflector -> hasAnnotation ( 'BruteForceProtection' )) {
$action = $this -> reflector -> getAnnotationParameter ( 'BruteForceProtection' , 'action' );
2023-02-28 16:26:22 -05:00
$ip = $this -> request -> getRemoteAddress ();
2023-05-11 03:17:30 -04:00
$this -> throttler -> registerAttempt ( $action , $ip , $response -> getThrottleMetadata ());
$this -> throttler -> sleepDelayOrThrowOnMax ( $ip , $action );
} else {
$reflectionMethod = new ReflectionMethod ( $controller , $methodName );
$attributes = $reflectionMethod -> getAttributes ( BruteForceProtection :: class );
if ( ! empty ( $attributes )) {
$ip = $this -> request -> getRemoteAddress ();
$metaData = $response -> getThrottleMetadata ();
foreach ( $attributes as $attribute ) {
/** @var BruteForceProtection $protection */
$protection = $attribute -> newInstance ();
$action = $protection -> getAction ();
if ( ! isset ( $metaData [ 'action' ]) || $metaData [ 'action' ] === $action ) {
$this -> throttler -> registerAttempt ( $action , $ip , $metaData );
$this -> throttler -> sleepDelayOrThrowOnMax ( $ip , $action );
}
2023-02-28 16:26:22 -05:00
}
2023-05-11 03:17:30 -04:00
} else {
$this -> logger -> debug ( 'Response for ' . get_class ( $controller ) . '::' . $methodName . ' got bruteforce throttled but has no annotation nor attribute defined.' );
2023-02-28 16:26:22 -05:00
}
}
2023-05-11 03:17:30 -04:00
} catch ( MaxDelayReached $e ) {
if ( $controller instanceof OCSController ) {
throw new OCSException ( $e -> getMessage (), Http :: STATUS_TOO_MANY_REQUESTS );
}
return new TooManyRequestsResponse ();
2023-02-28 16:26:22 -05:00
}
2017-04-13 16:50:44 -04:00
}
return parent :: afterController ( $controller , $methodName , $response );
}
2020-03-19 07:09:57 -04:00
/**
* @ param Controller $controller
* @ param string $methodName
* @ param \Exception $exception
* @ throws \Exception
* @ return Response
*/
public function afterException ( $controller , $methodName , \Exception $exception ) : Response {
if ( $exception instanceof MaxDelayReached ) {
if ( $controller instanceof OCSController ) {
throw new OCSException ( $exception -> getMessage (), Http :: STATUS_TOO_MANY_REQUESTS );
}
return new TooManyRequestsResponse ();
}
throw $exception ;
}
2017-04-13 16:50:44 -04:00
}