Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebAuthnSecondaryAuthenticationProvider
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 6
210
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 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 continueSecondaryAuthentication
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
42
 addModules
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 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\Context\RequestContext;
13use MediaWiki\Extension\OATHAuth\OATHUserRepository;
14use MediaWiki\Extension\OATHAuth\WebAuthnAuthenticator;
15
16class WebAuthnSecondaryAuthenticationProvider extends AbstractSecondaryAuthenticationProvider {
17
18    public function __construct(
19        private readonly OATHUserRepository $userRepository,
20        private readonly WebAuthnAuthenticator $authenticator,
21    ) {
22    }
23
24    /** @inheritDoc */
25    public function getAuthenticationRequests( $action, array $options ) {
26        return [];
27    }
28
29    /** @inheritDoc */
30    public function beginSecondaryAuthentication( $user, array $reqs ) {
31        $oathUser = $this->userRepository->findByUser( $user );
32        if ( !$this->authenticator->isEnabled( $oathUser ) ) {
33            return AuthenticationResponse::newAbstain();
34        }
35        $canAuthenticate = $this->authenticator->canAuthenticate( $oathUser );
36        if ( !$canAuthenticate->isGood() ) {
37            return AuthenticationResponse::newFail( $canAuthenticate->getMessage() );
38        }
39        $startAuthResult = $this->authenticator->startAuthentication( $oathUser );
40        if ( $startAuthResult->isGood() ) {
41            $request = new WebAuthnAuthenticationRequest( $startAuthResult->getValue()['json'] );
42            $this->addModules();
43            return AuthenticationResponse::newUI( [ $request ] );
44        }
45        return AuthenticationResponse::newFail( $startAuthResult->getMessage() );
46    }
47
48    /** @inheritDoc */
49    public function continueSecondaryAuthentication( $user, array $reqs ) {
50        $oathUser = $this->userRepository->findByUser( $user );
51        $canAuthenticate = $this->authenticator->canAuthenticate( $oathUser );
52        if ( !$canAuthenticate->isGood() ) {
53            return AuthenticationResponse::newFail( $canAuthenticate->getMessage() );
54        }
55
56        /** @var WebAuthnAuthenticationRequest $request */
57        $request = AuthenticationRequest::getRequestByClass(
58            $reqs,
59            WebAuthnAuthenticationRequest::class
60        );
61        if ( !$request ) {
62            // Re-ask user for credentials
63            $request = new WebAuthnAuthenticationRequest(
64                $this->authenticator->startAuthentication( $oathUser )->getValue()['json']
65            );
66            $this->addModules();
67            return AuthenticationResponse::newUI( [ $request ],
68                wfMessage( 'oathauth-webauthn-error-credentials-missing' ), 'error' );
69        }
70
71        // Get credential retrieved from the client
72        $verificationData = $request->getSubmittedData();
73        if ( $verificationData['credential'] === '' ) {
74            return AuthenticationResponse::newUI( [ $request ],
75                wfMessage( 'oathauth-webauthn-error-credentials-missing' ), 'error' );
76        }
77
78        $authResult = $this->authenticator->continueAuthentication( $oathUser, $verificationData['credential'] );
79        if ( $authResult->isGood() ) {
80            return AuthenticationResponse::newPass( $authResult->getValue()->getUser()->getName() );
81        }
82
83        $messages = $authResult->getMessages();
84
85        if ( $messages === [] ) {
86            return AuthenticationResponse::newFail( wfMessage( 'oathauth-webauthn-error-verification-failed' ) );
87        }
88
89        // Return the first error from the authenticator, if there is any
90        return AuthenticationResponse::newFail( wfMessage( $messages[0] ) );
91    }
92
93    private function addModules() {
94        // It would be better to add modules in HTMLFormField class,
95        // but that does not seem to work for the login form
96        $out = RequestContext::getMain()->getOutput();
97        $out->addModules( "ext.webauthn.login" );
98    }
99
100    /** @inheritDoc */
101    public function beginSecondaryAccountCreation( $user, $creator, array $reqs ) {
102        return AuthenticationResponse::newAbstain();
103    }
104}