Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 239
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialMWOAuthListConsumers
0.00% covered (danger)
0.00%
0 / 239
0.00% covered (danger)
0.00%
0 / 12
1892
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 showConsumerInfo
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
210
 showConsumerListForm
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
2
 showConsumerList
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 formatRow
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
30
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addNavigationSubtitle
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 updateLink
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 manageConsumerLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 manageMyGrantsLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 userGrantedAcceptance
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Extension\OAuth\Frontend\SpecialPages;
4
5/**
6 * (c) Aaron Schulz 2013, GPL
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * http://www.gnu.org/copyleft/gpl.html
22 */
23
24use HTMLForm;
25use LogEventsList;
26use LogPage;
27use MediaWiki\Extension\OAuth\Backend\Consumer;
28use MediaWiki\Extension\OAuth\Backend\ConsumerAcceptance;
29use MediaWiki\Extension\OAuth\Backend\Utils;
30use MediaWiki\Extension\OAuth\Control\ConsumerAccessControl;
31use MediaWiki\Extension\OAuth\Frontend\Pagers\ListConsumersPager;
32use MediaWiki\Extension\OAuth\Frontend\UIUtils;
33use MediaWiki\Linker\LinkRenderer;
34use MediaWiki\MediaWikiServices;
35use MediaWiki\Permissions\GrantsLocalization;
36use MediaWiki\SpecialPage\SpecialPage;
37use MediaWiki\User\User;
38use MediaWiki\WikiMap\WikiMap;
39use MWException;
40use OOUI\HtmlSnippet;
41use PermissionsError;
42use stdClass;
43use Wikimedia\Rdbms\IDatabase;
44use Xml;
45
46/**
47 * Special page for listing the queue of consumer requests and managing
48 * their approval/rejection and also for listing approved/disabled consumers
49 */
50class SpecialMWOAuthListConsumers extends SpecialPage {
51    /** @var GrantsLocalization */
52    private $grantsLocalization;
53
54    /**
55     * @param GrantsLocalization $grantsLocalization
56     */
57    public function __construct( GrantsLocalization $grantsLocalization ) {
58        parent::__construct( 'OAuthListConsumers' );
59        $this->grantsLocalization = $grantsLocalization;
60    }
61
62    public function execute( $par ) {
63        $this->setHeaders();
64        $this->addHelpLink( 'Help:OAuth' );
65
66        // Format is Special:OAuthListConsumers[/list|/view/[<consumer key>]]
67        $navigation = $par !== null ? explode( '/', $par ) : [];
68        $type = $navigation[0] ?? null;
69        $consumerKey = $navigation[1] ?? '';
70
71        $this->showConsumerListForm();
72
73        switch ( $type ) {
74            case 'view':
75                $this->showConsumerInfo( $consumerKey );
76                break;
77            default:
78                $this->showConsumerList();
79                break;
80        }
81
82        $this->getOutput()->addModuleStyles( 'ext.MWOAuth.styles' );
83    }
84
85    /**
86     * Show the form to approve/reject/disable/re-enable consumers
87     *
88     * @param string $consumerKey
89     * @throws PermissionsError
90     */
91    protected function showConsumerInfo( $consumerKey ) {
92        $user = $this->getUser();
93        $out = $this->getOutput();
94
95        if ( !$consumerKey ) {
96            $out->addWikiMsg( 'mwoauth-missing-consumer-key' );
97        }
98
99        $dbr = Utils::getCentralDB( DB_REPLICA );
100        $cmrAc = ConsumerAccessControl::wrap(
101            Consumer::newFromKey( $dbr, $consumerKey ), $this->getContext() );
102        $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
103
104        if ( !$cmrAc ) {
105            $out->addWikiMsg( 'mwoauth-invalid-consumer-key' );
106            return;
107        } elseif ( $cmrAc->getDeleted()
108            && !$permissionManager->userHasRight( $user, 'mwoauthviewsuppressed' ) ) {
109            throw new PermissionsError( 'mwoauthviewsuppressed' );
110        }
111
112        $grants = $cmrAc->getGrants();
113        if ( $grants === [ 'mwoauth-authonly' ] || $grants === [ 'mwoauth-authonlyprivate' ] ) {
114            $s = $this->msg( 'grant-' . $grants[0] )->plain();
115        } elseif ( $grants === [ 'basic' ] ) {
116            $s = $this->msg( 'mwoauthlistconsumers-basicgrantsonly' )->plain();
117        } else {
118            $s = $this->grantsLocalization->getGrantsWikiText( $grants, $this->getLanguage() );
119        }
120
121        $stageKey = Consumer::$stageNames[$cmrAc->getDAO()->getStage()];
122        $data = [
123            'mwoauthlistconsumers-name' => $cmrAc->getName(),
124            'mwoauthlistconsumers-version' => $cmrAc->getVersion(),
125            'mwoauth-oauth-version' => $cmrAc->getOAuthVersion() === Consumer::OAUTH_VERSION_2
126                ? $this->msg( 'mwoauth-oauth-version-2' )
127                : $this->msg( 'mwoauth-oauth-version-1' ),
128            'mwoauthlistconsumers-user' => $cmrAc->getUserName(),
129            'mwoauthlistconsumers-status' => $this->msg( "mwoauthlistconsumers-status-$stageKey" ),
130            'mwoauthlistconsumers-description' => $cmrAc->getDescription(),
131            'mwoauthlistconsumers-owner-only' => $cmrAc->getDAO()->getOwnerOnly()
132                ? $this->msg( 'htmlform-yes' ) : $this->msg( 'htmlform-no' ),
133            'mwoauthlistconsumers-wiki' => $cmrAc->getWikiName(),
134            'mwoauthlistconsumers-callbackurl' => $cmrAc->getCallbackUrl(),
135            'mwoauthlistconsumers-callbackisprefix' => $cmrAc->getCallbackIsPrefix() ?
136                $this->msg( 'htmlform-yes' ) : $this->msg( 'htmlform-no' ),
137            'mwoauthlistconsumers-grants' => new HtmlSnippet( $out->parseInlineAsInterface( $s ) ),
138        ];
139        if ( $cmrAc->getOAuthVersion() === Consumer::OAUTH_VERSION_2 ) {
140            $data += [
141                'mwoauthlistconsumers-oauth2-is-confidential' => $cmrAc->isConfidential() ?
142                    $this->msg( 'htmlform-yes' ) : $this->msg( 'htmlform-no' ),
143            ];
144        }
145
146        $out->addHTML( UIUtils::generateInfoTable( $data, $this->getContext() ) );
147
148        $rcLink = $this->getLinkRenderer()->makeKnownLink(
149            SpecialPage::getTitleFor( 'Recentchanges' ),
150            $this->msg( 'mwoauthlistconsumers-rclink' )->plain(),
151            [],
152            [ 'tagfilter' => Utils::getTagName( $cmrAc->getId() ) ]
153        );
154        $out->addHTML( "<p>$rcLink</p>" );
155
156        $this->addNavigationSubtitle( $cmrAc );
157
158        if ( Utils::isCentralWiki() ) {
159            // Show all of the status updates
160            $logPage = new LogPage( 'mwoauthconsumer' );
161            $out->addHTML( Xml::element( 'h2', null, $logPage->getName()->text() ) );
162            LogEventsList::showLogExtract( $out, 'mwoauthconsumer', '', '', [
163                'conds' => [
164                    'ls_field' => 'OAuthConsumer',
165                    'ls_value' => $cmrAc->getConsumerKey(),
166                ],
167                'flags' => LogEventsList::NO_EXTRA_USER_LINKS,
168            ] );
169        }
170    }
171
172    /**
173     * Show a form for the paged list of consumers
174     */
175    protected function showConsumerListForm() {
176        $form = HTMLForm::factory( 'ooui',
177            [
178                'name' => [
179                    'name'     => 'name',
180                    'type'     => 'text',
181                    'label-message' => 'mwoauth-consumer-name',
182                    'required' => false,
183                ],
184                'publisher' => [
185                    'name'     => 'publisher',
186                    'type'     => 'text',
187                    'label-message' => 'mwoauth-consumer-user',
188                    'required' => false
189                ],
190                'stage' => [
191                    'name'     => 'stage',
192                    'type'     => 'select',
193                    'label-message' => 'mwoauth-consumer-stage',
194                    'options'  => [
195                        $this->msg( 'mwoauth-consumer-stage-any' )->escaped() => -1,
196                        $this->msg( 'mwoauth-consumer-stage-proposed' )->escaped()
197                            => Consumer::STAGE_PROPOSED,
198                        $this->msg( 'mwoauth-consumer-stage-approved' )->escaped()
199                            => Consumer::STAGE_APPROVED,
200                        $this->msg( 'mwoauth-consumer-stage-rejected' )->escaped()
201                            => Consumer::STAGE_REJECTED,
202                        $this->msg( 'mwoauth-consumer-stage-disabled' )->escaped()
203                            => Consumer::STAGE_DISABLED,
204                        $this->msg( 'mwoauth-consumer-stage-expired' )->escaped()
205                            => Consumer::STAGE_EXPIRED
206                    ],
207                    'default'  => Consumer::STAGE_APPROVED,
208                    'required' => false
209                ]
210            ],
211            $this->getContext()
212        );
213        // always go back to listings
214        $form->setAction( $this->getPageTitle()->getFullURL() );
215        $form->setSubmitCallback( static function () {
216            return false;
217        } );
218        $form->setMethod( 'get' );
219        $form->setSubmitTextMsg( 'go' );
220        $form->setWrapperLegendMsg( 'mwoauthlistconsumers-legend' );
221        $form->show();
222    }
223
224    /**
225     * Show a paged list of consumers with links to details
226     */
227    protected function showConsumerList() {
228        $request = $this->getRequest();
229
230        $name = $request->getVal( 'name', '' );
231        $stage = $request->getInt( 'stage', Consumer::STAGE_APPROVED );
232        if ( $request->getVal( 'publisher', '' ) !== '' ) {
233            $centralId = Utils::getCentralIdFromUserName( $request->getVal( 'publisher' ) );
234        } else {
235            $centralId = null;
236        }
237
238        $pager = new ListConsumersPager( $this, [], $name, $centralId, $stage );
239        if ( $pager->getNumRows() ) {
240            $this->getOutput()->addHTML( $pager->getNavigationBar() );
241            $this->getOutput()->addHTML( $pager->getBody() );
242            $this->getOutput()->addHTML( $pager->getNavigationBar() );
243        } else {
244            // Messages: mwoauthlistconsumers-none-proposed, mwoauthlistconsumers-none-rejected,
245            // mwoauthlistconsumers-none-expired, mwoauthlistconsumers-none-approved,
246            // mwoauthlistconsumers-none-disabled
247            $this->getOutput()->addWikiMsg( "mwoauthlistconsumers-none" );
248        }
249        # Every 30th view, prune old deleted items
250        if ( mt_rand( 0, 29 ) == 0 ) {
251            Utils::runAutoMaintenance( Utils::getCentralDB( DB_PRIMARY ) );
252        }
253    }
254
255    /**
256     * @param IDatabase $db
257     * @param stdClass $row
258     * @return string
259     */
260    public function formatRow( IDatabase $db, $row ) {
261        $cmrAc = ConsumerAccessControl::wrap(
262            Consumer::newFromRow( $db, $row ), $this->getContext() );
263        $cmrKey = $cmrAc->getConsumerKey();
264        $stageKey = Consumer::$stageNames[$cmrAc->getStage()];
265        $permMgr = MediaWikiServices::getInstance()->getPermissionManager();
266
267        $links = [];
268        $links[] = $this->getLinkRenderer()->makeKnownLink(
269            $this->getPageTitle( "view/{$cmrKey}" ),
270            $this->msg( 'mwoauthlistconsumers-view' )->text(),
271            [],
272            $this->getRequest()->getValues( 'name', 'publisher', 'stage' )
273        );
274        if ( $permMgr->userHasRight( $this->getUser(), 'mwoauthmanageconsumer' ) ) {
275            $links[] = $this->getLinkRenderer()->makeKnownLink(
276                SpecialPage::getTitleFor( 'OAuthManageConsumers', $cmrKey ),
277                $this->msg( 'mwoauthmanageconsumers-review' )->text()
278            );
279        }
280        $links = $this->getLanguage()->pipeList( $links );
281
282        $encStageKey = htmlspecialchars( $stageKey );
283        $r = "<li class=\"mw-mwoauthlistconsumers-{$encStageKey}\">";
284
285        $name = $cmrAc->getNameAndVersion();
286        $r .= '<strong>' . $cmrAc->escapeForHtml( $name ) . '</strong> ' . $this->msg( 'parentheses' )
287                ->rawParams( "<strong>{$links}</strong>" )->escaped();
288
289        $lang = $this->getLanguage();
290        $data = [
291            'mwoauth-oauth-version' => $cmrAc->escapeForHtml(
292                $cmrAc->getOAuthVersion() === Consumer::OAUTH_VERSION_2
293                    ? $this->msg( 'mwoauth-oauth-version-2' )
294                    : $this->msg( 'mwoauth-oauth-version-1' )
295            ),
296            'mwoauthlistconsumers-user' => $cmrAc->escapeForHtml( $cmrAc->getUserName() ),
297            'mwoauthlistconsumers-description' => $cmrAc->escapeForHtml(
298                $cmrAc->get( 'description', static function ( $s ) use ( $lang ) {
299                    return $lang->truncateForVisual( $s, 10024 );
300                } )
301            ),
302            'mwoauthlistconsumers-wiki' => $cmrAc->escapeForHtml( $cmrAc->getWikiName() ),
303            'mwoauthlistconsumers-status' =>
304                $this->msg( "mwoauthlistconsumers-status-$stageKey" )->escaped(),
305        ];
306        if ( $cmrAc->getDAO()->getOwnerOnly() ) {
307            $data = wfArrayInsertAfter( $data, [
308                'mwoauthlistconsumers-owner-only' => $this->msg( 'htmlform-yes' ),
309            ], 'mwoauthlistconsumers-description' );
310        }
311
312        foreach ( $data as $msg => $encValue ) {
313            $r .= '<p>' . $this->msg( $msg )->escaped() . ': ' . $encValue . '</p>';
314        }
315
316        $rcLink = $this->getLinkRenderer()->makeKnownLink(
317            SpecialPage::getTitleFor( 'Recentchanges' ),
318            $this->msg( 'mwoauthlistconsumers-rclink' )->plain(),
319            [],
320            [ 'tagfilter' => Utils::getTagName( $cmrAc->getId() ) ]
321        );
322        $r .= '<p>' . $rcLink . '</p>';
323
324        $r .= '</li>';
325
326        return $r;
327    }
328
329    protected function getGroupName() {
330        return 'users';
331    }
332
333    /**
334     * @param ConsumerAccessControl $cmrAc
335     * @throws MWException
336     */
337    private function addNavigationSubtitle( ConsumerAccessControl $cmrAc ): void {
338        $user = $this->getUser();
339        $centralUserId = Utils::getCentralIdFromLocalUser( $user );
340        $linkRenderer = $this->getLinkRenderer();
341        $consumer = $cmrAc->getDAO();
342
343        $siteLinks = array_merge(
344            $this->updateLink( $cmrAc, $centralUserId, $linkRenderer ),
345            $this->manageConsumerLink( $consumer, $user, $linkRenderer ),
346            $this->manageMyGrantsLink( $consumer, $centralUserId, $linkRenderer )
347        );
348
349        if ( $siteLinks ) {
350            $links = $this->getLanguage()->pipeList( $siteLinks );
351            $this->getOutput()->setSubtitle(
352                "<strong>" . $this->msg( 'mwoauthlistconsumers-navigation' )->escaped() .
353                "</strong> [{$links}]" );
354        }
355    }
356
357    /**
358     * @param ConsumerAccessControl $cmrAc
359     * @param int $centralUserId Add update link for this user id, if they can update the consumer
360     * @param LinkRenderer $linkRenderer
361     * @return string[]
362     * @throws MWException
363     */
364    private function updateLink(
365        ConsumerAccessControl $cmrAc, $centralUserId,
366        LinkRenderer $linkRenderer
367    ): array {
368        if ( Utils::isCentralWiki() && $cmrAc->getDAO()->getUserId() === $centralUserId ) {
369            return [
370                $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'OAuthConsumerRegistration',
371                    'update/' . $cmrAc->getDAO()->getConsumerKey() ),
372                    $this->msg( 'mwoauthlistconsumers-update-link' )->text() )
373            ];
374        }
375
376        return [];
377    }
378
379    /**
380     * @param Consumer $consumer
381     * @param User $user
382     * @param LinkRenderer $linkRenderer
383     * @return string[]
384     * @throws MWException
385     */
386    private function manageConsumerLink(
387        Consumer $consumer, User $user, LinkRenderer $linkRenderer
388    ): array {
389        $permMgr = MediaWikiServices::getInstance()->getPermissionManager();
390
391        if ( Utils::isCentralWiki() && $permMgr->userHasRight( $user, 'mwoauthmanageconsumer' ) ) {
392            return [
393                $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'OAuthManageConsumers',
394                    $consumer->getConsumerKey() ),
395                    $this->msg( 'mwoauthlistconsumers-manage-link' )->text() )
396            ];
397        }
398
399        return [];
400    }
401
402    /**
403     * @param Consumer $consumer
404     * @param int $centralUserId Add link to manage grants for this user, if they've granted this
405     * consumer
406     * @param LinkRenderer $linkRenderer
407     * @return string[]
408     * @throws MWException
409     */
410    private function manageMyGrantsLink(
411        Consumer $consumer, $centralUserId, LinkRenderer $linkRenderer
412    ): array {
413        $acceptance = $this->userGrantedAcceptance( $consumer, $centralUserId );
414        if ( $acceptance !== false ) {
415            return [
416                $linkRenderer->makeKnownLink( SpecialPage::getTitleFor( 'OAuthManageMyGrants',
417                    'update/' . $acceptance->getId() ),
418                    $this->msg( 'mwoauthlistconsumers-grants-link' )->text() )
419            ];
420        }
421
422        return [];
423    }
424
425    /**
426     * @param Consumer $consumer
427     * @param int $centralUserId UserId to retrieve the grants for
428     * @return bool|ConsumerAcceptance
429     */
430    private function userGrantedAcceptance( Consumer $consumer, $centralUserId ) {
431        $dbr = Utils::getCentralDB( DB_REPLICA );
432        $wikiSpecificGrant =
433            ConsumerAcceptance::newFromUserConsumerWiki(
434                $dbr, $centralUserId, $consumer, WikiMap::getCurrentWikiId() );
435
436        $allWikiGrant = ConsumerAcceptance::newFromUserConsumerWiki(
437            $dbr, $centralUserId, $consumer, '*' );
438
439        if ( $wikiSpecificGrant !== false ) {
440            return $wikiSpecificGrant;
441        }
442        if ( $allWikiGrant !== false ) {
443            return $allWikiGrant;
444        }
445        return false;
446    }
447}