Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 114
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiGlobalUserRights
0.00% covered (danger)
0.00%
0 / 114
0.00% covered (danger)
0.00%
0 / 11
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getUserRightsPage
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
182
 getCentralAuthUser
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 mustBePosted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isWriteMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
6
 needsToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWebUITokenSalt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Created on 1st October, 2014
4 *
5 * Copyright © 2014 Alex Monk <krenair@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 *
22 * @file
23 */
24
25namespace MediaWiki\Extension\CentralAuth\Api;
26
27use ApiBase;
28use ApiMain;
29use ApiResult;
30use ChangeTags;
31use IDBAccessObject;
32use MediaWiki\Extension\CentralAuth\GlobalGroup\GlobalGroupLookup;
33use MediaWiki\Extension\CentralAuth\Special\SpecialGlobalGroupMembership;
34use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
35use MediaWiki\ParamValidator\TypeDef\UserDef;
36use MediaWiki\Specials\SpecialUserRights;
37use MediaWiki\Title\TitleFactory;
38use MediaWiki\User\UserNamePrefixSearch;
39use MediaWiki\User\UserNameUtils;
40use Wikimedia\ParamValidator\ParamValidator;
41
42/**
43 * @ingroup API
44 */
45class ApiGlobalUserRights extends ApiBase {
46    private ?CentralAuthUser $user = null;
47
48    private TitleFactory $titleFactory;
49
50    /** @var UserNamePrefixSearch */
51    private $userNamePrefixSearch;
52
53    /** @var UserNameUtils */
54    private $userNameUtils;
55
56    /** @var GlobalGroupLookup */
57    private $globalGroupLookup;
58
59    /**
60     * @param ApiMain $mainModule
61     * @param string $moduleName
62     * @param TitleFactory $titleFactory
63     * @param UserNamePrefixSearch $userNamePrefixSearch
64     * @param UserNameUtils $userNameUtils
65     * @param GlobalGroupLookup $globalGroupLookup
66     */
67    public function __construct(
68        ApiMain $mainModule,
69        $moduleName,
70        TitleFactory $titleFactory,
71        UserNamePrefixSearch $userNamePrefixSearch,
72        UserNameUtils $userNameUtils,
73        GlobalGroupLookup $globalGroupLookup
74    ) {
75        parent::__construct( $mainModule, $moduleName );
76        $this->titleFactory = $titleFactory;
77        $this->userNamePrefixSearch = $userNamePrefixSearch;
78        $this->userNameUtils = $userNameUtils;
79        $this->globalGroupLookup = $globalGroupLookup;
80    }
81
82    private function getUserRightsPage(): SpecialGlobalGroupMembership {
83        return new SpecialGlobalGroupMembership(
84            $this->titleFactory,
85            $this->userNamePrefixSearch,
86            $this->userNameUtils,
87            $this->globalGroupLookup
88        );
89    }
90
91    public function execute() {
92        $pUser = $this->getUser();
93        // Deny if the user is blocked and doesn't have the full 'userrights' permission.
94        // This matches what Special:UserRights does for the web UI.
95        if ( !$this->getAuthority()->isAllowed( 'userrights' ) ) {
96            $block = $pUser->getBlock( IDBAccessObject::READ_LATEST );
97            if ( $block && $block->isSitewide() ) {
98                $this->dieBlocked( $block );
99            }
100        }
101        $params = $this->extractRequestParams();
102        $expiry = (array)$params['expiry'];
103        $add = (array)$params['add'];
104        if ( !$add ) {
105            $expiry = [];
106        } elseif ( count( $expiry ) !== count( $add ) ) {
107            if ( count( $expiry ) === 1 ) {
108                $expiry = array_fill( 0, count( $add ), $expiry[0] );
109            } else {
110                $this->dieWithError( [
111                    'apierror-toofewexpiries',
112                    count( $expiry ),
113                    count( $add )
114                ] );
115            }
116        }
117        // Validate the expiries
118        $groupExpiries = [];
119        foreach ( $expiry as $index => $expiryValue ) {
120            $group = $add[$index];
121            $groupExpiries[$group] = SpecialUserRights::expiryToTimestamp( $expiryValue );
122            if ( $groupExpiries[$group] === false ) {
123                $this->dieWithError( [ 'apierror-invalidexpiry', wfEscapeWikiText( $expiryValue ) ] );
124            }
125            // not allowed to have things expiring in the past
126            if ( $groupExpiries[$group] && $groupExpiries[$group] < wfTimestampNow() ) {
127                $this->dieWithError( [ 'apierror-pastexpiry', wfEscapeWikiText( $expiryValue ) ] );
128            }
129        }
130
131        $user = $this->getCentralAuthUser( $params );
132
133        $tags = $params['tags'];
134        // Check if user can add tags
135        if ( $tags !== null ) {
136            $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getAuthority() );
137            if ( !$ableToTag->isOK() ) {
138                $this->dieStatus( $ableToTag );
139            }
140        }
141        $form = $this->getUserRightsPage();
142        $form->setContext( $this->getContext() );
143        $r = [];
144        $r['user'] = $user->getName();
145        $r['userid'] = $user->getId();
146        [ $r['added'], $r['removed'] ] = $form->doSaveUserGroups(
147            // Don't pass null to doSaveUserGroups() for array params, cast to empty array
148            $user, $add, (array)$params['remove'],
149            $params['reason'], (array)$tags, $groupExpiries
150        );
151        $result = $this->getResult();
152        ApiResult::setIndexedTagName( $r['added'], 'group' );
153        ApiResult::setIndexedTagName( $r['removed'], 'group' );
154        $result->addValue( null, $this->getModuleName(), $r );
155    }
156
157    /**
158     * @param array $params
159     * @return CentralAuthUser
160     */
161    private function getCentralAuthUser( array $params ): CentralAuthUser {
162        if ( $this->user !== null ) {
163            return $this->user;
164        }
165
166        $this->requireOnlyOneParameter( $params, 'user', 'userid' );
167        $user = $params['user'] ?? '#' . $params['userid'];
168        $form = $this->getUserRightsPage();
169        $form->setContext( $this->getContext() );
170        $status = $form->fetchUser( $user );
171        if ( !$status->isOK() ) {
172            $this->dieStatus( $status );
173        }
174
175        $this->user = $status->value;
176        return $status->value;
177    }
178
179    /** @inheritDoc */
180    public function mustBePosted() {
181        return true;
182    }
183
184    /** @inheritDoc */
185    public function isWriteMode() {
186        return true;
187    }
188
189    /** @inheritDoc */
190    public function getAllowedParams( $flags = 0 ) {
191        $allGroups = $this->globalGroupLookup->getDefinedGroups();
192        if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
193            sort( $allGroups );
194        }
195        return [
196            'user' => [
197                ParamValidator::PARAM_TYPE => 'user',
198                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ],
199            ],
200            'userid' => [
201                ParamValidator::PARAM_TYPE => 'integer',
202                ParamValidator::PARAM_DEPRECATED => true,
203            ],
204            'add' => [
205                ParamValidator::PARAM_TYPE => $allGroups,
206                ParamValidator::PARAM_ISMULTI => true
207            ],
208            'expiry' => [
209                ParamValidator::PARAM_ISMULTI => true,
210                ParamValidator::PARAM_ALLOW_DUPLICATES => true,
211                ParamValidator::PARAM_DEFAULT => 'infinite',
212            ],
213            'remove' => [
214                ParamValidator::PARAM_TYPE => $allGroups,
215                ParamValidator::PARAM_ISMULTI => true
216            ],
217            'reason' => [
218                ParamValidator::PARAM_DEFAULT => ''
219            ],
220            'token' => [
221                // Standard definition automatically inserted
222                ApiBase::PARAM_HELP_MSG_APPEND => [ 'api-help-param-token-webui' ],
223            ],
224            'tags' => [
225                ParamValidator::PARAM_TYPE => 'tags',
226                ParamValidator::PARAM_ISMULTI => true
227            ],
228        ];
229    }
230
231    public function needsToken() {
232        return 'userrights';
233    }
234
235    /** @inheritDoc */
236    protected function getWebUITokenSalt( array $params ) {
237        return $this->getCentralAuthUser( $params )->getName();
238    }
239
240    /** @inheritDoc */
241    protected function getExamplesMessages() {
242        return [
243            'action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
244                => 'apihelp-globaluserrights-example-1',
245            'action=userrights&userid=123&add=bot&remove=sysop|bureaucrat&token=123ABC'
246                => 'apihelp-globaluserrights-example-2',
247        ];
248    }
249
250    /** @inheritDoc */
251    public function getHelpUrls() {
252        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:User_group_membership';
253    }
254}