Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.99% covered (warning)
86.99%
107 / 123
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiUnblock
87.70% covered (warning)
87.70%
107 / 122
50.00% covered (danger)
50.00%
4 / 8
25.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 execute
98.59% covered (success)
98.59%
70 / 71
0.00% covered (danger)
0.00%
0 / 1
16
 mustBePosted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isWriteMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedParams
75.86% covered (warning)
75.86%
22 / 29
0.00% covered (danger)
0.00%
0 / 1
2.06
 needsToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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 * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com>
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use MediaWiki\Block\Block;
12use MediaWiki\Block\BlockPermissionCheckerFactory;
13use MediaWiki\Block\BlockTargetFactory;
14use MediaWiki\Block\DatabaseBlockStore;
15use MediaWiki\Block\UnblockUserFactory;
16use MediaWiki\MainConfigNames;
17use MediaWiki\ParamValidator\TypeDef\UserDef;
18use MediaWiki\Title\Title;
19use MediaWiki\User\Options\UserOptionsLookup;
20use MediaWiki\User\UserIdentityLookup;
21use MediaWiki\Watchlist\WatchedItemStoreInterface;
22use MediaWiki\Watchlist\WatchlistManager;
23use RuntimeException;
24use Wikimedia\ParamValidator\ParamValidator;
25use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
26
27/**
28 * API module that facilitates the unblocking of users. Requires API write mode
29 * to be enabled.
30 *
31 * @ingroup API
32 */
33class ApiUnblock extends ApiBase {
34
35    use ApiBlockInfoTrait;
36    use ApiWatchlistTrait;
37
38    private BlockPermissionCheckerFactory $permissionCheckerFactory;
39    private UnblockUserFactory $unblockUserFactory;
40    private UserIdentityLookup $userIdentityLookup;
41    private WatchedItemStoreInterface $watchedItemStore;
42    private DatabaseBlockStore $blockStore;
43    private BlockTargetFactory $blockTargetFactory;
44
45    public function __construct(
46        ApiMain $main,
47        string $action,
48        BlockPermissionCheckerFactory $permissionCheckerFactory,
49        UnblockUserFactory $unblockUserFactory,
50        UserIdentityLookup $userIdentityLookup,
51        WatchedItemStoreInterface $watchedItemStore,
52        WatchlistManager $watchlistManager,
53        UserOptionsLookup $userOptionsLookup,
54        DatabaseBlockStore $blockStore,
55        BlockTargetFactory $blockTargetFactory
56    ) {
57        parent::__construct( $main, $action );
58
59        $this->permissionCheckerFactory = $permissionCheckerFactory;
60        $this->unblockUserFactory = $unblockUserFactory;
61        $this->userIdentityLookup = $userIdentityLookup;
62        $this->watchedItemStore = $watchedItemStore;
63
64        // Variables needed in ApiWatchlistTrait trait
65        $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
66        $this->watchlistMaxDuration =
67            $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration );
68        $this->watchlistManager = $watchlistManager;
69        $this->userOptionsLookup = $userOptionsLookup;
70        $this->blockStore = $blockStore;
71        $this->blockTargetFactory = $blockTargetFactory;
72    }
73
74    /**
75     * Unblocks the specified user or provides the reason the unblock failed.
76     */
77    public function execute() {
78        $performer = $this->getUser();
79        $params = $this->extractRequestParams();
80
81        $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' );
82
83        if ( !$this->getAuthority()->isAllowed( 'block' ) ) {
84            $this->dieWithError( 'apierror-permissiondenied-unblock', 'permissiondenied' );
85        }
86
87        if ( $params['userid'] !== null ) {
88            $identity = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] );
89            if ( !$identity ) {
90                $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' );
91            }
92            $params['user'] = $identity;
93        }
94
95        $blockToRemove = null;
96        if ( $params['id'] !== null ) {
97            $blockToRemove = $this->blockStore->newFromID( $params['id'], true );
98            if ( !$blockToRemove ) {
99                $this->dieWithError(
100                    [ 'apierror-nosuchblockid', $params['id'] ],
101                    'nosuchblockid' );
102            }
103            $target = $blockToRemove->getRedactedTarget();
104            if ( !$target ) {
105                throw new RuntimeException( 'Block has no target' );
106            }
107        } else {
108            $target = $this->blockTargetFactory->newFromUser( $params['user'] );
109        }
110
111        # T17810: blocked admins should have limited access here
112        $status = $this->permissionCheckerFactory
113            ->newChecker(
114                $this->getAuthority()
115            )->checkBlockPermissions( $target );
116
117        if ( $status !== true ) {
118            $this->dieWithError(
119                $status,
120                null,
121                // @phan-suppress-next-line PhanTypeMismatchArgumentNullable Block is checked and not null
122                [ 'blockinfo' => $this->getBlockDetails( $performer->getBlock() ) ]
123            );
124        }
125
126        if ( $blockToRemove !== null ) {
127            $status = $this->unblockUserFactory->newRemoveBlock(
128                $blockToRemove,
129                $this->getAuthority(),
130                $params['reason'],
131                $params['tags'] ?? []
132            )->unblock();
133        } else {
134            $status = $this->unblockUserFactory->newUnblockUser(
135                $target,
136                $this->getAuthority(),
137                $params['reason'],
138                $params['tags'] ?? []
139            )->unblock();
140        }
141
142        if ( !$status->isOK() ) {
143            $this->dieStatus( $status );
144        }
145
146        $block = $status->getValue();
147        $targetType = $block->getType();
148        $targetName = $targetType === Block::TYPE_AUTO ? '' : $block->getTargetName();
149        $targetUserId = $block->getTargetUserIdentity() ? $block->getTargetUserIdentity()->getId() : 0;
150
151        $userPage = Title::makeTitle( NS_USER, $targetName );
152        $watchlistExpiry = $this->getExpiryFromParams( $params, $userPage, $this->getUser() );
153        $watchuser = $params['watchuser'];
154        if ( $watchuser && $targetType !== Block::TYPE_RANGE && $targetType !== Block::TYPE_AUTO ) {
155            $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
156        } else {
157            $watchuser = false;
158            $watchlistExpiry = null;
159        }
160
161        $res = [
162            'id' => $block->getId(),
163            'user' => $targetName,
164            'userid' => $targetUserId,
165            'reason' => $params['reason'],
166            'watchuser' => $watchuser,
167        ];
168
169        if ( $watchlistExpiry !== null ) {
170            $res['watchlistexpiry'] = $this->getWatchlistExpiry(
171                $this->watchedItemStore,
172                $userPage,
173                $this->getUser()
174            );
175        }
176
177        $this->getResult()->addValue( null, $this->getModuleName(), $res );
178    }
179
180    /** @inheritDoc */
181    public function mustBePosted() {
182        return true;
183    }
184
185    /** @inheritDoc */
186    public function isWriteMode() {
187        return true;
188    }
189
190    /** @inheritDoc */
191    public function getAllowedParams() {
192        $params = [
193            'id' => [
194                ParamValidator::PARAM_TYPE => 'integer',
195            ],
196            'user' => [
197                ParamValidator::PARAM_TYPE => 'user',
198                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ],
199                UserDef::PARAM_RETURN_OBJECT => true,
200            ],
201            'userid' => [
202                ParamValidator::PARAM_TYPE => 'integer',
203                ParamValidator::PARAM_DEPRECATED => true,
204            ],
205            'reason' => '',
206            'tags' => [
207                ParamValidator::PARAM_TYPE => 'tags',
208                ParamValidator::PARAM_ISMULTI => true,
209            ],
210            'watchuser' => false,
211        ];
212
213        // Params appear in the docs in the order they are defined,
214        // which is why this is here and not at the bottom.
215        // @todo Find better way to support insertion at arbitrary position
216        if ( $this->watchlistExpiryEnabled ) {
217            $params += [
218                'watchlistexpiry' => [
219                    ParamValidator::PARAM_TYPE => 'expiry',
220                    ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
221                    ExpiryDef::PARAM_USE_MAX => true,
222                ]
223            ];
224        }
225
226        return $params;
227    }
228
229    /** @inheritDoc */
230    public function needsToken() {
231        return 'csrf';
232    }
233
234    /** @inheritDoc */
235    protected function getExamplesMessages() {
236        return [
237            'action=unblock&id=105'
238                => 'apihelp-unblock-example-id',
239            'action=unblock&user=Bob&reason=Sorry%20Bob'
240                => 'apihelp-unblock-example-user',
241        ];
242    }
243
244    /** @inheritDoc */
245    public function getHelpUrls() {
246        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block';
247    }
248}
249
250/** @deprecated class alias since 1.43 */
251class_alias( ApiUnblock::class, 'ApiUnblock' );