Refactor upload dialog to make it configurable
[lhc/web/wiklou.git] / includes / api / ApiAuthManagerHelper.php
1 <?php
2 /**
3 * Copyright © 2016 Brad Jorsch <bjorsch@wikimedia.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @since 1.27
22 */
23
24 use MediaWiki\Auth\AuthManager;
25 use MediaWiki\Auth\AuthenticationRequest;
26 use MediaWiki\Auth\AuthenticationResponse;
27 use MediaWiki\Auth\CreateFromLoginAuthenticationRequest;
28
29 /**
30 * Helper class for AuthManager-using API modules. Intended for use via
31 * composition.
32 *
33 * @ingroup API
34 */
35 class ApiAuthManagerHelper {
36
37 /** @var ApiBase API module, for context and parameters */
38 private $module;
39
40 /** @var string Message output format */
41 private $messageFormat;
42
43 /**
44 * @param ApiBase $module API module, for context and parameters
45 */
46 public function __construct( ApiBase $module ) {
47 $this->module = $module;
48
49 $params = $module->extractRequestParams();
50 $this->messageFormat = isset( $params['messageformat'] ) ? $params['messageformat'] : 'wikitext';
51 }
52
53 /**
54 * Static version of the constructor, for chaining
55 * @param ApiBase $module API module, for context and parameters
56 * @return ApiAuthManagerHelper
57 */
58 public static function newForModule( ApiBase $module ) {
59 return new self( $module );
60 }
61
62 /**
63 * Format a message for output
64 * @param array &$res Result array
65 * @param string $key Result key
66 * @param Message $message
67 */
68 private function formatMessage( array &$res, $key, Message $message ) {
69 switch ( $this->messageFormat ) {
70 case 'none':
71 break;
72
73 case 'wikitext':
74 $res[$key] = $message->setContext( $this->module )->text();
75 break;
76
77 case 'html':
78 $res[$key] = $message->setContext( $this->module )->parseAsBlock();
79 $res[$key] = Parser::stripOuterParagraph( $res[$key] );
80 break;
81
82 case 'raw':
83 $res[$key] = [
84 'key' => $message->getKey(),
85 'params' => $message->getParams(),
86 ];
87 break;
88 }
89 }
90
91 /**
92 * Call $manager->securitySensitiveOperationStatus()
93 * @param string $operation Operation being checked.
94 * @throws UsageException
95 */
96 public function securitySensitiveOperation( $operation ) {
97 $status = AuthManager::singleton()->securitySensitiveOperationStatus( $operation );
98 switch ( $status ) {
99 case AuthManager::SEC_OK:
100 return;
101
102 case AuthManager::SEC_REAUTH:
103 $this->module->dieUsage(
104 'You have not authenticated recently in this session, please reauthenticate.', 'reauthenticate'
105 );
106
107 case AuthManager::SEC_FAIL:
108 $this->module->dieUsage(
109 'This action is not available as your identify cannot be verified.', 'cannotreauthenticate'
110 );
111
112 default:
113 throw new UnexpectedValueException( "Unknown status \"$status\"" );
114 }
115 }
116
117 /**
118 * Filter out authentication requests by class name
119 * @param AuthenticationRequest[] $reqs Requests to filter
120 * @param string[] $blacklist Class names to remove
121 * @return AuthenticationRequest[]
122 */
123 public static function blacklistAuthenticationRequests( array $reqs, array $blacklist ) {
124 if ( $blacklist ) {
125 $blacklist = array_flip( $blacklist );
126 $reqs = array_filter( $reqs, function ( $req ) use ( $blacklist ) {
127 return !isset( $blacklist[get_class( $req )] );
128 } );
129 }
130 return $reqs;
131 }
132
133 /**
134 * Fetch and load the AuthenticationRequests for an action
135 * @param string $action One of the AuthManager::ACTION_* constants
136 * @return AuthenticationRequest[]
137 */
138 public function loadAuthenticationRequests( $action ) {
139 $params = $this->module->extractRequestParams();
140
141 $manager = AuthManager::singleton();
142 $reqs = $manager->getAuthenticationRequests( $action, $this->module->getUser() );
143
144 // Filter requests, if requested to do so
145 $wantedRequests = null;
146 if ( isset( $params['requests'] ) ) {
147 $wantedRequests = array_flip( $params['requests'] );
148 } elseif ( isset( $params['request'] ) ) {
149 $wantedRequests = [ $params['request'] => true ];
150 }
151 if ( $wantedRequests !== null ) {
152 $reqs = array_filter( $reqs, function ( $req ) use ( $wantedRequests ) {
153 return isset( $wantedRequests[$req->getUniqueId()] );
154 } );
155 }
156
157 // Collect the fields for all the requests
158 $fields = [];
159 foreach ( $reqs as $req ) {
160 $fields += (array)$req->getFieldInfo();
161 }
162
163 // Extract the request data for the fields and mark those request
164 // parameters as used
165 $data = array_intersect_key( $this->module->getRequest()->getValues(), $fields );
166 $this->module->getMain()->markParamsUsed( array_keys( $data ) );
167
168 return AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
169 }
170
171 /**
172 * Format an AuthenticationResponse for return
173 * @param AuthenticationResponse $res
174 * @return array
175 */
176 public function formatAuthenticationResponse( AuthenticationResponse $res ) {
177 $params = $this->module->extractRequestParams();
178
179 $ret = [
180 'status' => $res->status,
181 ];
182
183 if ( $res->status === AuthenticationResponse::PASS && $res->username !== null ) {
184 $ret['username'] = $res->username;
185 }
186
187 if ( $res->status === AuthenticationResponse::REDIRECT ) {
188 $ret['redirecttarget'] = $res->redirectTarget;
189 if ( $res->redirectApiData !== null ) {
190 $ret['redirectdata'] = $res->redirectApiData;
191 }
192 }
193
194 if ( $res->status === AuthenticationResponse::REDIRECT ||
195 $res->status === AuthenticationResponse::UI ||
196 $res->status === AuthenticationResponse::RESTART
197 ) {
198 $ret += $this->formatRequests( $res->neededRequests );
199 }
200
201 if ( $res->status === AuthenticationResponse::FAIL ||
202 $res->status === AuthenticationResponse::UI ||
203 $res->status === AuthenticationResponse::RESTART
204 ) {
205 $this->formatMessage( $ret, 'message', $res->message );
206 }
207
208 if ( $res->status === AuthenticationResponse::FAIL ||
209 $res->status === AuthenticationResponse::RESTART
210 ) {
211 $this->module->getRequest()->getSession()->set(
212 'ApiAuthManagerHelper::createRequest',
213 $res->createRequest
214 );
215 $ret['canpreservestate'] = $res->createRequest !== null;
216 } else {
217 $this->module->getRequest()->getSession()->remove( 'ApiAuthManagerHelper::createRequest' );
218 }
219
220 return $ret;
221 }
222
223 /**
224 * Fetch the preserved CreateFromLoginAuthenticationRequest, if any
225 * @return CreateFromLoginAuthenticationRequest|null
226 */
227 public function getPreservedRequest() {
228 $ret = $this->module->getRequest()->getSession()->get( 'ApiAuthManagerHelper::createRequest' );
229 return $ret instanceof CreateFromLoginAuthenticationRequest ? $ret : null;
230 }
231
232 /**
233 * Format an array of AuthenticationRequests for return
234 * @param AuthenticationRequest[] $reqs
235 * @return array Will have a 'requests' key, and also 'fields' if $module's
236 * params include 'mergerequestfields'.
237 */
238 public function formatRequests( array $reqs ) {
239 $params = $this->module->extractRequestParams();
240 $mergeFields = !empty( $params['mergerequestfields'] );
241
242 $ret = [ 'requests' => [] ];
243 foreach ( $reqs as $req ) {
244 $describe = $req->describeCredentials();
245 $reqInfo = [
246 'id' => $req->getUniqueId(),
247 'metadata' => $req->getMetadata(),
248 ];
249 switch ( $req->required ) {
250 case AuthenticationRequest::OPTIONAL:
251 $reqInfo['required'] = 'optional';
252 break;
253 case AuthenticationRequest::REQUIRED:
254 $reqInfo['required'] = 'required';
255 break;
256 case AuthenticationRequest::PRIMARY_REQUIRED:
257 $reqInfo['required'] = 'primary-required';
258 break;
259 }
260 $this->formatMessage( $reqInfo, 'provider', $describe['provider'] );
261 $this->formatMessage( $reqInfo, 'account', $describe['account'] );
262 if ( !$mergeFields ) {
263 $reqInfo['fields'] = $this->formatFields( (array)$req->getFieldInfo() );
264 }
265 $ret['requests'][] = $reqInfo;
266 }
267
268 if ( $mergeFields ) {
269 $fields = AuthenticationRequest::mergeFieldInfo( $reqs );
270 $ret['fields'] = $this->formatFields( $fields );
271 }
272
273 return $ret;
274 }
275
276 /**
277 * Clean up a field array for output
278 * @param ApiBase $module For context and parameters 'mergerequestfields'
279 * and 'messageformat'
280 * @param array $fields
281 * @return array
282 */
283 private function formatFields( array $fields ) {
284 static $copy = [
285 'type' => true,
286 'image' => true,
287 'value' => true,
288 ];
289
290 $module = $this->module;
291 $retFields = [];
292
293 foreach ( $fields as $name => $field ) {
294 $ret = array_intersect_key( $field, $copy );
295
296 if ( isset( $field['options'] ) ) {
297 $ret['options'] = array_map( function ( $msg ) use ( $module ) {
298 return $msg->setContext( $module )->plain();
299 }, $field['options'] );
300 ApiResult::setArrayType( $ret['options'], 'assoc' );
301 }
302 $this->formatMessage( $ret, 'label', $field['label'] );
303 $this->formatMessage( $ret, 'help', $field['help'] );
304 $ret['optional'] = !empty( $field['optional'] );
305
306 $retFields[$name] = $ret;
307 }
308
309 ApiResult::setArrayType( $retFields, 'assoc' );
310
311 return $retFields;
312 }
313
314 /**
315 * Fetch the standard parameters this helper recognizes
316 * @param string $action AuthManager action
317 * @param string $param... Parameters to use
318 * @return array
319 */
320 public static function getStandardParams( $action, $param /* ... */ ) {
321 $params = [
322 'requests' => [
323 ApiBase::PARAM_TYPE => 'string',
324 ApiBase::PARAM_ISMULTI => true,
325 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-requests', $action ],
326 ],
327 'request' => [
328 ApiBase::PARAM_TYPE => 'string',
329 ApiBase::PARAM_REQUIRED => true,
330 ApiBase::PARAM_HELP_MSG => [ 'api-help-authmanagerhelper-request', $action ],
331 ],
332 'messageformat' => [
333 ApiBase::PARAM_DFLT => 'wikitext',
334 ApiBase::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
335 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-messageformat',
336 ],
337 'mergerequestfields' => [
338 ApiBase::PARAM_DFLT => false,
339 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-mergerequestfields',
340 ],
341 'preservestate' => [
342 ApiBase::PARAM_DFLT => false,
343 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-preservestate',
344 ],
345 'returnurl' => [
346 ApiBase::PARAM_TYPE => 'string',
347 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-returnurl',
348 ],
349 'continue' => [
350 ApiBase::PARAM_DFLT => false,
351 ApiBase::PARAM_HELP_MSG => 'api-help-authmanagerhelper-continue',
352 ],
353 ];
354
355 $ret = [];
356 $wantedParams = func_get_args();
357 array_shift( $wantedParams );
358 foreach ( $wantedParams as $name ) {
359 if ( isset( $params[$name] ) ) {
360 $ret[$name] = $params[$name];
361 }
362 }
363 return $ret;
364 }
365 }