Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
EmailAuthSecondaryAuthenticationProvider
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
5 / 5
13
100.00% covered (success)
100.00%
1 / 1
 getAuthenticationRequests
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beginSecondaryAuthentication
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 continueSecondaryAuthentication
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
 beginSecondaryAccountCreation
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 runEmailAuthRequireToken
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace MediaWiki\Extension\EmailAuth;
4
5use MediaWiki\Auth\AbstractSecondaryAuthenticationProvider;
6use MediaWiki\Auth\AuthenticationRequest;
7use MediaWiki\Auth\AuthenticationResponse;
8use MediaWiki\Logger\LoggerFactory;
9use MediaWiki\MediaWikiServices;
10use Message;
11use MWCryptRand;
12use User;
13
14class EmailAuthSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
15    /** Fail the login attempt after this many retries */
16    const RETRIES = 3;
17
18    public function getAuthenticationRequests( $action, array $options ) {
19        return [];
20    }
21
22    public function beginSecondaryAuthentication( $user, array $reqs ) {
23        $token = MWCryptRand::generateHex( 6 );
24        $messages = $this->runEmailAuthRequireToken( $user, $token );
25        if ( !$messages ) {
26            return AuthenticationResponse::newPass();
27        }
28        /** @var Message $formMessage */
29        /** @var Message $subjectMessage */
30        /** @var Message $bodyMessage */
31        [ $formMessage, $subjectMessage, $bodyMessage ] = $messages;
32
33        LoggerFactory::getInstance( 'EmailAuth' )->info( 'Verification requested for {user}', [
34            'user' => $user->getName(),
35            'ip' => $user->getRequest()->getIP(),
36            'formMessageKey' => $formMessage->getKey(),
37            'subjectMessageKey' => $subjectMessage->getKey(),
38            'bodyMessageKey' => $bodyMessage->getKey(),
39        ] );
40
41        $this->manager->setAuthenticationSessionData( 'EmailAuthToken', $token );
42        $this->manager->setAuthenticationSessionData( 'EmailAuthFailures', 0 );
43        $user->sendMail( $subjectMessage, $bodyMessage );
44        return AuthenticationResponse::newUI( [ new EmailAuthAuthenticationRequest() ], $formMessage );
45    }
46
47    public function continueSecondaryAuthentication( $user, array $reqs ) {
48        $token = $this->manager->getAuthenticationSessionData( 'EmailAuthToken' );
49        /** @var EmailAuthAuthenticationRequest $req */
50        $req = AuthenticationRequest::getRequestByClass( $reqs, EmailAuthAuthenticationRequest::class );
51        if ( $req && hash_equals( $token, $req->token ) ) {
52            LoggerFactory::getInstance( 'EmailAuth' )->info( 'Successful verification for {user}', [
53                'user' => $user->getName(),
54                'ip' => $user->getRequest()->getIP(),
55            ] );
56            return AuthenticationResponse::newPass();
57        } elseif ( $req && $req->token ) {
58            // do not log if the code is simply missing - accidental enter or confused bot
59            LoggerFactory::getInstance( 'EmailAuth' )->info( 'Failed verification for {user}', [
60                'user' => $user->getName(),
61                'ip' => $user->getRequest()->getIP(),
62            ] );
63        }
64
65        $failures = $this->manager->getAuthenticationSessionData( 'EmailAuthFailures' );
66        if ( $failures >= self::RETRIES ) {
67                return AuthenticationResponse::newFail( wfMessage( 'emailauth-login-retry-limit' ) );
68        }
69        $this->manager->setAuthenticationSessionData( 'EmailAuthFailures', $failures + 1 );
70        return AuthenticationResponse::newUI( [ new EmailAuthAuthenticationRequest() ],
71            wfMessage( 'emailauth-login-failure' ), 'error' );
72    }
73
74    public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
75        return AuthenticationResponse::newAbstain();
76    }
77
78    /**
79     * @param User $user
80     * @param string $token
81     * @return Message[]|bool [ form message, email subject, email body ] or false if no
82     *   verification should happen
83     */
84    protected function runEmailAuthRequireToken( User $user, $token ) {
85        global $wgSitename;
86
87        if ( !$user->isEmailConfirmed() ) {
88            // nothing we can do
89            return false;
90        }
91
92        $verificationRequired = false;
93        $formMessage = wfMessage( 'emailauth-login-message', $user->getEmail() );
94        $subjectMessage = wfMessage( 'emailauth-email-subject', $wgSitename );
95        $bodyMessage = wfMessage( 'emailauth-email-body', $wgSitename );
96
97        MediaWikiServices::getInstance()->getHookContainer()->run( 'EmailAuthRequireToken',
98            [ $user, &$verificationRequired, &$formMessage, &$subjectMessage, &$bodyMessage ] );
99        $bodyMessage->params( $token );
100
101        return $verificationRequired ? [ $formMessage, $subjectMessage, $bodyMessage ] :
102            false;
103    }
104}