Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
55.32% |
26 / 47 |
|
42.86% |
6 / 14 |
CRAP | |
0.00% |
0 / 1 |
ClientEntity | |
55.32% |
26 / 47 |
|
42.86% |
6 / 14 |
65.17 | |
0.00% |
0 / 1 |
getRedirectUri | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isConfidential | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIdentifier | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setIdentifier | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllowedGrants | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getScopes | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validate | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getOAuthVersion | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isSecretValid | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
isGrantAllowed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
authorize | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getOwnerOnlyAccessToken | |
73.91% |
17 / 23 |
|
0.00% |
0 / 1 |
5.44 | |||
getVerifiedScopes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\OAuth\Entity; |
4 | |
5 | use DateInterval; |
6 | use DateTimeImmutable; |
7 | use Exception; |
8 | use League\OAuth2\Server\Entities\AccessTokenEntityInterface; |
9 | use League\OAuth2\Server\Entities\ScopeEntityInterface; |
10 | use League\OAuth2\Server\Exception\OAuthServerException; |
11 | use MediaWiki\Extension\OAuth\Backend\Consumer; |
12 | use MediaWiki\Extension\OAuth\Backend\ConsumerAcceptance; |
13 | use MediaWiki\Extension\OAuth\Backend\MWOAuthException; |
14 | use MediaWiki\Extension\OAuth\Backend\Utils; |
15 | use MediaWiki\Extension\OAuth\Repository\AccessTokenRepository; |
16 | use MediaWiki\Extension\OAuth\Repository\ClaimStore; |
17 | use MediaWiki\User\User; |
18 | |
19 | class 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 | } |