Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiSetGlobalAccountStatus
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 7
272
0.00% covered (danger)
0.00%
0 / 1
 execute
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
90
 getStateHash
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 27
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
 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
 needsToken
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Created on Oct 17, 2012
4 *
5 * CentralAuth extension
6 *
7 * Copyright (C) 2012 Alex Monk (krenair@gmail.com)
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * http://www.gnu.org/copyleft/gpl.html
23 */
24
25namespace MediaWiki\Extension\CentralAuth\Api;
26
27use ApiBase;
28use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
29use Wikimedia\ParamValidator\ParamValidator;
30
31/**
32 * API module to lock/hide a global account.
33 *
34 * @ingroup API
35 * @ingroup Extensions
36 */
37class ApiSetGlobalAccountStatus extends ApiBase {
38    /** @var array<string, int> Mapping for string hidden values to the new int "level" values. */
39    private const HIDDEN_LEVEL_MAPPING = [
40        ''           => CentralAuthUser::HIDDEN_LEVEL_NONE,
41        'lists'      => CentralAuthUser::HIDDEN_LEVEL_LISTS,
42        'suppressed' => CentralAuthUser::HIDDEN_LEVEL_SUPPRESSED,
43    ];
44
45    /** @var array<int, string> Mapping for new int "level" hidden values to the string values. */
46    private const HIDDEN_LEVEL_MAPPING_REVERSE = [
47        CentralAuthUser::HIDDEN_LEVEL_NONE       => '',
48        CentralAuthUser::HIDDEN_LEVEL_LISTS      => 'lists',
49        CentralAuthUser::HIDDEN_LEVEL_SUPPRESSED => 'suppressed',
50    ];
51
52    public function execute() {
53        // Heavily based on code from SpecialCentralAuth::doSubmit
54        $params = $this->extractRequestParams();
55        $this->requireAtLeastOneParameter( $params, 'locked', 'hidden' );
56
57        $this->checkUserRightsAny( 'centralauth-lock' );
58
59        $globalUser = CentralAuthUser::getPrimaryInstanceByName( $params['user'] );
60        if ( !$globalUser->exists() ||
61            ( $globalUser->isSuppressed() && !$this->getAuthority()->isAllowed( 'centralauth-suppress' ) )
62        ) {
63            $this->dieWithError(
64                [ 'nosuchusershort', wfEscapeWikiText( $globalUser->getName() ) ], 'nosuchuser'
65            );
66        }
67
68        if ( !$params['locked'] ) {
69            // Don't lock or unlock
70            $setLocked = null;
71        } else {
72            $setLocked = $params['locked'] === 'lock';
73        }
74
75        if ( $params['hidden'] == null ) {
76            $setHidden = null;
77        } else {
78            $setHidden = self::HIDDEN_LEVEL_MAPPING[$params['hidden']];
79        }
80
81        $reason = $params['reason'];
82        $stateCheck = $params['statecheck'];
83
84        if ( $stateCheck && $stateCheck !== $this->getStateHash( $globalUser ) ) {
85            $this->dieWithError( 'apierror-centralauth-editconflict', 'editconflict' );
86        }
87
88        $status = $globalUser->adminLockHide(
89            $setLocked,
90            $setHidden,
91            $reason,
92            $this->getContext()
93        );
94
95        $hidden = self::HIDDEN_LEVEL_MAPPING_REVERSE[$globalUser->getHiddenLevelInt()];
96
97        // Logging etc
98        if ( $status->isGood() ) {
99            $this->getResult()->addValue( null, $this->getModuleName(), [
100                'user' => $globalUser->getName(),
101                'locked' => $globalUser->isLocked(),
102                'hidden' => $hidden,
103                'reason' => $reason
104            ] );
105        } else {
106            $error = $this->getErrorFormatter()->arrayFromStatus( $status );
107            $this->getResult()->addValue( 'error', null, $error );
108            $this->getResult()->addValue( null, $this->getModuleName(), [
109                'user' => $globalUser->getName(),
110                'locked' => $globalUser->isLocked(),
111                'hidden' => $hidden,
112            ] );
113        }
114    }
115
116    /**
117     * Calculates a state hash for edit conflict detection. This is separate
118     * from {@link CentralAuthUser::getStateHash()} as it uses the 'old'
119     * (non-normalized string) values for the hidden level value.
120     *
121     * @param CentralAuthUser $user
122     * @return string
123     */
124    private function getStateHash( CentralAuthUser $user ): string {
125        $parts = [
126            (string)$user->getId(),
127            $user->getName(),
128            self::HIDDEN_LEVEL_MAPPING_REVERSE[$user->getHiddenLevelInt()],
129            $user->isLocked() ? '1' : '0'
130        ];
131
132        return md5( implode( ':', $parts ) );
133    }
134
135    /** @inheritDoc */
136    public function getAllowedParams() {
137        return [
138            'user' => [
139                ParamValidator::PARAM_TYPE => 'string',
140                ParamValidator::PARAM_REQUIRED => true
141            ],
142            'locked' => [
143                ParamValidator::PARAM_TYPE => [
144                    'lock',
145                    'unlock',
146                    // Unset - basically means 'do not modify lock status'
147                    ''
148                ]
149            ],
150            'hidden' => [
151                ParamValidator::PARAM_TYPE => [
152                    '',
153                    'lists',
154                    'suppressed'
155                ]
156            ],
157            'reason' => [
158                ParamValidator::PARAM_TYPE => 'string'
159            ],
160            'statecheck' => [
161                ParamValidator::PARAM_TYPE => 'string',
162                ParamValidator::PARAM_REQUIRED => false
163            ],
164        ];
165    }
166
167    /** @inheritDoc */
168    protected function getExamplesMessages() {
169        return [
170            'action=setglobalaccountstatus&user=Example&locked=lock&hidden=&reason=Spam'
171                => 'apihelp-setglobalaccountstatus-example-1',
172            'action=setglobalaccountstatus&user=Example&locked=unlock&hidden=suppressed&reason=I%20can'
173                => 'apihelp-setglobalaccountstatus-example-2',
174        ];
175    }
176
177    /** @inheritDoc */
178    public function mustBePosted() {
179        return true;
180    }
181
182    /** @inheritDoc */
183    public function isWriteMode() {
184        return true;
185    }
186
187    /** @inheritDoc */
188    public function needsToken() {
189        return 'setglobalaccountstatus';
190    }
191}