Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
41.51% |
22 / 53 |
|
16.67% |
1 / 6 |
CRAP | |
0.00% |
0 / 1 |
AccessTokenEntity | |
41.51% |
22 / 53 |
|
16.67% |
1 / 6 |
100.04 | |
0.00% |
0 / 1 |
__construct | |
55.00% |
11 / 20 |
|
0.00% |
0 / 1 |
11.47 | |||
getApproval | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setPrivateKeyFromConfig | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getClient | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setApprovalFromClientScopesUser | |
31.58% |
6 / 19 |
|
0.00% |
0 / 1 |
22.70 | |||
confirmClientUsable | |
44.44% |
4 / 9 |
|
0.00% |
0 / 1 |
4.54 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\OAuth\Entity; |
4 | |
5 | use InvalidArgumentException; |
6 | use League\OAuth2\Server\CryptKey; |
7 | use League\OAuth2\Server\Entities\AccessTokenEntityInterface; |
8 | use League\OAuth2\Server\Entities\ScopeEntityInterface; |
9 | use League\OAuth2\Server\Entities\Traits\AccessTokenTrait; |
10 | use League\OAuth2\Server\Entities\Traits\EntityTrait; |
11 | use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; |
12 | use League\OAuth2\Server\Exception\OAuthServerException; |
13 | use MediaWiki\Extension\OAuth\Backend\ConsumerAcceptance; |
14 | use MediaWiki\Extension\OAuth\Backend\Utils; |
15 | use MediaWiki\MediaWikiServices; |
16 | use MediaWiki\User\User; |
17 | use MediaWiki\WikiMap\WikiMap; |
18 | use Throwable; |
19 | |
20 | class AccessTokenEntity implements AccessTokenEntityInterface { |
21 | use AccessTokenTrait; |
22 | use EntityTrait; |
23 | use TokenEntityTrait; |
24 | |
25 | /** |
26 | * @var ClientEntity |
27 | */ |
28 | protected $client; |
29 | |
30 | /** |
31 | * User approval of the client |
32 | * |
33 | * @var ConsumerAcceptance|false |
34 | */ |
35 | private $approval; |
36 | |
37 | /** |
38 | * @param ClientEntity $clientEntity |
39 | * @param ScopeEntityInterface[] $scopes |
40 | * @param string $issuer |
41 | * @param string|int|null $userIdentifier |
42 | * @throws OAuthServerException |
43 | */ |
44 | public function __construct( |
45 | ClientEntity $clientEntity, |
46 | array $scopes, |
47 | string $issuer, |
48 | $userIdentifier = null |
49 | ) { |
50 | $this->approval = $this->setApprovalFromClientScopesUser( |
51 | $clientEntity, $scopes, $userIdentifier |
52 | ); |
53 | |
54 | $this->setClient( $clientEntity ); |
55 | $this->setIssuer( $issuer ); |
56 | if ( $clientEntity->getOwnerOnly() ) { |
57 | if ( $userIdentifier !== null && $userIdentifier !== $clientEntity->getUserId() ) { |
58 | throw new InvalidArgumentException( |
59 | '$userIdentifier must be null, or match the client owner user id,' . |
60 | ' for owner-only clients, ' . $userIdentifier . ' given' |
61 | ); |
62 | } |
63 | foreach ( $clientEntity->getScopes() as $scope ) { |
64 | $this->addScope( $scope ); |
65 | } |
66 | $this->setUserIdentifier( $clientEntity->getUserId() ); |
67 | } else { |
68 | foreach ( $scopes as $scope ) { |
69 | if ( !in_array( $scope->getIdentifier(), $clientEntity->getGrants() ) ) { |
70 | continue; |
71 | } |
72 | $this->addScope( $scope ); |
73 | } |
74 | $this->setUserIdentifier( $userIdentifier ); |
75 | } |
76 | |
77 | $this->confirmClientUsable(); |
78 | } |
79 | |
80 | /** |
81 | * Get the approval that allows this AT to be created |
82 | * |
83 | * @return ConsumerAcceptance|false |
84 | */ |
85 | public function getApproval() { |
86 | return $this->approval; |
87 | } |
88 | |
89 | /** |
90 | * Set configured private key |
91 | */ |
92 | public function setPrivateKeyFromConfig() { |
93 | $oauthConfig = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'mwoauth' ); |
94 | // Private key to sign the token |
95 | $privateKey = new CryptKey( $oauthConfig->get( 'OAuth2PrivateKey' ) ); |
96 | $this->setPrivateKey( $privateKey ); |
97 | } |
98 | |
99 | /** |
100 | * Get the client that the token was issued to. |
101 | * |
102 | * @return ClientEntity |
103 | */ |
104 | public function getClient() { |
105 | return $this->client; |
106 | } |
107 | |
108 | /** |
109 | * @param ClientEntity $clientEntity |
110 | * @param array $scopes |
111 | * @param string|int|null $userIdentifier |
112 | * @return ConsumerAcceptance|false |
113 | */ |
114 | private function setApprovalFromClientScopesUser( |
115 | ClientEntity $clientEntity, array $scopes, $userIdentifier = null |
116 | ) { |
117 | if ( $clientEntity->getOwnerOnly() && $userIdentifier === null ) { |
118 | $userIdentifier = $clientEntity->getUserId(); |
119 | $scopes = $clientEntity->getScopes(); |
120 | } |
121 | if ( !$userIdentifier ) { |
122 | return false; |
123 | } |
124 | try { |
125 | $user = Utils::getLocalUserFromCentralId( $userIdentifier ); |
126 | $approval = $clientEntity->getCurrentAuthorization( $user, WikiMap::getCurrentWikiId() ); |
127 | } catch ( Throwable $ex ) { |
128 | return false; |
129 | } |
130 | if ( !$approval ) { |
131 | return $approval; |
132 | } |
133 | |
134 | $approvedScopes = $approval->getGrants(); |
135 | $notApproved = array_filter( |
136 | $scopes, |
137 | static function ( ScopeEntityInterface $scope ) use ( $approvedScopes ) { |
138 | return !in_array( $scope->getIdentifier(), $approvedScopes, true ); |
139 | } |
140 | ); |
141 | |
142 | return !$notApproved ? $approval : false; |
143 | } |
144 | |
145 | private function confirmClientUsable() { |
146 | $userId = $this->getUserIdentifier() ?? 0; |
147 | $user = Utils::getLocalUserFromCentralId( $userId ); |
148 | if ( !$user ) { |
149 | $user = User::newFromId( 0 ); |
150 | } |
151 | |
152 | if ( !$this->getClient()->isUsableBy( $user ) ) { |
153 | throw OAuthServerException::accessDenied( |
154 | 'Client ' . $this->getClient()->getIdentifier() . |
155 | ' is not usable by user with ID ' . $user->getId() |
156 | ); |
157 | } |
158 | } |
159 | |
160 | } |