Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
TOTPSecondaryAuthenticationProvider
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 4
90
0.00% covered (danger)
0.00%
0 / 1
 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 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 continueSecondaryAuthentication
0.00% covered (danger)
0.00%
0 / 31
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
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 */
18
19namespace MediaWiki\Extension\OATHAuth\Auth;
20
21use MediaWiki\Auth\AbstractSecondaryAuthenticationProvider;
22use MediaWiki\Auth\AuthenticationRequest;
23use MediaWiki\Auth\AuthenticationResponse;
24use MediaWiki\Auth\AuthManager;
25use MediaWiki\Extension\OATHAuth\Module\TOTP;
26use MediaWiki\MediaWikiServices;
27use MediaWiki\User\User;
28use Message;
29
30/**
31 * AuthManager secondary authentication provider for TOTP second-factor authentication.
32 *
33 * After a successful primary authentication, requests a time-based one-time password
34 * (typically generated by a mobile app such as Google Authenticator) from the user.
35 *
36 * @see AuthManager
37 * @see https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm
38 */
39class TOTPSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
40
41    /**
42     * @param string $action
43     * @param array $options
44     *
45     * @return array
46     */
47    public function getAuthenticationRequests( $action, array $options ) {
48        // don't ask for anything initially, so the second factor is on a separate screen
49        return [];
50    }
51
52    /**
53     * If the user has enabled two-factor authentication, request a second factor.
54     *
55     * @param User $user
56     * @param array $reqs
57     *
58     * @return AuthenticationResponse
59     */
60    public function beginSecondaryAuthentication( $user, array $reqs ) {
61        $userRepo = MediaWikiServices::getInstance()->getService( 'OATHUserRepository' );
62        $authUser = $userRepo->findByUser( $user );
63
64        if ( !( $authUser->getModule() instanceof TOTP ) ) {
65            return AuthenticationResponse::newAbstain();
66        }
67
68        return AuthenticationResponse::newUI(
69            [ new TOTPAuthenticationRequest() ],
70            wfMessage( 'oathauth-auth-ui' ),
71        );
72    }
73
74    /**
75     * Verify the second factor.
76     * @inheritDoc
77     */
78    public function continueSecondaryAuthentication( $user, array $reqs ) {
79        /** @var TOTPAuthenticationRequest $request */
80        $request = AuthenticationRequest::getRequestByClass( $reqs, TOTPAuthenticationRequest::class );
81        if ( !$request ) {
82            return AuthenticationResponse::newUI( [ new TOTPAuthenticationRequest() ],
83                wfMessage( 'oathauth-login-failed' ), 'error' );
84        }
85
86        $userRepo = MediaWikiServices::getInstance()->getService( 'OATHUserRepository' );
87        $authUser = $userRepo->findByUser( $user );
88        $token = $request->OATHToken;
89
90        if ( !( $authUser->getModule() instanceof TOTP ) ) {
91            $this->logger->warning( 'Two-factor authentication was disabled mid-authentication for {user}', [
92                'user' => $user->getName(),
93            ] );
94            return AuthenticationResponse::newAbstain();
95        }
96
97        // Don't increase pingLimiter, just check for limit exceeded.
98        if ( $user->pingLimiter( 'badoath', 0 ) ) {
99            return AuthenticationResponse::newUI(
100                [ new TOTPAuthenticationRequest() ],
101                new Message(
102                    'oathauth-throttled',
103                    // Arbitrary duration given here
104                    [ Message::durationParam( 60 ) ]
105                ), 'error' );
106        }
107
108        if ( $authUser->getModule()->verify( $authUser, [ 'token' => $token ] ) ) {
109            return AuthenticationResponse::newPass();
110        }
111
112        // Increase rate limit counter for failed request
113        $user->pingLimiter( 'badoath' );
114
115        $this->logger->info( 'OATHAuth user {user} failed OTP token/recovery code from {clientip}', [
116            'user'     => $user->getName(),
117            'clientip' => $user->getRequest()->getIP(),
118        ] );
119
120        return AuthenticationResponse::newUI(
121            [ new TOTPAuthenticationRequest() ],
122            wfMessage( 'oathauth-login-failed' ),
123            'error'
124        );
125    }
126
127    /**
128     * @param User $user
129     * @param User $creator
130     * @param array $reqs
131     *
132     * @return AuthenticationResponse
133     */
134    public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
135        return AuthenticationResponse::newAbstain();
136    }
137}