Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
55.32% covered (warning)
55.32%
26 / 47
42.86% covered (danger)
42.86%
6 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ClientEntity
55.32% covered (warning)
55.32%
26 / 47
42.86% covered (danger)
42.86%
6 / 14
65.17
0.00% covered (danger)
0.00%
0 / 1
 getRedirectUri
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isConfidential
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIdentifier
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIdentifier
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedGrants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getScopes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getOAuthVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSecretValid
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 isGrantAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 authorize
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getOwnerOnlyAccessToken
73.91% covered (warning)
73.91%
17 / 23
0.00% covered (danger)
0.00%
0 / 1
5.44
 getVerifiedScopes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\OAuth\Entity;
4
5use DateInterval;
6use DateTimeImmutable;
7use Exception;
8use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
9use League\OAuth2\Server\Entities\ScopeEntityInterface;
10use League\OAuth2\Server\Exception\OAuthServerException;
11use MediaWiki\Extension\OAuth\Backend\Consumer;
12use MediaWiki\Extension\OAuth\Backend\ConsumerAcceptance;
13use MediaWiki\Extension\OAuth\Backend\MWOAuthException;
14use MediaWiki\Extension\OAuth\Backend\Utils;
15use MediaWiki\Extension\OAuth\Repository\AccessTokenRepository;
16use MediaWiki\Extension\OAuth\Repository\ClaimStore;
17use MediaWiki\User\User;
18
19class ClientEntity extends Consumer implements MWClientEntityInterface {
20
21    /**
22     * Returns the registered redirect URI (as a string).
23     *
24     * Alternatively return an indexed array of redirect URIs.
25     *
26     * @return string|string[]
27     */
28    public function getRedirectUri() {
29        return $this->getCallbackUrl();
30    }
31
32    /**
33     * Returns true if the client is confidential.
34     *
35     * @return bool
36     */
37    public function isConfidential() {
38        return $this->oauth2IsConfidential;
39    }
40
41    /**
42     * @return mixed
43     */
44    public function getIdentifier() {
45        return $this->getConsumerKey();
46    }
47
48    /**
49     * @param mixed $identifier
50     */
51    public function setIdentifier( $identifier ) {
52        $this->consumerKey = $identifier;
53    }
54
55    /**
56     * Get the grant types this client is allowed to use
57     *
58     * @return string[]
59     */
60    public function getAllowedGrants() {
61        return $this->oauth2GrantTypes;
62    }
63
64    /**
65     * Convenience function, same as getGrants()
66     * it just returns array of ScopeEntity-es instead of strings
67     *
68     * @return ScopeEntityInterface[]
69     */
70    public function getScopes() {
71        $scopeEntities = [];
72        foreach ( $this->getGrants() as $grant ) {
73            $scopeEntities[] = new ScopeEntity( $grant );
74        }
75
76        return $scopeEntities;
77    }
78
79    /**
80     * @return bool|User
81     */
82    public function getUser() {
83        return Utils::getLocalUserFromCentralId( $this->getUserId() );
84    }
85
86    /**
87     * @param null|string $secret
88     * @param null|string $grantType
89     * @return bool
90     */
91    public function validate( $secret, $grantType ) {
92        if ( !$this->isSecretValid( $secret ) ) {
93            return false;
94        }
95
96        if ( !$this->isGrantAllowed( $grantType ) ) {
97            return false;
98        }
99
100        return true;
101    }
102
103    /**
104     * @return int
105     */
106    public function getOAuthVersion() {
107        return static::OAUTH_VERSION_2;
108    }
109
110    /**
111     * @param null|string $secret
112     * @return bool
113     */
114    private function isSecretValid( $secret ) {
115        return is_string( $secret )
116            && hash_equals( $secret, Utils::hmacDBSecret( $this->secretKey ) );
117    }
118
119    /**
120     * @param string $grantType
121     * @return bool
122     */
123    public function isGrantAllowed( $grantType ) {
124        return in_array( $grantType, $this->getAllowedGrants() );
125    }
126
127    /**
128     * @param User $mwUser
129     * @param bool $update
130     * @param string[] $grants
131     * @param null $requestTokenKey
132     * @return bool
133     * @throws MWOAuthException
134     */
135    public function authorize( User $mwUser, $update, $grants, $requestTokenKey = null ) {
136        $this->conductAuthorizationChecks( $mwUser );
137
138        $grants = $this->getVerifiedScopes( $grants );
139        $this->saveAuthorization( $mwUser, $update, $grants );
140
141        return true;
142    }
143
144    /**
145     * Get the access token to be used with a single user
146     * Should never be called outside of client registration/manage code
147     *
148     * @param ConsumerAcceptance $approval
149     * @param bool $revokeExisting Delete all existing tokens
150     *
151     * @return AccessTokenEntityInterface
152     * @throws MWOAuthException
153     * @throws OAuthServerException
154     * @throws Exception
155     */
156    public function getOwnerOnlyAccessToken(
157        ConsumerAcceptance $approval, $revokeExisting = false
158    ) {
159        $grantType = 'client_credentials';
160        if (
161            count( $this->getAllowedGrants() ) !== 1 ||
162            $this->getAllowedGrants()[0] !== $grantType
163        ) {
164            // make sure client is allowed *only* client_credentials grant,
165            // so that this AT cannot be used in other grant type requests
166            throw new MWOAuthException( 'mwoauth-oauth2-error-owner-only-invalid-grant', [
167                'consumer' => $this->getConsumerKey(),
168                'consumer_name' => $this->getName(),
169            ] );
170        }
171        $accessToken = null;
172        $accessTokenRepo = new AccessTokenRepository();
173        if ( $revokeExisting ) {
174            $accessTokenRepo->deleteForApprovalId( $approval->getId() );
175        }
176        /** @var AccessTokenEntity $accessToken */
177        $accessToken = $accessTokenRepo->getNewToken( $this, $this->getScopes(), $approval->getUserId() );
178        '@phan-var AccessTokenEntity $accessToken';
179        $claimStore = new ClaimStore();
180        $claims = $claimStore->getClaims( $grantType, $this );
181        foreach ( $claims as $claim ) {
182            $accessToken->addClaim( $claim );
183        }
184        $accessToken->setExpiryDateTime( ( new DateTimeImmutable() )->add(
185            new DateInterval( 'P1000Y' )
186        ) );
187        $accessToken->setPrivateKeyFromConfig();
188        $accessToken->setIdentifier( bin2hex( random_bytes( 40 ) ) );
189
190        $accessTokenRepo->persistNewAccessToken( $accessToken );
191
192        return $accessToken;
193    }
194
195    /**
196     * Filter out scopes that application cannot use
197     *
198     * @param string[] $requested
199     * @return string[]
200     */
201    private function getVerifiedScopes( $requested ) {
202        return array_intersect( $requested, $this->getGrants() );
203    }
204}