2016-05-11 05:23:25 -04:00
< ? php
2021-12-16 07:58:25 -05:00
declare ( strict_types = 1 );
2016-05-11 05:23:25 -04:00
/**
2024-05-27 04:08:53 -04:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2016-05-11 05:23:25 -04:00
*/
namespace OC\Core\Middleware ;
use Exception ;
2025-09-17 09:32:11 -04:00
use OC\AppFramework\Http\Attributes\TwoFactorSetUpDoneRequired ;
2016-05-11 05:23:25 -04:00
use OC\Authentication\Exceptions\TwoFactorAuthRequiredException ;
use OC\Authentication\Exceptions\UserAlreadyLoggedInException ;
use OC\Authentication\TwoFactorAuth\Manager ;
2016-08-24 04:42:07 -04:00
use OC\Core\Controller\LoginController ;
2016-05-11 05:23:25 -04:00
use OC\Core\Controller\TwoFactorChallengeController ;
use OC\User\Session ;
2021-10-04 03:54:58 -04:00
use OCA\TwoFactorNextcloudNotification\Controller\APIController ;
2016-05-11 05:23:25 -04:00
use OCP\AppFramework\Controller ;
2025-09-17 09:32:11 -04:00
use OCP\AppFramework\Http\Attribute\NoTwoFactorRequired ;
2016-05-11 05:23:25 -04:00
use OCP\AppFramework\Http\RedirectResponse ;
use OCP\AppFramework\Middleware ;
use OCP\AppFramework\Utility\IControllerMethodReflector ;
2019-04-05 12:21:08 -04:00
use OCP\Authentication\TwoFactorAuth\ALoginSetupController ;
2016-06-01 07:54:08 -04:00
use OCP\IRequest ;
2016-05-11 05:23:25 -04:00
use OCP\ISession ;
use OCP\IURLGenerator ;
2016-08-24 04:42:07 -04:00
use OCP\IUser ;
2025-09-17 09:32:11 -04:00
use Psr\Log\LoggerInterface ;
use ReflectionMethod ;
2016-05-11 05:23:25 -04:00
class TwoFactorMiddleware extends Middleware {
2023-06-23 15:33:56 -04:00
public function __construct (
private Manager $twoFactorManager ,
private Session $userSession ,
private ISession $session ,
private IURLGenerator $urlGenerator ,
private IControllerMethodReflector $reflector ,
private IRequest $request ,
2025-09-17 09:32:11 -04:00
private LoggerInterface $logger ,
2023-06-23 15:33:56 -04:00
) {
2016-05-11 05:23:25 -04:00
}
/**
* @ param Controller $controller
* @ param string $methodName
*/
2017-08-01 11:32:03 -04:00
public function beforeController ( $controller , $methodName ) {
2025-09-17 09:32:11 -04:00
$reflectionMethod = new ReflectionMethod ( $controller , $methodName );
if ( $this -> hasAnnotationOrAttribute ( $reflectionMethod , 'NoTwoFactorRequired' , NoTwoFactorRequired :: class )) {
2021-11-17 12:42:21 -05:00
// Route handler explicitly marked to work without finished 2FA are
// not blocked
return ;
}
2021-10-04 03:54:58 -04:00
if ( $controller instanceof APIController && $methodName === 'poll' ) {
// Allow polling the twofactor nextcloud notifications state
return ;
}
2019-10-25 08:42:00 -04:00
if ( $controller instanceof TwoFactorChallengeController
&& $this -> userSession -> getUser () !== null
2025-09-17 09:32:11 -04:00
&& ! $reflectionMethod -> getAttributes ( TwoFactorSetUpDoneRequired :: class )) {
2019-10-25 08:42:00 -04:00
$providers = $this -> twoFactorManager -> getProviderSet ( $this -> userSession -> getUser ());
2021-07-30 12:29:23 -04:00
if ( ! ( $providers -> getPrimaryProviders () === [] && ! $providers -> isProviderMissing ())) {
2019-10-25 08:42:00 -04:00
throw new TwoFactorAuthRequiredException ();
}
}
2019-04-05 12:21:08 -04:00
if ( $controller instanceof ALoginSetupController
&& $this -> userSession -> getUser () !== null
&& $this -> twoFactorManager -> needsSecondFactor ( $this -> userSession -> getUser ())) {
2021-07-21 03:58:17 -04:00
$providers = $this -> twoFactorManager -> getProviderSet ( $this -> userSession -> getUser ());
2021-12-10 05:35:36 -05:00
if ( $providers -> getPrimaryProviders () === [] && ! $providers -> isProviderMissing ()) {
2021-07-21 03:58:17 -04:00
return ;
}
2019-04-05 12:21:08 -04:00
}
2016-08-24 04:42:07 -04:00
if ( $controller instanceof LoginController && $methodName === 'logout' ) {
2016-06-07 11:53:00 -04:00
// Don't block the logout page, to allow canceling the 2FA
return ;
}
2016-05-11 05:23:25 -04:00
if ( $this -> userSession -> isLoggedIn ()) {
$user = $this -> userSession -> getUser ();
2023-12-25 10:12:54 -05:00
if ( $this -> session -> exists ( 'app_password' ) // authenticated using an app password
|| $this -> session -> exists ( 'app_api' ) // authenticated using an AppAPI Auth
|| $this -> twoFactorManager -> isTwoFactorAuthenticated ( $user )) {
2025-09-17 09:32:11 -04:00
$this -> checkTwoFactor ( $controller , $user );
2020-04-10 04:35:09 -04:00
} elseif ( $controller instanceof TwoFactorChallengeController ) {
2016-05-17 09:48:41 -04:00
// Allow access to the two-factor controllers only if two-factor authentication
// is in progress.
throw new UserAlreadyLoggedInException ();
2016-05-11 05:23:25 -04:00
}
}
// TODO: dont check/enforce 2FA if a auth token is used
}
2025-09-17 09:32:11 -04:00
private function checkTwoFactor ( Controller $controller , IUser $user ) {
2016-05-11 05:23:25 -04:00
// If two-factor auth is in progress disallow access to any controllers
// defined within "LoginController".
2016-08-24 04:42:07 -04:00
$needsSecondFactor = $this -> twoFactorManager -> needsSecondFactor ( $user );
2016-05-11 05:23:25 -04:00
$twoFactor = $controller instanceof TwoFactorChallengeController ;
// Disallow access to any controller if 2FA needs to be checked
if ( $needsSecondFactor && ! $twoFactor ) {
throw new TwoFactorAuthRequiredException ();
}
// Allow access to the two-factor controllers only if two-factor authentication
// is in progress.
if ( ! $needsSecondFactor && $twoFactor ) {
throw new UserAlreadyLoggedInException ();
}
}
2017-08-01 11:32:03 -04:00
public function afterException ( $controller , $methodName , Exception $exception ) {
2016-05-11 05:23:25 -04:00
if ( $exception instanceof TwoFactorAuthRequiredException ) {
2024-04-09 02:12:21 -04:00
$params = [
'redirect_url' => $this -> request -> getParam ( 'redirect_url' ),
];
if ( ! isset ( $params [ 'redirect_url' ]) && isset ( $this -> request -> server [ 'REQUEST_URI' ])) {
2017-05-15 08:33:27 -04:00
$params [ 'redirect_url' ] = $this -> request -> server [ 'REQUEST_URI' ];
}
return new RedirectResponse ( $this -> urlGenerator -> linkToRoute ( 'core.TwoFactorChallenge.selectChallenge' , $params ));
2016-05-11 05:23:25 -04:00
}
if ( $exception instanceof UserAlreadyLoggedInException ) {
return new RedirectResponse ( $this -> urlGenerator -> linkToRoute ( 'files.view.index' ));
}
2016-08-12 06:33:31 -04:00
throw $exception ;
2016-05-11 05:23:25 -04:00
}
2025-09-17 09:32:11 -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 ( $annotationName && $this -> reflector -> hasAnnotation ( $annotationName )) {
$this -> logger -> debug ( $reflectionMethod -> getDeclaringClass () -> getName () . '::' . $reflectionMethod -> getName () . ' uses the @' . $annotationName . ' annotation and should use the #[' . $attributeClass . '] attribute instead' );
return true ;
}
return false ;
}
2016-05-11 05:23:25 -04:00
}