Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 67 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
ResourceServer | |
0.00% |
0 / 67 |
|
0.00% |
0 / 15 |
702 | |
0.00% |
0 / 1 |
factory | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
isOAuth2Request | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
verify | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getUser | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getClient | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getScopes | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getAccessTokenId | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isScopeAllowed | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
setVerifiedInfo | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
setUser | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
setClient | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
setScopes | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
setAccessTokenId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
assertVerified | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\OAuth; |
4 | |
5 | use League\OAuth2\Server\Entities\ScopeEntityInterface; |
6 | use League\OAuth2\Server\Middleware\ResourceServerMiddleware; |
7 | use League\OAuth2\Server\ResourceServer as LeagueResourceServer; |
8 | use MediaWiki\Extension\OAuth\Backend\Consumer; |
9 | use MediaWiki\Extension\OAuth\Backend\MWOAuthException; |
10 | use MediaWiki\Extension\OAuth\Backend\Utils; |
11 | use MediaWiki\Extension\OAuth\Entity\ClientEntity; |
12 | use MediaWiki\Extension\OAuth\Entity\ScopeEntity; |
13 | use MediaWiki\Extension\OAuth\Repository\AccessTokenRepository; |
14 | use MediaWiki\Extension\OAuth\Repository\ScopeRepository; |
15 | use MediaWiki\MediaWikiServices; |
16 | use MediaWiki\Request\WebRequest; |
17 | use MediaWiki\Rest\HttpException; |
18 | use MediaWiki\User\User; |
19 | use MWException; |
20 | use Psr\Http\Message\ResponseInterface; |
21 | use Psr\Http\Message\ServerRequestInterface; |
22 | |
23 | class ResourceServer { |
24 | /** @var ResourceServerMiddleware */ |
25 | protected $middleware; |
26 | /** @var User */ |
27 | protected $user; |
28 | /** @var ClientEntity */ |
29 | protected $client; |
30 | /** @var ScopeEntity[] */ |
31 | protected $scopes; |
32 | /** @var string */ |
33 | protected $accessTokenId; |
34 | /** @var bool */ |
35 | protected $verified = false; |
36 | |
37 | public static function factory() { |
38 | $config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'mwoauth' ); |
39 | return new static( $config->get( 'OAuth2PublicKey' ), $config->get( 'CanonicalServer' ) ); |
40 | } |
41 | |
42 | /** |
43 | * @param string $publicKey |
44 | * @param string $canonicalServer |
45 | */ |
46 | protected function __construct( string $publicKey, string $canonicalServer ) { |
47 | $accessTokenRepository = new AccessTokenRepository( $canonicalServer ); |
48 | |
49 | $server = new LeagueResourceServer( |
50 | $accessTokenRepository, |
51 | $publicKey |
52 | ); |
53 | $this->middleware = new ResourceServerMiddleware( $server ); |
54 | } |
55 | |
56 | /** |
57 | * Check if the request is an OAuth2 request |
58 | * |
59 | * @param WebRequest|ServerRequestInterface $request |
60 | * @return bool |
61 | */ |
62 | public static function isOAuth2Request( $request ) { |
63 | $authHeader = $request->getHeader( 'authorization' ); |
64 | |
65 | // Normalize to array |
66 | if ( is_string( $authHeader ) ) { |
67 | $authHeader = [ $authHeader ]; |
68 | } |
69 | if ( $authHeader && strpos( $authHeader[0], 'Bearer' ) === 0 ) { |
70 | return true; |
71 | } |
72 | return false; |
73 | } |
74 | |
75 | /** |
76 | * @param ServerRequestInterface $request |
77 | * @param ResponseInterface $response |
78 | * @param callable $callback |
79 | * @return ResponseInterface |
80 | */ |
81 | public function verify( $request, $response, $callback ) { |
82 | $this->verified = false; |
83 | |
84 | return $this->middleware->__invoke( |
85 | $request, |
86 | $response, |
87 | function ( $request, $response ) use ( $callback ) { |
88 | $this->setVerifiedInfo( $request ); |
89 | return $callback( $request, $response ); |
90 | } |
91 | ); |
92 | } |
93 | |
94 | /** |
95 | * @return User |
96 | * @throws MWOAuthException |
97 | */ |
98 | public function getUser() { |
99 | $this->assertVerified(); |
100 | return $this->user; |
101 | } |
102 | |
103 | /** |
104 | * @return ClientEntity |
105 | * @throws MWOAuthException |
106 | */ |
107 | public function getClient() { |
108 | $this->assertVerified(); |
109 | return $this->client; |
110 | } |
111 | |
112 | /** |
113 | * @return ScopeEntity[] |
114 | * @throws MWOAuthException |
115 | */ |
116 | public function getScopes() { |
117 | $this->assertVerified(); |
118 | return $this->scopes; |
119 | } |
120 | |
121 | /** |
122 | * Get access token this request was made with |
123 | * |
124 | * @return string |
125 | * @throws MWOAuthException |
126 | */ |
127 | public function getAccessTokenId() { |
128 | $this->assertVerified(); |
129 | return $this->accessTokenId; |
130 | } |
131 | |
132 | /** |
133 | * Check if the scope is allowed |
134 | * |
135 | * @param string|ScopeEntityInterface $scope |
136 | * @return bool |
137 | * @throws MWOAuthException |
138 | */ |
139 | public function isScopeAllowed( $scope ) { |
140 | $this->assertVerified(); |
141 | |
142 | if ( $scope instanceof ScopeEntityInterface ) { |
143 | $scope = $scope->getIdentifier(); |
144 | } |
145 | |
146 | return isset( $this->scopes[$scope] ); |
147 | } |
148 | |
149 | /** |
150 | * Read out the verified request and get relevant information |
151 | * |
152 | * @param ServerRequestInterface $request |
153 | * @throws HttpException |
154 | */ |
155 | public function setVerifiedInfo( ServerRequestInterface $request ) { |
156 | $this->setUser( $request ); |
157 | $this->setClient( $request ); |
158 | $this->setScopes( $request ); |
159 | $this->setAccessTokenId( $request ); |
160 | |
161 | $this->verified = true; |
162 | } |
163 | |
164 | /** |
165 | * Set authorized user to the global context |
166 | * |
167 | * @param ServerRequestInterface $request |
168 | * @throws HttpException |
169 | */ |
170 | private function setUser( ServerRequestInterface $request ) { |
171 | $userId = $request->getAttribute( 'oauth_user_id', 0 ); |
172 | if ( !$userId ) { |
173 | // Set anon user when no user id is present in the AT (machine grant) |
174 | $this->user = User::newFromId( 0 ); |
175 | return; |
176 | } |
177 | |
178 | try { |
179 | $user = Utils::getLocalUserFromCentralId( $userId ); |
180 | } catch ( MWException $ex ) { |
181 | throw new HttpException( $ex->getMessage(), 403 ); |
182 | } |
183 | |
184 | $this->user = $user; |
185 | } |
186 | |
187 | /** |
188 | * Set the ClientEntity from validated request |
189 | * |
190 | * @param ServerRequestInterface $request |
191 | * @throws HttpException |
192 | */ |
193 | private function setClient( ServerRequestInterface $request ) { |
194 | $this->client = ClientEntity::newFromKey( |
195 | Utils::getCentralDB( DB_REPLICA ), |
196 | $request->getAttribute( 'oauth_client_id' ) |
197 | ); |
198 | if ( !$this->client || $this->client->getOAuthVersion() !== Consumer::OAUTH_VERSION_2 ) { |
199 | throw new HttpException( 'Client represented by given access token is invalid', 403 ); |
200 | } |
201 | } |
202 | |
203 | /** |
204 | * Set validated scopes |
205 | * |
206 | * @param ServerRequestInterface $request |
207 | */ |
208 | private function setScopes( ServerRequestInterface $request ) { |
209 | $scopeNames = $request->getAttribute( 'oauth_scopes', [] ); |
210 | $scopeRepo = new ScopeRepository(); |
211 | foreach ( $scopeNames as $scopeName ) { |
212 | $scope = $scopeRepo->getScopeEntityByIdentifier( $scopeName ); |
213 | if ( !$scope ) { |
214 | continue; |
215 | } |
216 | $this->scopes[$scope->getIdentifier()] = $scope; |
217 | } |
218 | } |
219 | |
220 | /** |
221 | * Set the access token this request was made with |
222 | * |
223 | * @param ServerRequestInterface $request |
224 | */ |
225 | private function setAccessTokenId( ServerRequestInterface $request ) { |
226 | $this->accessTokenId = $request->getAttribute( 'oauth_access_token_id' ); |
227 | } |
228 | |
229 | private function assertVerified() { |
230 | if ( !$this->verified ) { |
231 | throw new MWOAuthException( 'mwoauth-oauth2-error-request-not-verified', [ |
232 | 'consumer' => $this->getClient()->getConsumerKey(), |
233 | 'consumer_name' => $this->getClient()->getName(), |
234 | ] ); |
235 | } |
236 | } |
237 | } |