Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 31
CRAP
0.00% covered (danger)
0.00%
0 / 1
GlobalRenameRequest
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 31
1722
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWiki
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNewName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReason
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRequested
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCompleted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDeleted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPerformer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getComments
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setName
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setWiki
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setNewName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 setReason
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setRequested
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setStatus
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setCompleted
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setDeleted
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPerformer
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setComments
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setType
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 exists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isPending
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 userIsGlobal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 importRow
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 toArray
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 isNameAvailable
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
90
1<?php
2/**
3 * @section LICENSE
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22namespace MediaWiki\Extension\CentralAuth\GlobalRename;
23
24use BadMethodCallException;
25use InvalidArgumentException;
26use MediaWiki\Extension\CentralAuth\CentralAuthServices;
27use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
28use MediaWiki\MediaWikiServices;
29use MediaWiki\Status\Status;
30use MediaWiki\User\UserNameUtils;
31use stdClass;
32use Wikimedia\Rdbms\DBAccessObjectUtils;
33use Wikimedia\Rdbms\IDBAccessObject;
34
35/**
36 * Data access object for global rename requests.
37 *
38 * @author Bryan Davis <bd808@wikimedia.org>
39 * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
40 */
41class GlobalRenameRequest {
42
43    private UserNameUtils $userNameUtils;
44
45    public const PENDING = 'pending';
46    public const APPROVED = 'approved';
47    public const REJECTED = 'rejected';
48
49    public const RENAME = 0;
50    public const VANISH = 1;
51
52    /** @var int|null */
53    protected $id;
54    /** @var string|null */
55    protected $name;
56    /** @var string|null */
57    protected $wiki;
58    /** @var string|null */
59    protected $newName;
60    /** @var string|null */
61    protected $reason;
62    /** @var string|null */
63    protected $requested;
64    /** @var string|null */
65    protected $status;
66    /** @var string|null */
67    protected $completed;
68    /** @var int */
69    protected $deleted = 0;
70    /** @var int|null */
71    protected $performer;
72    /** @var string|null */
73    protected $comments;
74    /** @var int|null */
75    protected $type;
76
77    /**
78     * @internal Use GlobalRenameRequestStore::newBlankRequest instead
79     * @param UserNameUtils $userNameUtils
80     */
81    public function __construct( UserNameUtils $userNameUtils ) {
82        $this->userNameUtils = $userNameUtils;
83    }
84
85    /**
86     * @return int
87     */
88    public function getId() {
89        return $this->id;
90    }
91
92    /**
93     * @return string Requesting user's name
94     */
95    public function getName() {
96        return $this->name;
97    }
98
99    /**
100     * @return string Requesting user's home wiki or null if CentralAuth user
101     */
102    public function getWiki() {
103        return $this->wiki;
104    }
105
106    /**
107     * @return string
108     */
109    public function getNewName() {
110        return $this->newName;
111    }
112
113    /**
114     * @return string User's reason for requesting rename
115     */
116    public function getReason() {
117        return $this->reason;
118    }
119
120    /**
121     * @return string MW timestamp that request was made
122     */
123    public function getRequested() {
124        return $this->requested;
125    }
126
127    /**
128     * @return string
129     */
130    public function getStatus() {
131        return $this->status;
132    }
133
134    /**
135     * @return string MW timestamp that request was processed
136     */
137    public function getCompleted() {
138        return $this->completed;
139    }
140
141    /**
142     * @return int Protection flags
143     */
144    public function getDeleted() {
145        return $this->deleted;
146    }
147
148    /**
149     * @return int CentralAuth user id of the user who processed the request
150     */
151    public function getPerformer() {
152        return $this->performer;
153    }
154
155    /**
156     * @return string
157     */
158    public function getComments() {
159        return $this->comments;
160    }
161
162    /**
163     * @return int
164     */
165    public function getType() {
166        return $this->type;
167    }
168
169    /**
170     * @param int $id
171     */
172    public function setId( int $id ) {
173        if ( $this->id !== null ) {
174            throw new BadMethodCallException( "Can't replace id when already set" );
175        }
176
177        $this->id = $id;
178    }
179
180    /**
181     * @param string $name
182     * @return GlobalRenameRequest self, for message chaining
183     */
184    public function setName( $name ) {
185        $this->name = $name;
186        return $this;
187    }
188
189    /**
190     * @param string $wiki
191     * @return GlobalRenameRequest self, for message chaining
192     */
193    public function setWiki( $wiki ) {
194        $this->wiki = $wiki;
195        return $this;
196    }
197
198    /**
199     * @param string $newName
200     * @return GlobalRenameRequest self, for message chaining
201     */
202    public function setNewName( $newName ) {
203        $canonicalName = $this->userNameUtils->getCanonical( $newName, UserNameUtils::RIGOR_CREATABLE );
204        if ( $canonicalName === false ) {
205            throw new InvalidArgumentException( "Invalid username '{$newName}'" );
206        }
207        $this->newName = $canonicalName;
208        return $this;
209    }
210
211    /**
212     * @param string $reason
213     * @return GlobalRenameRequest self, for message chaining
214     */
215    public function setReason( $reason ) {
216        $this->reason = $reason;
217        return $this;
218    }
219
220    /**
221     * @param string|null $requested MW timestamp, null for now
222     * @return GlobalRenameRequest self, for message chaining
223     */
224    public function setRequested( $requested = null ) {
225        $this->requested = $requested ?? wfTimestampNow();
226        return $this;
227    }
228
229    /**
230     * @param string $status
231     * @return GlobalRenameRequest self, for message chaining
232     */
233    public function setStatus( $status ) {
234        $this->status = $status;
235        return $this;
236    }
237
238    /**
239     * @param string|null $completed MW timestamp, null for now
240     * @return GlobalRenameRequest self, for message chaining
241     */
242    public function setCompleted( $completed = null ) {
243        $this->completed = $completed ?? wfTimestampNow();
244        return $this;
245    }
246
247    /**
248     * @param int $deleted Bitmask
249     * @return GlobalRenameRequest self, for message chaining
250     */
251    public function setDeleted( $deleted ) {
252        $this->deleted = $deleted;
253        return $this;
254    }
255
256    /**
257     * @param int $performer
258     * @return GlobalRenameRequest self, for message chaining
259     */
260    public function setPerformer( $performer ) {
261        $this->performer = $performer;
262        return $this;
263    }
264
265    /**
266     * @param string $comments
267     * @return GlobalRenameRequest self, for message chaining
268     */
269    public function setComments( $comments ) {
270        $this->comments = $comments;
271        return $this;
272    }
273
274    /**
275     * @param int $type
276     * @return GlobalRenameRequest self, for message chaining
277     */
278    public function setType( $type ) {
279        $this->type = $type;
280        return $this;
281    }
282
283    /**
284     * @return bool
285     */
286    public function exists() {
287        return $this->id !== null;
288    }
289
290    /**
291     * @return bool
292     */
293    public function isPending() {
294        return $this->status === self::PENDING;
295    }
296
297    /**
298     * @return bool
299     */
300    public function userIsGlobal() {
301        return $this->wiki === null;
302    }
303
304    /**
305     * @internal
306     * @param stdClass $row Database row
307     */
308    public function importRow( stdClass $row ) {
309        $this->id = $row->id;
310        $this->name = $row->name;
311        $this->wiki = $row->wiki;
312        $this->newName = $row->newname;
313        $this->reason = $row->reason;
314        $this->requested = wfTimestampOrNull( TS_MW, $row->requested );
315        $this->status = $row->status;
316        $this->completed = wfTimestampOrNull( TS_MW, $row->completed );
317        $this->deleted = $row->deleted;
318        $this->performer = $row->performer;
319        $this->comments = $row->comments;
320        $this->type = (int)$row->type;
321    }
322
323    /**
324     * @internal
325     * @return array array representation of the rename request
326     */
327    public function toArray(): array {
328        return [
329            'id' => $this->id,
330            'name' => $this->name,
331            'wiki' => $this->wiki,
332            'newname' => $this->newName,
333            'reason' => $this->reason,
334            'requested' => $this->requested,
335            'status' => $this->status,
336            'completed' => $this->completed,
337            'deleted' => $this->deleted,
338            'performer' => $this->performer,
339            'comments' => $this->comments,
340            'type' => $this->type,
341        ];
342    }
343
344    /**
345     * Check to see if a given username is available for use via CentralAuth.
346     *
347     * Note that this is not a definitive check. It does not include checking
348     * for AntiSpoof, TitleBlacklist or other AbortNewAccount hook blocks.
349     * Unfortunately the only canonical way to validate that an account is
350     * available is to make the account and check that it wasn't blocked by
351     * something.
352     *
353     * @param string $name
354     * @param int $flags one of IDBAccessObject::READ_* flags
355     * @return Status Canonicalized name
356     */
357    public static function isNameAvailable( string $name, int $flags = IDBAccessObject::READ_LATEST ) {
358        $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils();
359        $safe = $userNameUtils->getCanonical( $name, UserNameUtils::RIGOR_CREATABLE );
360        $status = Status::newGood( $safe );
361
362        if ( $safe === false || $safe === '' ) {
363            $status->fatal( 'globalrenamerequest-newname-err-invalid' );
364            return $status;
365        }
366
367        if ( CentralAuthServices::getGlobalRenameRequestStore()->nameHasPendingRequest( $safe, $flags ) ) {
368            $status->fatal( 'globalrenamerequest-newname-err-taken' );
369            return $status;
370        }
371
372        // New user creation checks against local wiki only using an API
373        // request, but we need to check against the central user table instead
374
375        if ( DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST ) ) {
376            $centralUser = CentralAuthUser::getPrimaryInstanceByName( $safe );
377        } else {
378            $centralUser = CentralAuthUser::getInstanceByName( $safe );
379        }
380
381        if ( $centralUser->exists() || $centralUser->listUnattached() ) {
382            $status->fatal( 'globalrenamerequest-newname-err-taken' );
383            return $status;
384        }
385
386        // Check to see if there is an active rename to the desired name.
387        $progress = $centralUser->renameInProgress();
388        if ( $progress && $safe == $progress[1] ) {
389            $status->fatal( 'globalrenamerequest-newname-err-taken' );
390            return $status;
391        }
392
393        return $status;
394    }
395
396}