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