Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 74 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
AuthenticationHandler | |
0.00% |
0 / 74 |
|
0.00% |
0 / 11 |
552 | |
0.00% |
0 / 1 |
factory | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
needsReadAccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
needsWriteAccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAuthorizationProvider | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
validate | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
queueError | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getQueryParamsCgi | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
errorResponse | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
getLocalizedErrorMessage | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
12 | |||
detectExtraneousBodyFields | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGrantType | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getGrantClass | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\OAuth\Rest\Handler; |
4 | |
5 | use League\OAuth2\Server\Exception\OAuthServerException; |
6 | use LogicException; |
7 | use MediaWiki\Config\Config; |
8 | use MediaWiki\Context\RequestContext; |
9 | use MediaWiki\Extension\OAuth\AuthorizationProvider\AccessToken as AccessTokenProvider; |
10 | use MediaWiki\Extension\OAuth\AuthorizationProvider\Grant\AuthorizationCodeAuthorization; |
11 | use MediaWiki\Extension\OAuth\Backend\Utils; |
12 | use MediaWiki\Extension\OAuth\Response; |
13 | use MediaWiki\MediaWikiServices; |
14 | use MediaWiki\Message\Message; |
15 | use MediaWiki\Rest\Handler; |
16 | use MediaWiki\Rest\HttpException; |
17 | use MediaWiki\Rest\LocalizedHttpException; |
18 | use MediaWiki\Rest\Response as RestResponse; |
19 | use MediaWiki\Rest\StringStream; |
20 | use MediaWiki\Rest\Validator\Validator; |
21 | use MediaWiki\User\User; |
22 | use Psr\Http\Message\ResponseInterface; |
23 | |
24 | abstract class AuthenticationHandler extends Handler { |
25 | |
26 | /** |
27 | * @var User |
28 | */ |
29 | protected $user; |
30 | |
31 | /** |
32 | * @var Config |
33 | */ |
34 | protected $config; |
35 | |
36 | /** |
37 | * @var OAuthServerException|null |
38 | */ |
39 | protected $queuedError; |
40 | |
41 | /** |
42 | * @return AuthenticationHandler |
43 | */ |
44 | public static function factory() { |
45 | $centralId = Utils::getCentralIdFromLocalUser( RequestContext::getMain()->getUser() ); |
46 | $user = $centralId ? Utils::getLocalUserFromCentralId( $centralId ) : User::newFromId( 0 ); |
47 | $config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'mwoauth' ); |
48 | // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic |
49 | return new static( $user, $config ); |
50 | } |
51 | |
52 | /** |
53 | * @param User $user |
54 | * @param Config $config |
55 | */ |
56 | protected function __construct( User $user, Config $config ) { |
57 | $this->user = $user; |
58 | $this->config = $config; |
59 | } |
60 | |
61 | /** |
62 | * We do not want any permission checks |
63 | * |
64 | * @return bool |
65 | */ |
66 | public function needsReadAccess() { |
67 | return false; |
68 | } |
69 | |
70 | /** |
71 | * We do not want any permission checks |
72 | * |
73 | * @return bool |
74 | */ |
75 | public function needsWriteAccess() { |
76 | return false; |
77 | } |
78 | |
79 | /** |
80 | * @throws HttpException |
81 | * @return AccessTokenProvider|AuthorizationCodeAuthorization |
82 | */ |
83 | protected function getAuthorizationProvider() { |
84 | $grantType = $this->getGrantType(); |
85 | |
86 | $class = $this->getGrantClass( $grantType ); |
87 | if ( !$class || !is_callable( [ $class, 'factory' ] ) ) { |
88 | throw new LogicException( 'Could not find grant class factory' ); |
89 | } |
90 | |
91 | /** @var AccessTokenProvider|AuthorizationCodeAuthorization $authProvider */ |
92 | $authProvider = $class::factory(); |
93 | '@phan-var AccessTokenProvider|AuthorizationCodeAuthorization $authProvider'; |
94 | return $authProvider; |
95 | } |
96 | |
97 | public function validate( Validator $restValidator ) { |
98 | try { |
99 | parent::validate( $restValidator ); |
100 | } catch ( HttpException $exception ) { |
101 | if ( $exception instanceof LocalizedHttpException ) { |
102 | $formatted = $this->getResponseFactory()->formatMessage( $exception->getMessageValue() ); |
103 | $message = $formatted['messageTranslations']['en'] ?? reset( $formatted['messageTranslations'] ); |
104 | } else { |
105 | $message = $exception->getMessage(); |
106 | } |
107 | // Catch and store any validation errors, so they can be thrown |
108 | // during the execution, and get caught by appropriate error handling code |
109 | $type = $exception->getErrorData()['error'] ?? 'parameter-validation-failed'; |
110 | if ( $type === 'parameter-validation-failed' ) { |
111 | $missingParam = $exception->getErrorData()['name'] ?? ''; |
112 | $this->queueError( new OAuthServerException( |
113 | // OAuthServerException::invalidRequest() but with more useful text |
114 | 'Invalid request: ' . $message, |
115 | 3, |
116 | 'invalid_request', |
117 | 400, |
118 | $missingParam ? \sprintf( 'Check the `%s` parameter', $missingParam ) : null |
119 | ) ); |
120 | return; |
121 | } |
122 | $this->queueError( OAuthServerException::serverError( $message ) ); |
123 | } |
124 | } |
125 | |
126 | /** |
127 | * @param OAuthServerException $ex |
128 | */ |
129 | protected function queueError( OAuthServerException $ex ) { |
130 | // If already set, do not override, since we cannot throw more than one error, |
131 | // and it will probably be more useful to throw first error that occurred |
132 | if ( !$this->queuedError ) { |
133 | $this->queuedError = $ex; |
134 | } |
135 | } |
136 | |
137 | /** |
138 | * @param array $query |
139 | * @return string |
140 | */ |
141 | protected function getQueryParamsCgi( $query = [] ) { |
142 | $queryParams = $this->getRequest()->getQueryParams(); |
143 | unset( $queryParams['title'] ); |
144 | |
145 | $queryParams = array_merge( $queryParams, $query ); |
146 | return wfArrayToCgi( $queryParams ); |
147 | } |
148 | |
149 | /** |
150 | * @param OAuthServerException $exception |
151 | * @param Response|null $response |
152 | * @return ResponseInterface|RestResponse |
153 | */ |
154 | protected function errorResponse( $exception, $response = null ) { |
155 | $response ??= new Response(); |
156 | $response = $exception->generateHttpResponse( $response ); |
157 | if ( $exception->hasRedirect() || $this->getRequest()->getMethod() === 'POST' ) { |
158 | return $response; |
159 | } |
160 | |
161 | $out = RequestContext::getMain()->getOutput(); |
162 | $out->showErrorPage( |
163 | 'mwoauth-error', |
164 | $this->getLocalizedErrorMessage( $exception ) |
165 | ); |
166 | |
167 | ob_start(); |
168 | $out->output(); |
169 | $html = ob_get_clean(); |
170 | |
171 | $response = $this->getResponseFactory()->create(); |
172 | $stream = new StringStream( $html ); |
173 | $response->setStatus( $exception->getHttpStatusCode() ); |
174 | $response->setHeader( 'Content-Type', 'text/html' ); |
175 | $response->setBody( $stream ); |
176 | |
177 | return $response; |
178 | } |
179 | |
180 | private function getLocalizedErrorMessage( OAuthServerException $exception ): Message { |
181 | $type = $exception->getErrorType(); |
182 | $map = [ |
183 | 'invalid_client' => 'mwoauth-oauth2-error-invalid-client', |
184 | 'invalid_request' => 'mwoauth-oauth2-error-invalid-request', |
185 | 'unauthorized_client' => 'mwoauth-oauth2-error-unauthorized-client', |
186 | 'access_denied' => 'mwoauth-oauth2-error-access-denied', |
187 | 'unsupported_response_type' => 'mwoauth-oauth2-error-unsupported-response-type', |
188 | 'invalid_scope' => 'mwoauth-oauth2-error-invalid-scope', |
189 | 'temporarily_unavailable' => 'mwoauth-oauth2-error-temporarily-unavailable', |
190 | // 'server_error' is passed through to the catch-all handler below |
191 | ]; |
192 | $msg = isset( $map[$type] ) |
193 | ? wfMessage( $map[$type] ) |
194 | : wfMessage( 'mwoauth-oauth2-error-server-error', $exception->getMessage() ); |
195 | if ( $exception->getHint() ) { |
196 | return wfMessage( 'mwoauth-oauth2-error-serverexception-withhint', $msg, $exception->getHint() ); |
197 | } else { |
198 | return $msg; |
199 | } |
200 | } |
201 | |
202 | /** @inheritDoc */ |
203 | protected function detectExtraneousBodyFields( Validator $restValidator ) { |
204 | // Ignore unexpected parameters per https://datatracker.ietf.org/doc/html/rfc6749#section-3.1 |
205 | // and https://datatracker.ietf.org/doc/html/rfc6749#section-3.2 : |
206 | // "The authorization server MUST ignore unrecognized request parameters." |
207 | } |
208 | |
209 | /** |
210 | * @return string |
211 | */ |
212 | abstract protected function getGrantType(); |
213 | |
214 | /** |
215 | * @param string $grantType |
216 | * @return string|false |
217 | */ |
218 | abstract protected function getGrantClass( $grantType ); |
219 | |
220 | } |