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 ChangeTags;
28use MediaWiki\Api\ApiBase;
29use MediaWiki\Api\ApiMain;
30use MediaWiki\Api\ApiResult;
31use MediaWiki\Extension\CentralAuth\GlobalGroup\GlobalGroupLookup;
32use MediaWiki\Extension\CentralAuth\Special\SpecialGlobalGroupMembership;
33use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
34use MediaWiki\ParamValidator\TypeDef\UserDef;
35use MediaWiki\Specials\SpecialUserRights;
36use MediaWiki\Title\TitleFactory;
37use MediaWiki\User\UserNamePrefixSearch;
38use MediaWiki\User\UserNameUtils;
39use Wikimedia\ParamValidator\ParamValidator;
40use Wikimedia\Rdbms\IDBAccessObject;
41
42/**
43 * @ingroup API
44 */
45class ApiGlobalUserRights extends ApiBase {
46
47    private ?CentralAuthUser $user = null;
48
49    private TitleFactory $titleFactory;
50    private UserNamePrefixSearch $userNamePrefixSearch;
51    private UserNameUtils $userNameUtils;
52    private GlobalGroupLookup $globalGroupLookup;
53
54    public function __construct(
55        ApiMain $mainModule,
56        string $moduleName,
57        TitleFactory $titleFactory,
58        UserNamePrefixSearch $userNamePrefixSearch,
59        UserNameUtils $userNameUtils,
60        GlobalGroupLookup $globalGroupLookup
61    ) {
62        parent::__construct( $mainModule, $moduleName );
63        $this->titleFactory = $titleFactory;
64        $this->userNamePrefixSearch = $userNamePrefixSearch;
65        $this->userNameUtils = $userNameUtils;
66        $this->globalGroupLookup = $globalGroupLookup;
67    }
68
69    private function getUserRightsPage(): SpecialGlobalGroupMembership {
70        return new SpecialGlobalGroupMembership(
71            $this->titleFactory,
72            $this->userNamePrefixSearch,
73            $this->userNameUtils,
74            $this->globalGroupLookup
75        );
76    }
77
78    public function execute() {
79        $pUser = $this->getUser();
80        // Deny if the user is blocked and doesn't have the full 'userrights' permission.
81        // This matches what Special:UserRights does for the web UI.
82        if ( !$this->getAuthority()->isAllowed( 'userrights' ) ) {
83            $block = $pUser->getBlock( IDBAccessObject::READ_LATEST );
84            if ( $block && $block->isSitewide() ) {
85                $this->dieBlocked( $block );
86            }
87        }
88        $params = $this->extractRequestParams();
89        $expiry = (array)$params['expiry'];
90        $add = (array)$params['add'];
91        if ( !$add ) {
92            $expiry = [];
93        } elseif ( count( $expiry ) !== count( $add ) ) {
94            if ( count( $expiry ) === 1 ) {
95                $expiry = array_fill( 0, count( $add ), $expiry[0] );
96            } else {
97                $this->dieWithError( [
98                    'apierror-toofewexpiries',
99                    count( $expiry ),
100                    count( $add )
101                ] );
102            }
103        }
104        // Validate the expiries
105        $groupExpiries = [];
106        foreach ( $expiry as $index => $expiryValue ) {
107            $group = $add[$index];
108            $groupExpiries[$group] = SpecialUserRights::expiryToTimestamp( $expiryValue );
109            if ( $groupExpiries[$group] === false ) {
110                $this->dieWithError( [ 'apierror-invalidexpiry', wfEscapeWikiText( $expiryValue ) ] );
111            }
112            // not allowed to have things expiring in the past
113            if ( $groupExpiries[$group] && $groupExpiries[$group] < wfTimestampNow() ) {
114                $this->dieWithError( [ 'apierror-pastexpiry', wfEscapeWikiText( $expiryValue ) ] );
115            }
116        }
117
118        $user = $this->getCentralAuthUser( $params );
119
120        $tags = $params['tags'];
121        // Check if user can add tags
122        if ( $tags !== null ) {
123            $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getAuthority() );
124            if ( !$ableToTag->isOK() ) {
125                $this->dieStatus( $ableToTag );
126            }
127        }
128        $form = $this->getUserRightsPage();
129        $form->setContext( $this->getContext() );
130        $r = [];
131        $r['user'] = $user->getName();
132        $r['userid'] = $user->getId();
133        [ $r['added'], $r['removed'] ] = $form->doSaveUserGroups(
134            // Don't pass null to doSaveUserGroups() for array params, cast to empty array
135            $user, $add, (array)$params['remove'],
136            $params['reason'], (array)$tags, $groupExpiries
137        );
138        $result = $this->getResult();
139        ApiResult::setIndexedTagName( $r['added'], 'group' );
140        ApiResult::setIndexedTagName( $r['removed'], 'group' );
141        $result->addValue( null, $this->getModuleName(), $r );
142    }
143
144    /**
145     * @param array $params
146     * @return CentralAuthUser
147     */
148    private function getCentralAuthUser( array $params ): CentralAuthUser {
149        if ( $this->user !== null ) {
150            return $this->user;
151        }
152
153        $this->requireOnlyOneParameter( $params, 'user', 'userid' );
154        $user = $params['user'] ?? '#' . $params['userid'];
155        $form = $this->getUserRightsPage();
156        $form->setContext( $this->getContext() );
157        $status = $form->fetchUser( $user );
158        if ( !$status->isOK() ) {
159            $this->dieStatus( $status );
160        }
161
162        $this->user = $status->value;
163        return $status->value;
164    }
165
166    /** @inheritDoc */
167    public function mustBePosted() {
168        return true;
169    }
170
171    /** @inheritDoc */
172    public function isWriteMode() {
173        return true;
174    }
175
176    /** @inheritDoc */
177    public function getAllowedParams( $flags = 0 ) {
178        $allGroups = $this->globalGroupLookup->getDefinedGroups();
179        if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
180            sort( $allGroups );
181        }
182        return [
183            'user' => [
184                ParamValidator::PARAM_TYPE => 'user',
185                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ],
186            ],
187            'userid' => [
188                ParamValidator::PARAM_TYPE => 'integer',
189                ParamValidator::PARAM_DEPRECATED => true,
190            ],
191            'add' => [
192                ParamValidator::PARAM_TYPE => $allGroups,
193                ParamValidator::PARAM_ISMULTI => true
194            ],
195            'expiry' => [
196                ParamValidator::PARAM_ISMULTI => true,
197                ParamValidator::PARAM_ALLOW_DUPLICATES => true,
198                ParamValidator::PARAM_DEFAULT => 'infinite',
199            ],
200            'remove' => [
201                ParamValidator::PARAM_TYPE => $allGroups,
202                ParamValidator::PARAM_ISMULTI => true
203            ],
204            'reason' => [
205                ParamValidator::PARAM_DEFAULT => ''
206            ],
207            'token' => [
208                // Standard definition automatically inserted
209                ApiBase::PARAM_HELP_MSG_APPEND => [ 'api-help-param-token-webui' ],
210            ],
211            'tags' => [
212                ParamValidator::PARAM_TYPE => 'tags',
213                ParamValidator::PARAM_ISMULTI => true
214            ],
215        ];
216    }
217
218    public function needsToken() {
219        return 'userrights';
220    }
221
222    /** @inheritDoc */
223    protected function getWebUITokenSalt( array $params ) {
224        return $this->getCentralAuthUser( $params )->getName();
225    }
226
227    /** @inheritDoc */
228    protected function getExamplesMessages() {
229        return [
230            'action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
231                => 'apihelp-globaluserrights-example-1',
232            'action=userrights&userid=123&add=bot&remove=sysop|bureaucrat&token=123ABC'
233                => 'apihelp-globaluserrights-example-2',
234        ];
235    }
236
237    /** @inheritDoc */
238    public function getHelpUrls() {
239        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:User_group_membership';
240    }
241}