Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 215 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
SpecialMWOAuthManageMyGrants | |
0.00% |
0 / 215 |
|
0.00% |
0 / 9 |
1122 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
72 | |||
addSubtitleLinks | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
handleConsumerForm | |
0.00% |
0 / 110 |
|
0.00% |
0 / 1 |
156 | |||
showConsumerList | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
formatRow | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
12 | |||
irrevocableGrants | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\OAuth\Frontend\SpecialPages; |
4 | |
5 | use ErrorPageError; |
6 | use FormatJson; |
7 | use HTMLForm; |
8 | use IContextSource; |
9 | use MediaWiki\Extension\OAuth\Backend\Consumer; |
10 | use MediaWiki\Extension\OAuth\Backend\ConsumerAcceptance; |
11 | use MediaWiki\Extension\OAuth\Backend\Utils; |
12 | use MediaWiki\Extension\OAuth\Control\ConsumerAcceptanceAccessControl; |
13 | use MediaWiki\Extension\OAuth\Control\ConsumerAcceptanceSubmitControl; |
14 | use MediaWiki\Extension\OAuth\Control\ConsumerAccessControl; |
15 | use MediaWiki\Extension\OAuth\Frontend\Pagers\ManageMyGrantsPager; |
16 | use MediaWiki\Extension\OAuth\Frontend\UIUtils; |
17 | use MediaWiki\MediaWikiServices; |
18 | use MediaWiki\Permissions\GrantsInfo; |
19 | use MediaWiki\Permissions\GrantsLocalization; |
20 | use MediaWiki\SpecialPage\SpecialPage; |
21 | use MediaWiki\Status\Status; |
22 | use PermissionsError; |
23 | use stdClass; |
24 | use Wikimedia\Rdbms\IDatabase; |
25 | |
26 | /** |
27 | * (c) Aaron Schulz 2013, GPL |
28 | * |
29 | * This program is free software; you can redistribute it and/or modify |
30 | * it under the terms of the GNU General Public License as published by |
31 | * the Free Software Foundation; either version 2 of the License, or |
32 | * (at your option) any later version. |
33 | * |
34 | * This program is distributed in the hope that it will be useful, |
35 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
36 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
37 | * GNU General Public License for more details. |
38 | * |
39 | * You should have received a copy of the GNU General Public License along |
40 | * with this program; if not, write to the Free Software Foundation, Inc., |
41 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
42 | * http://www.gnu.org/copyleft/gpl.html |
43 | */ |
44 | |
45 | /** |
46 | * Special page for listing consumers this user granted access to and |
47 | * for manage the specific grants given or revoking access for the consumer |
48 | */ |
49 | class SpecialMWOAuthManageMyGrants extends SpecialPage { |
50 | /** @var string[]|null */ |
51 | private static $irrevocableGrants = null; |
52 | |
53 | /** @var GrantsInfo */ |
54 | private $grantsInfo; |
55 | |
56 | /** @var GrantsLocalization */ |
57 | private $grantsLocalization; |
58 | |
59 | /** |
60 | * @param GrantsInfo $grantsInfo |
61 | * @param GrantsLocalization $grantsLocalization |
62 | */ |
63 | public function __construct( |
64 | GrantsInfo $grantsInfo, |
65 | GrantsLocalization $grantsLocalization |
66 | ) { |
67 | parent::__construct( 'OAuthManageMyGrants', 'mwoauthmanagemygrants' ); |
68 | $this->grantsInfo = $grantsInfo; |
69 | $this->grantsLocalization = $grantsLocalization; |
70 | } |
71 | |
72 | public function doesWrites() { |
73 | return true; |
74 | } |
75 | |
76 | public function execute( $par ) { |
77 | $this->setHeaders(); |
78 | $this->getOutput()->disallowUserJs(); |
79 | $this->addHelpLink( 'Help:OAuth' ); |
80 | $this->requireNamedUser( 'mwoauth-available-only-to-registered' ); |
81 | |
82 | $user = $this->getUser(); |
83 | $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); |
84 | if ( !$permissionManager->userHasRight( $user, 'mwoauthmanagemygrants' ) ) { |
85 | throw new PermissionsError( 'mwoauthmanagemygrants' ); |
86 | } |
87 | |
88 | // Format is Special:OAuthManageMyGrants[/list|/manage/<accesstoken>] |
89 | $navigation = $par !== null ? explode( '/', $par ) : []; |
90 | $typeKey = $navigation[0] ?? null; |
91 | $acceptanceId = $navigation[1] ?? null; |
92 | |
93 | if ( $this->getConfig()->get( 'MWOAuthReadOnly' ) |
94 | && in_array( $typeKey, [ 'update', 'revoke' ] ) |
95 | ) { |
96 | throw new ErrorPageError( 'mwoauth-error', 'mwoauth-db-readonly' ); |
97 | } |
98 | |
99 | switch ( $typeKey ) { |
100 | case 'update': |
101 | case 'revoke': |
102 | $this->handleConsumerForm( $acceptanceId ?? 0, $typeKey ); |
103 | break; |
104 | default: |
105 | $this->showConsumerList(); |
106 | break; |
107 | } |
108 | |
109 | $this->addSubtitleLinks( $acceptanceId ); |
110 | |
111 | $this->getOutput()->addModuleStyles( 'ext.MWOAuth.styles' ); |
112 | } |
113 | |
114 | /** |
115 | * Show navigation links |
116 | * |
117 | * @param string|null $acceptanceId |
118 | * @return void |
119 | */ |
120 | protected function addSubtitleLinks( $acceptanceId ) { |
121 | $listLinks = []; |
122 | |
123 | if ( $acceptanceId ) { |
124 | $dbr = Utils::getCentralDB( DB_REPLICA ); |
125 | $cmraAc = ConsumerAcceptance::newFromId( $dbr, (int)$acceptanceId ); |
126 | $listLinks[] = $this->getLinkRenderer()->makeKnownLink( |
127 | $this->getPageTitle(), |
128 | $this->msg( 'mwoauthmanagemygrants-showlist' )->text() |
129 | ); |
130 | |
131 | if ( $cmraAc ) { |
132 | $cmrAc = Consumer::newFromId( $dbr, $cmraAc->getConsumerId() ); |
133 | $consumerKey = $cmrAc->getConsumerKey(); |
134 | $listLinks[] = $this->getLinkRenderer()->makeKnownLink( |
135 | SpecialPage::getTitleFor( 'OAuthListConsumers', "view/$consumerKey" ), |
136 | $this->msg( 'mwoauthconsumer-application-view' )->text() |
137 | ); |
138 | } |
139 | } else { |
140 | $listLinks[] = $this->msg( 'mwoauthmanagemygrants-showlist' )->escaped(); |
141 | } |
142 | |
143 | $linkHtml = $this->getLanguage()->pipeList( $listLinks ); |
144 | |
145 | $this->getOutput()->setSubtitle( |
146 | "<strong>" . $this->msg( 'mwoauthmanagemygrants-navigation' )->escaped() . |
147 | "</strong> [{$linkHtml}]" ); |
148 | } |
149 | |
150 | /** |
151 | * Show the form to approve/reject/disable/re-enable consumers |
152 | * |
153 | * @param int $acceptanceId |
154 | * @param string $type One of (update,revoke) |
155 | * @throws PermissionsError |
156 | */ |
157 | protected function handleConsumerForm( $acceptanceId, $type ) { |
158 | $user = $this->getUser(); |
159 | $dbr = Utils::getCentralDB( DB_REPLICA ); |
160 | $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); |
161 | |
162 | $centralUserId = Utils::getCentralIdFromLocalUser( $user ); |
163 | if ( !$centralUserId ) { |
164 | $this->getOutput()->addWikiMsg( 'badaccess-group0' ); |
165 | return; |
166 | } |
167 | |
168 | $cmraAc = ConsumerAcceptanceAccessControl::wrap( |
169 | ConsumerAcceptance::newFromId( $dbr, $acceptanceId ), $this->getContext() ); |
170 | if ( !$cmraAc || $cmraAc->getUserId() !== $centralUserId ) { |
171 | $this->getOutput()->addHTML( $this->msg( 'mwoauth-invalid-access-token' )->escaped() ); |
172 | return; |
173 | } |
174 | |
175 | $cmrAc = ConsumerAccessControl::wrap( |
176 | Consumer::newFromId( $dbr, $cmraAc->getConsumerId() ), $this->getContext() ); |
177 | if ( $cmrAc->getDeleted() |
178 | && !$permissionManager->userHasRight( $user, 'mwoauthviewsuppressed' ) ) { |
179 | throw new PermissionsError( 'mwoauthviewsuppressed' ); |
180 | } |
181 | |
182 | $action = ''; |
183 | if ( $this->getRequest()->getCheck( 'renounce' ) ) { |
184 | $action = 'renounce'; |
185 | } elseif ( $this->getRequest()->getCheck( 'update' ) ) { |
186 | $action = 'update'; |
187 | } |
188 | |
189 | $data = [ 'action' => $action ]; |
190 | $control = new ConsumerAcceptanceSubmitControl( |
191 | $this->getContext(), $data, $dbr, $cmraAc->getDAO()->getOAuthVersion() |
192 | ); |
193 | $form = HTMLForm::factory( 'ooui', |
194 | $control->registerValidators( [ |
195 | 'info' => [ |
196 | 'type' => 'info', |
197 | 'raw' => true, |
198 | 'default' => UIUtils::generateInfoTable( [ |
199 | 'mwoauth-consumer-name' => $cmrAc->getNameAndVersion(), |
200 | 'mwoauth-consumer-user' => $cmrAc->getUserName(), |
201 | 'mwoauth-consumer-description' => $cmrAc->getDescription(), |
202 | 'mwoauthmanagemygrants-wikiallowed' => $cmraAc->getWikiName(), |
203 | ], $this->getContext() ), |
204 | ], |
205 | 'grants' => [ |
206 | 'type' => 'checkmatrix', |
207 | 'label-message' => 'mwoauthmanagemygrants-applicablegrantsallowed', |
208 | 'columns' => [ |
209 | $this->msg( 'mwoauthmanagemygrants-grantaccept' )->escaped() => 'grant' |
210 | ], |
211 | 'rows' => array_combine( |
212 | array_map( [ $this->grantsLocalization, 'getGrantsLink' ], $cmrAc->getGrants() ), |
213 | $cmrAc->getGrants() |
214 | ), |
215 | 'default' => array_map( |
216 | static function ( $g ) { |
217 | return "grant-$g"; |
218 | }, |
219 | $cmraAc->getGrants() |
220 | ), |
221 | 'tooltips-html' => [ |
222 | $this->grantsLocalization->getGrantsLink( 'basic' ) => |
223 | $this->msg( 'mwoauthmanagemygrants-basic-tooltip' )->parse(), |
224 | $this->grantsLocalization->getGrantsLink( 'mwoauth-authonly' ) => |
225 | $this->msg( 'mwoauthmanagemygrants-authonly-tooltip' )->parse(), |
226 | $this->grantsLocalization->getGrantsLink( 'mwoauth-authonlyprivate' ) => |
227 | $this->msg( 'mwoauthmanagemygrants-authonly-tooltip' )->parse(), |
228 | ], |
229 | 'force-options-on' => array_map( |
230 | static function ( $g ) { |
231 | return "grant-$g"; |
232 | }, |
233 | ( $type === 'revoke' ) |
234 | ? array_merge( $this->grantsInfo->getValidGrants(), self::irrevocableGrants() ) |
235 | : self::irrevocableGrants() |
236 | ), |
237 | 'validation-callback' => null |
238 | ], |
239 | 'acceptanceId' => [ |
240 | 'type' => 'hidden', |
241 | 'default' => $cmraAc->getId(), |
242 | ] |
243 | ] ), |
244 | $this->getContext() |
245 | ); |
246 | $form->setSubmitCallback( |
247 | static function ( array $data, IContextSource $context ) use ( $action, $cmraAc ) { |
248 | $data['action'] = $action; |
249 | // adapt form to controller |
250 | $data['grants'] = FormatJson::encode( |
251 | preg_replace( '/^grant-/', '', $data['grants'] ) |
252 | ); |
253 | |
254 | $dbw = Utils::getCentralDB( DB_PRIMARY ); |
255 | $control = new ConsumerAcceptanceSubmitControl( |
256 | $context, $data, $dbw, $cmraAc->getDAO()->getOAuthVersion() |
257 | ); |
258 | return $control->submit(); |
259 | } |
260 | ); |
261 | |
262 | $form->setWrapperLegendMsg( 'mwoauthmanagemygrants-confirm-legend' ); |
263 | $form->suppressDefaultSubmit(); |
264 | if ( $type === 'revoke' ) { |
265 | $form->addButton( [ |
266 | 'name' => 'renounce', |
267 | 'value' => $this->msg( 'mwoauthmanagemygrants-renounce' )->text(), |
268 | 'flags' => [ 'primary', 'destructive' ], |
269 | ] ); |
270 | } else { |
271 | $form->addButton( [ |
272 | 'name' => 'update', |
273 | 'value' => $this->msg( 'mwoauthmanagemygrants-update' )->text(), |
274 | 'flags' => [ 'primary', 'progressive' ], |
275 | ] ); |
276 | } |
277 | $form->addPreHtml( |
278 | $this->msg( "mwoauthmanagemygrants-$type-text" )->parseAsBlock() ); |
279 | |
280 | $status = $form->show(); |
281 | if ( $status instanceof Status && $status->isOK() ) { |
282 | // Messages: mwoauthmanagemygrants-success-update, mwoauthmanagemygrants-success-renounce |
283 | $this->getOutput()->addWikiMsg( "mwoauthmanagemygrants-success-$action" ); |
284 | } |
285 | } |
286 | |
287 | /** |
288 | * Show a paged list of consumers with links to details |
289 | * |
290 | * @return void |
291 | */ |
292 | protected function showConsumerList() { |
293 | $this->getOutput()->addWikiMsg( 'mwoauthmanagemygrants-text' ); |
294 | |
295 | $centralUserId = Utils::getCentralIdFromLocalUser( $this->getUser() ); |
296 | $pager = new ManageMyGrantsPager( $this, [], $centralUserId ); |
297 | if ( $pager->getNumRows() ) { |
298 | $this->getOutput()->addHTML( $pager->getNavigationBar() ); |
299 | $this->getOutput()->addHTML( $pager->getBody() ); |
300 | $this->getOutput()->addHTML( $pager->getNavigationBar() ); |
301 | } else { |
302 | $this->getOutput()->addWikiMsg( "mwoauthmanagemygrants-none" ); |
303 | } |
304 | } |
305 | |
306 | /** |
307 | * @param IDatabase $db |
308 | * @param stdClass $row |
309 | * @return string |
310 | */ |
311 | public function formatRow( IDatabase $db, $row ) { |
312 | $cmrAc = ConsumerAccessControl::wrap( |
313 | Consumer::newFromRow( $db, $row ), $this->getContext() ); |
314 | $cmraAc = ConsumerAcceptanceAccessControl::wrap( |
315 | ConsumerAcceptance::newFromRow( $db, $row ), $this->getContext() ); |
316 | |
317 | $linkRenderer = $this->getLinkRenderer(); |
318 | |
319 | $links = []; |
320 | if ( array_diff( $cmrAc->getGrants(), self::irrevocableGrants() ) ) { |
321 | $links[] = $linkRenderer->makeKnownLink( |
322 | $this->getPageTitle( 'update/' . $cmraAc->getId() ), |
323 | $this->msg( 'mwoauthmanagemygrants-review' )->text() |
324 | ); |
325 | } |
326 | $links[] = $linkRenderer->makeKnownLink( |
327 | $this->getPageTitle( 'revoke/' . $cmraAc->getId() ), |
328 | $this->msg( 'mwoauthmanagemygrants-revoke' )->text() |
329 | ); |
330 | $reviewLinks = $this->getLanguage()->pipeList( $links ); |
331 | |
332 | $encName = $cmrAc->escapeForHtml( $cmrAc->getNameAndVersion() ); |
333 | |
334 | $r = '<li class="mw-mwoauthmanagemygrants-list-item">'; |
335 | $r .= "<strong dir='ltr'>{$encName}</strong> (<strong>$reviewLinks</strong>)"; |
336 | $data = [ |
337 | 'mwoauthmanagemygrants-user' => $cmrAc->getUserName(), |
338 | 'mwoauthmanagemygrants-wikiallowed' => $cmraAc->getWikiName(), |
339 | ]; |
340 | |
341 | foreach ( $data as $msg => $val ) { |
342 | $r .= '<p>' . $this->msg( $msg )->escaped() . ' ' . $cmrAc->escapeForHtml( $val ) . '</p>'; |
343 | } |
344 | |
345 | $editsLink = $linkRenderer->makeKnownLink( |
346 | SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() ), |
347 | $this->msg( 'mwoauthmanagemygrants-editslink', $this->getUser()->getName() )->text(), |
348 | [], |
349 | [ 'tagfilter' => Utils::getTagName( $cmrAc->getId() ) ] |
350 | ); |
351 | $r .= '<p>' . $editsLink . '</p>'; |
352 | $actionsLink = $linkRenderer->makeKnownLink( |
353 | SpecialPage::getTitleFor( 'Log' ), |
354 | $this->msg( 'mwoauthmanagemygrants-actionslink', $this->getUser()->getName() )->text(), |
355 | [], |
356 | [ |
357 | 'user' => $this->getUser()->getName(), |
358 | 'tagfilter' => Utils::getTagName( $cmrAc->getId() ), |
359 | ] |
360 | ); |
361 | $r .= '<p>' . $actionsLink . '</p>'; |
362 | |
363 | $r .= '</li>'; |
364 | |
365 | return $r; |
366 | } |
367 | |
368 | private static function irrevocableGrants() { |
369 | if ( self::$irrevocableGrants === null ) { |
370 | self::$irrevocableGrants = array_merge( |
371 | MediaWikiServices::getInstance()->getGrantsInfo()->getHiddenGrants(), |
372 | [ 'mwoauth-authonly', 'mwoauth-authonlyprivate' ] |
373 | ); |
374 | } |
375 | return self::$irrevocableGrants; |
376 | } |
377 | |
378 | protected function getGroupName() { |
379 | return 'login'; |
380 | } |
381 | } |