Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebAuthnAuthenticationRequest
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 5
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 describeCredentials
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getFieldInfo
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
30
 loadFromSubmission
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getSubmittedData
0.00% covered (danger)
0.00%
0 / 3
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\AuthenticationRequest;
10use MediaWiki\Language\RawMessage;
11
12class WebAuthnAuthenticationRequest extends AuthenticationRequest {
13
14    public string $credential;
15
16    /**
17     * @param string $authInfo Serialized JSON blob obtained from
18     *   WebAuthnAuthenticator::startAuthentication()
19     * @param array{isReauth?:bool, showPrompt?:bool, showButton?:'passwordless'|'interstitial'|false} $options
20     *   - isReauth: Whether this request is for a reauthentication (default: false)
21     *   - showPrompt: Whether to display the prompt telling the user to use their security key (default: true)
22     *   - showButton: Whether to show a button that activates the WebAuthn authentication.
23     *       - 'passwordless': Display a "Log in with passkey" or "Continue with passkey" button
24     *       - 'interstitial': Display a "Continue with security key" button
25     *       - false: Don't display a button, and activate the WebAuthn authentication immediately (default)
26     */
27    public function __construct(
28        public string $authInfo,
29        public array $options = []
30    ) {
31        $this->options += [
32            'isReauth' => false,
33            'showPrompt' => true,
34            'showButton' => false
35        ];
36    }
37
38    /** @inheritDoc */
39    public function describeCredentials() {
40        return [
41            'provider' => wfMessage( 'oathauth-describe-provider' ),
42            'account' => new RawMessage( '$1', [ $this->username ] ),
43        ] + parent::describeCredentials();
44    }
45
46    /** @inheritDoc */
47    public function getFieldInfo() {
48        $fields = [
49            // The hidden auth_info field only exists to send the authInfo JSON blob to the client.
50            // It's not used for authentication and ignored when submitted back to us, we get the
51            // authInfo blob from the session instead.
52            'auth_info' => [
53                'type' => 'hidden',
54                'value' => $this->authInfo,
55                'label' => wfMessage( 'oathauth-webauthn-authentication-info-label' ),
56                'help' => wfMessage( 'oathauth-webauthn-authentication-info-help' ),
57            ],
58            'credential' => [
59                'type' => 'hidden',
60                'value' => '',
61                'label' => wfMessage( 'oathauth-webauthn-credential-label' ),
62                'help' => wfMessage( 'oathauth-webauthn-credential-help' ),
63            ]
64        ];
65
66        if ( $this->options['showPrompt'] ) {
67            $fields['webauthnLabel'] = [
68                'type' => 'null',
69                'value' => wfMessage( 'oathauth-webauthn-ui-login-prompt' ),
70                // TODO: Use a different message for help?
71                'help' => wfMessage( 'oathauth-webauthn-ui-login-prompt' ),
72            ];
73        }
74
75        if ( $this->options['showButton'] === 'passwordless' ) {
76            $fields['passwordlessButton'] = [
77                'type' => 'button',
78                'label' => $this->options['isReauth'] ?
79                    wfMessage( 'oathauth-webauthn-reauth-passkey-button' ) :
80                    wfMessage( 'oathauth-webauthn-login-passkey-button' ),
81            ];
82        } elseif ( $this->options['showButton'] === 'interstitial' ) {
83            $fields['webauthnButton'] = [
84                'type' => 'button',
85                'label' => wfMessage( 'oathauth-webauthn-reauth-security-key-button' ),
86            ];
87        }
88
89        return $fields;
90    }
91
92    /** @inheritDoc */
93    public function loadFromSubmission( array $data ) {
94        if ( !isset( $data['credential'] ) ) {
95            return false;
96        }
97        $this->credential = $data['credential'];
98
99        return true;
100    }
101
102    public function getSubmittedData(): array {
103        // Don't trust the submitted auth_info, otherwise the user could control which challenge
104        // we're validating against and do a replay attack. Instead, we use the authInfo blob
105        // in the session, which we stored there when we issued the challenge.
106        return [
107            'credential' => $this->credential
108        ];
109    }
110}