Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
48 / 48 |
|
100.00% |
5 / 5 |
CRAP | |
100.00% |
1 / 1 |
EmailAuthSecondaryAuthenticationProvider | |
100.00% |
48 / 48 |
|
100.00% |
5 / 5 |
13 | |
100.00% |
1 / 1 |
getAuthenticationRequests | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
beginSecondaryAuthentication | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
2 | |||
continueSecondaryAuthentication | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
6 | |||
beginSecondaryAccountCreation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
runEmailAuthRequireToken | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\EmailAuth; |
4 | |
5 | use MediaWiki\Auth\AbstractSecondaryAuthenticationProvider; |
6 | use MediaWiki\Auth\AuthenticationRequest; |
7 | use MediaWiki\Auth\AuthenticationResponse; |
8 | use MediaWiki\Logger\LoggerFactory; |
9 | use MediaWiki\MediaWikiServices; |
10 | use Message; |
11 | use MWCryptRand; |
12 | use User; |
13 | |
14 | class 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 | } |