Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
TOTPSecondaryAuthenticationProvider
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 5
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAuthenticationRequests
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 beginSecondaryAuthentication
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 continueSecondaryAuthentication
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
30
 beginSecondaryAccountCreation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types=1 );
3/**
4 * @license GPL-2.0-or-later
5 */
6
7namespace MediaWiki\Extension\OATHAuth\Auth;
8
9use MediaWiki\Auth\AbstractSecondaryAuthenticationProvider;
10use MediaWiki\Auth\AuthenticationRequest;
11use MediaWiki\Auth\AuthenticationResponse;
12use MediaWiki\Auth\AuthManager;
13use MediaWiki\Extension\OATHAuth\Module\TOTP;
14use MediaWiki\Extension\OATHAuth\OATHAuthLogger;
15use MediaWiki\Extension\OATHAuth\OATHUserRepository;
16use MediaWiki\Message\Message;
17
18/**
19 * AuthManager secondary authentication provider for TOTP second-factor authentication.
20 *
21 * After a successful primary authentication, requests a time-based one-time password
22 * (typically generated by a mobile app such as Google Authenticator) from the user.
23 *
24 * @see AuthManager
25 * @see https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm
26 */
27class TOTPSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
28    public function __construct(
29        private readonly TOTP $module,
30        private readonly OATHUserRepository $userRepository,
31        private readonly OATHAuthLogger $oathLogger,
32    ) {
33    }
34
35    /** @inheritDoc */
36    public function getAuthenticationRequests( $action, array $options ) {
37        // don't ask for anything initially, so the second factor is on a separate screen
38        return [];
39    }
40
41    /**
42     * If the user has enabled two-factor authentication, request a second factor.
43     *
44     * @inheritDoc
45     */
46    public function beginSecondaryAuthentication( $user, array $reqs ) {
47        $authUser = $this->userRepository->findByUser( $user );
48
49        if ( !$this->module->isEnabled( $authUser ) ) {
50            return AuthenticationResponse::newAbstain();
51        }
52
53        return AuthenticationResponse::newUI( [ new TOTPAuthenticationRequest() ] );
54    }
55
56    /** @inheritDoc */
57    public function continueSecondaryAuthentication( $user, array $reqs ) {
58        /** @var TOTPAuthenticationRequest $request */
59        $request = AuthenticationRequest::getRequestByClass( $reqs, TOTPAuthenticationRequest::class );
60        if ( !$request ) {
61            return AuthenticationResponse::newUI( [ new TOTPAuthenticationRequest() ],
62                wfMessage( 'oathauth-login-failed' ), 'error' );
63        }
64
65        // Check for (and increment) rate limiter before doing the auth
66        if ( $user->pingLimiter( 'badoath' ) || $user->pingLimiter( 'badoath-long' ) ) {
67            return AuthenticationResponse::newUI(
68                [ new TOTPAuthenticationRequest() ],
69                new Message( 'oathauth-throttled' ),
70                'error'
71            );
72        }
73
74        $authUser = $this->userRepository->findByUser( $user );
75        $token = $request->OATHToken;
76
77        if ( $this->module->verify( $authUser, [
78            'token' => $token,
79            'disableRecoveryCodeFallback' => ReauthPrimaryAuthenticationProvider::isRestrictedReauth( $this->manager ),
80        ] ) ) {
81            return AuthenticationResponse::newPass();
82        }
83
84        $this->logger->info( 'OATHAuth user {user} failed OTP token/recovery code from {clientip}', [
85            'user'     => $user->getName(),
86            'clientip' => $user->getRequest()->getIP(),
87        ] );
88
89        $this->oathLogger->logFailedVerification( $user );
90
91        return AuthenticationResponse::newUI(
92            [ new TOTPAuthenticationRequest() ],
93            wfMessage( 'oathauth-login-failed' ),
94            'error'
95        );
96    }
97
98    /** @inheritDoc */
99    public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
100        return AuthenticationResponse::newAbstain();
101    }
102}