2018-01-02 15:13:32 -05:00
< ? php
/**
2024-05-23 03:26:56 -04:00
* SPDX - FileCopyrightText : 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX - License - Identifier : AGPL - 3.0 - or - later
2018-01-02 15:13:32 -05:00
*/
namespace OC\AppFramework\Middleware\Security ;
use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException ;
use OC\AppFramework\Utility\ControllerMethodReflector ;
2024-03-01 12:37:47 -05:00
use OC\Authentication\Token\IProvider ;
2018-01-02 15:13:32 -05:00
use OCP\AppFramework\Controller ;
2023-04-24 11:13:18 -04:00
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired ;
2018-01-02 15:13:32 -05:00
use OCP\AppFramework\Middleware ;
use OCP\AppFramework\Utility\ITimeFactory ;
2024-03-01 12:37:47 -05:00
use OCP\Authentication\Exceptions\ExpiredTokenException ;
use OCP\Authentication\Exceptions\InvalidTokenException ;
use OCP\Authentication\Exceptions\WipeTokenException ;
2024-03-15 07:51:31 -04:00
use OCP\Authentication\Token\IToken ;
2018-01-02 15:13:32 -05:00
use OCP\ISession ;
use OCP\IUserSession ;
2024-03-01 12:37:47 -05:00
use OCP\Session\Exceptions\SessionNotAvailableException ;
2018-10-11 15:56:24 -04:00
use OCP\User\Backend\IPasswordConfirmationBackend ;
2024-07-15 09:25:45 -04:00
use Psr\Log\LoggerInterface ;
2023-04-24 11:13:18 -04:00
use ReflectionMethod ;
2018-01-02 15:13:32 -05:00
class PasswordConfirmationMiddleware extends Middleware {
/** @var ControllerMethodReflector */
private $reflector ;
/** @var ISession */
private $session ;
/** @var IUserSession */
private $userSession ;
/** @var ITimeFactory */
private $timeFactory ;
2018-10-27 09:43:51 -04:00
/** @var array */
private $excludedUserBackEnds = [ 'user_saml' => true , 'user_globalsiteselector' => true ];
2024-03-01 12:37:47 -05:00
private IProvider $tokenProvider ;
2018-01-02 15:13:32 -05:00
/**
* PasswordConfirmationMiddleware constructor .
*
* @ param ControllerMethodReflector $reflector
* @ param ISession $session
* @ param IUserSession $userSession
* @ param ITimeFactory $timeFactory
*/
public function __construct ( ControllerMethodReflector $reflector ,
ISession $session ,
IUserSession $userSession ,
2024-03-01 12:37:47 -05:00
ITimeFactory $timeFactory ,
IProvider $tokenProvider ,
2024-07-15 09:25:45 -04:00
private readonly LoggerInterface $logger ,
2024-03-01 12:37:47 -05:00
) {
2018-01-02 15:13:32 -05:00
$this -> reflector = $reflector ;
$this -> session = $session ;
$this -> userSession = $userSession ;
$this -> timeFactory = $timeFactory ;
2024-03-01 12:37:47 -05:00
$this -> tokenProvider = $tokenProvider ;
2018-01-02 15:13:32 -05:00
}
/**
* @ param Controller $controller
* @ param string $methodName
* @ throws NotConfirmedException
*/
public function beforeController ( $controller , $methodName ) {
2023-04-24 11:13:18 -04:00
$reflectionMethod = new ReflectionMethod ( $controller , $methodName );
if ( $this -> hasAnnotationOrAttribute ( $reflectionMethod , 'PasswordConfirmationRequired' , PasswordConfirmationRequired :: class )) {
2018-01-02 15:13:32 -05:00
$user = $this -> userSession -> getUser ();
$backendClassName = '' ;
if ( $user !== null ) {
2018-10-11 15:56:24 -04:00
$backend = $user -> getBackend ();
if ( $backend instanceof IPasswordConfirmationBackend ) {
if ( ! $backend -> canConfirmPassword ( $user -> getUID ())) {
return ;
}
}
2018-01-02 15:13:32 -05:00
$backendClassName = $user -> getBackendClassName ();
}
2024-03-01 12:37:47 -05:00
try {
$sessionId = $this -> session -> getId ();
$token = $this -> tokenProvider -> getToken ( $sessionId );
} catch ( SessionNotAvailableException | InvalidTokenException | WipeTokenException | ExpiredTokenException ) {
// States we do not deal with here.
return ;
}
$scope = $token -> getScopeAsArray ();
2024-03-15 07:51:31 -04:00
if ( isset ( $scope [ IToken :: SCOPE_SKIP_PASSWORD_VALIDATION ]) && $scope [ IToken :: SCOPE_SKIP_PASSWORD_VALIDATION ] === true ) {
2024-03-01 12:37:47 -05:00
// Users logging in from SSO backends cannot confirm their password by design
return ;
}
2018-01-02 15:13:32 -05:00
$lastConfirm = ( int ) $this -> session -> get ( 'last-password-confirm' );
2024-03-01 12:37:47 -05:00
// TODO: confirm excludedUserBackEnds can go away and remove it
2018-10-27 09:43:51 -04:00
if ( ! isset ( $this -> excludedUserBackEnds [ $backendClassName ]) && $lastConfirm < ( $this -> timeFactory -> getTime () - ( 30 * 60 + 15 ))) { // allow 15 seconds delay
2018-01-02 15:13:32 -05:00
throw new NotConfirmedException ();
}
}
}
2023-04-24 11:13:18 -04:00
/**
* @ template T
*
* @ param ReflectionMethod $reflectionMethod
* @ param string $annotationName
* @ param class - string < T > $attributeClass
* @ return boolean
*/
protected function hasAnnotationOrAttribute ( ReflectionMethod $reflectionMethod , string $annotationName , string $attributeClass ) : bool {
if ( ! empty ( $reflectionMethod -> getAttributes ( $attributeClass ))) {
return true ;
}
if ( $this -> reflector -> hasAnnotation ( $annotationName )) {
2024-07-15 09:25:45 -04:00
$this -> logger -> debug ( $reflectionMethod -> getDeclaringClass () -> getName () . '::' . $reflectionMethod -> getName () . ' uses the @' . $annotationName . ' annotation and should use the #[' . $attributeClass . '] attribute instead' );
2023-04-24 11:13:18 -04:00
return true ;
}
return false ;
}
2018-01-02 15:13:32 -05:00
}