Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebAuthnCredentialRepository
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 8
342
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
 getWebAuthnKeys
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getFriendlyNames
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 findOneByCredentialId
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 findAllForUserEntity
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 saveCredentialSource
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 credentialSourceFromKey
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 updateCounterFor
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Extension\WebAuthn;
4
5use Base64Url\Base64Url;
6use Generator;
7use MediaWiki\Config\ConfigException;
8use MediaWiki\Extension\OATHAuth\OATHAuthServices;
9use MediaWiki\Extension\OATHAuth\OATHUser;
10use MediaWiki\Extension\WebAuthn\Key\WebAuthnKey;
11use Webauthn\PublicKeyCredentialSource;
12use Webauthn\PublicKeyCredentialSourceRepository;
13use Webauthn\PublicKeyCredentialUserEntity;
14
15class WebAuthnCredentialRepository implements PublicKeyCredentialSourceRepository {
16    private OATHUser $oauthUser;
17
18    public function __construct( OATHUser $user ) {
19        $this->oauthUser = $user;
20    }
21
22    /**
23     * @return Generator<WebAuthnKey>
24     */
25    private function getWebAuthnKeys(): Generator {
26        foreach ( $this->oauthUser->getKeys() as $key ) {
27            if ( !$key instanceof WebAuthnKey ) {
28                continue;
29            }
30
31            yield $key;
32        }
33    }
34
35    /**
36     * @param bool $lc Whether to return the names in lowercase form
37     * @return array
38     */
39    public function getFriendlyNames( $lc = false ) {
40        $friendlyNames = [];
41        foreach ( $this->getWebAuthnKeys() as $key ) {
42            $friendlyName = $key->getFriendlyName();
43            if ( $lc ) {
44                $friendlyName = strtolower( $friendlyName );
45            }
46            $friendlyNames[] = $friendlyName;
47        }
48        return $friendlyNames;
49    }
50
51    public function findOneByCredentialId(
52        string $publicKeyCredentialId
53    ): ?PublicKeyCredentialSource {
54        foreach ( $this->getWebAuthnKeys() as $key ) {
55            if ( $key->getAttestedCredentialData()->getCredentialId() === $publicKeyCredentialId ) {
56                return $this->credentialSourceFromKey( $key );
57            }
58        }
59
60        return null;
61    }
62
63    /**
64     * @param PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity
65     * @return PublicKeyCredentialSource[]
66     */
67    public function findAllForUserEntity(
68        PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity
69    ): array {
70        $res = [];
71        foreach ( $this->getWebAuthnKeys() as $key ) {
72            if ( $key->getUserHandle() === $publicKeyCredentialUserEntity->getId() ) {
73                $res[] = $this->credentialSourceFromKey( $key );
74            }
75        }
76
77        return $res;
78    }
79
80    /**
81     * @param PublicKeyCredentialSource $publicKeyCredentialSource
82     * @throws ConfigException
83     */
84    public function saveCredentialSource(
85        PublicKeyCredentialSource $publicKeyCredentialSource
86    ): void {
87        $this->updateCounterFor(
88            $publicKeyCredentialSource->getPublicKeyCredentialId(),
89            $publicKeyCredentialSource->getCounter()
90        );
91    }
92
93    /**
94     * @param WebAuthnKey $key
95     * @return PublicKeyCredentialSource
96     */
97    private function credentialSourceFromKey( WebAuthnKey $key ) {
98        return PublicKeyCredentialSource::createFromArray( [
99            'userHandle' => Base64Url::encode( $key->getUserHandle() ),
100            'aaguid' => $key->getAttestedCredentialData()->getAaguid()->toString(),
101            'friendlyName' => $key->getFriendlyName(),
102            'publicKeyCredentialId' => Base64Url::encode(
103                $key->getAttestedCredentialData()->getCredentialId()
104            ),
105            'credentialPublicKey' => Base64Url::encode(
106                (string)$key->getAttestedCredentialData()->getCredentialPublicKey()
107            ),
108            'counter' => $key->getSignCounter(),
109            'userMWId' => $this->oauthUser->getUser()->getId(),
110            'type' => $key->getType(),
111            'transports' => $key->getTransports(),
112            'attestationType' => $key->getAttestationType(),
113            'trustPath' => $key->getTrustPath()->jsonSerialize()
114        ] );
115    }
116
117    /**
118     * Set a new sign counter-value for the credential
119     *
120     * @param string $credentialId
121     * @param int $newCounter
122     */
123    private function updateCounterFor( string $credentialId, int $newCounter ): void {
124        foreach ( $this->getWebAuthnKeys() as $key ) {
125            if ( $key->getAttestedCredentialData()->getCredentialId() !== $credentialId ) {
126                continue;
127            }
128
129            $key->setSignCounter( $newCounter );
130
131            OATHAuthServices::getInstance()
132                ->getUserRepository()
133                ->updateKey(
134                    $this->oauthUser,
135                    $key
136                );
137        }
138    }
139}