Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
74.36% |
58 / 78 |
|
58.33% |
7 / 12 |
CRAP | |
0.00% |
0 / 1 |
BlockTargetFactory | |
74.36% |
58 / 78 |
|
58.33% |
7 / 12 |
57.85 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getWikiId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromString | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
7 | |||
newFromUser | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
newFromLegacyUnion | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
newFromRowRaw | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromRowRedacted | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newFromRowInternal | |
65.00% |
13 / 20 |
|
0.00% |
0 / 1 |
14.29 | |||
newAutoBlockTarget | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
newUserBlockTarget | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
newAnonIpBlockTarget | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
newRangeBlockTarget | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Block; |
4 | |
5 | use InvalidArgumentException; |
6 | use MediaWiki\Config\ServiceOptions; |
7 | use MediaWiki\DAO\WikiAwareEntity; |
8 | use MediaWiki\DAO\WikiAwareEntityTrait; |
9 | use MediaWiki\MainConfigNames; |
10 | use MediaWiki\User\UserIdentity; |
11 | use MediaWiki\User\UserIdentityLookup; |
12 | use MediaWiki\User\UserIdentityValue; |
13 | use MediaWiki\User\UserNameUtils; |
14 | use RuntimeException; |
15 | use stdClass; |
16 | use Wikimedia\IPUtils; |
17 | use Wikimedia\Rdbms\IDBAccessObject; |
18 | |
19 | /** |
20 | * Factory for BlockTarget objects |
21 | * |
22 | * @since 1.44 |
23 | */ |
24 | class BlockTargetFactory implements WikiAwareEntity { |
25 | use WikiAwareEntityTrait; |
26 | |
27 | private UserIdentityLookup $userIdentityLookup; |
28 | private UserNameUtils $userNameUtils; |
29 | |
30 | /** @var string|false */ |
31 | private $wikiId; |
32 | |
33 | /** |
34 | * @var array The range block minimum prefix lengths indexed by protocol (IPv4 or IPv6) |
35 | */ |
36 | private $rangePrefixLimits; |
37 | |
38 | /** |
39 | * @internal Only for use by ServiceWiring |
40 | */ |
41 | public const CONSTRUCTOR_OPTIONS = [ |
42 | MainConfigNames::BlockCIDRLimit, |
43 | ]; |
44 | |
45 | /** |
46 | * @param ServiceOptions $options |
47 | * @param UserIdentityLookup $userIdentityLookup |
48 | * @param UserNameUtils $userNameUtils |
49 | * @param string|false $wikiId |
50 | */ |
51 | public function __construct( |
52 | ServiceOptions $options, |
53 | UserIdentityLookup $userIdentityLookup, |
54 | UserNameUtils $userNameUtils, |
55 | /* string|false */ $wikiId = Block::LOCAL |
56 | ) { |
57 | $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
58 | $this->rangePrefixLimits = $options->get( MainConfigNames::BlockCIDRLimit ); |
59 | $this->userIdentityLookup = $userIdentityLookup; |
60 | $this->userNameUtils = $userNameUtils; |
61 | $this->wikiId = $wikiId; |
62 | } |
63 | |
64 | public function getWikiId() { |
65 | return $this->wikiId; |
66 | } |
67 | |
68 | /** |
69 | * Try to create a block target from a user input string. |
70 | * |
71 | * @param string|null $str |
72 | * @return BlockTarget|null |
73 | */ |
74 | public function newFromString( ?string $str ): ?BlockTarget { |
75 | if ( $str === null ) { |
76 | return null; |
77 | } |
78 | |
79 | $str = trim( $str ); |
80 | |
81 | if ( IPUtils::isValid( $str ) ) { |
82 | return new AnonIpBlockTarget( IPUtils::sanitizeIP( $str ), $this->wikiId ); |
83 | } elseif ( IPUtils::isValidRange( $str ) ) { |
84 | return new RangeBlockTarget( |
85 | IPUtils::sanitizeRange( $str ), |
86 | $this->rangePrefixLimits, |
87 | $this->wikiId |
88 | ); |
89 | } |
90 | |
91 | if ( preg_match( '/^#\d+$/', $str ) ) { |
92 | // Autoblock reference in the form "#12345" |
93 | return new AutoBlockTarget( |
94 | (int)substr( $str, 1 ), |
95 | $this->wikiId |
96 | ); |
97 | } |
98 | |
99 | $userFromDB = $this->userIdentityLookup->getUserIdentityByName( $str ); |
100 | if ( $userFromDB instanceof UserIdentity ) { |
101 | return new UserBlockTarget( $userFromDB ); |
102 | } |
103 | |
104 | // Wrap the invalid user in a UserIdentityValue. |
105 | // This allows validateTarget() to return a "nosuchusershort" message, |
106 | // which is needed for Special:Block. |
107 | $canonicalName = $this->userNameUtils->getCanonical( $str ); |
108 | if ( $canonicalName !== false ) { |
109 | return new UserBlockTarget( new UserIdentityValue( 0, $canonicalName ) ); |
110 | } |
111 | |
112 | return null; |
113 | } |
114 | |
115 | /** |
116 | * Create a BlockTarget from a UserIdentity, which may refer to a |
117 | * registered user, an IP address or range. |
118 | * |
119 | * @param UserIdentity $user |
120 | * @return BlockTarget |
121 | */ |
122 | public function newFromUser( UserIdentity $user ): BlockTarget { |
123 | $this->assertWiki( $user->getWikiId() ); |
124 | $name = $user->getName(); |
125 | if ( $user->getId( $this->wikiId ) !== 0 ) { |
126 | // We'll trust the caller and skip IP validity checks |
127 | return new UserBlockTarget( $user ); |
128 | } elseif ( IPUtils::isValidRange( $name ) ) { |
129 | return $this->newRangeBlockTarget( IPUtils::sanitizeRange( $name ) ); |
130 | } elseif ( IPUtils::isValid( $name ) ) { |
131 | return $this->newAnonIpBlockTarget( $name ); |
132 | } else { |
133 | return new UserBlockTarget( $user ); |
134 | } |
135 | } |
136 | |
137 | /** |
138 | * Try to create a BlockTarget from a UserIdentity|string|null, a union type |
139 | * previously used as a target by various methods. |
140 | * |
141 | * @param UserIdentity|string|null $union |
142 | * @return BlockTarget|null |
143 | */ |
144 | public function newFromLegacyUnion( $union ): ?BlockTarget { |
145 | if ( $union instanceof UserIdentity ) { |
146 | if ( IPUtils::isValid( $union->getName() ) ) { |
147 | return new AnonIpBlockTarget( $union->getName(), $this->wikiId ); |
148 | } else { |
149 | return new UserBlockTarget( $union ); |
150 | } |
151 | } elseif ( is_string( $union ) ) { |
152 | return $this->newFromString( $union ); |
153 | } else { |
154 | return null; |
155 | } |
156 | } |
157 | |
158 | /** |
159 | * Try to create a BlockTarget from a row which must contain bt_user, |
160 | * bt_address and optionally bt_user_text. |
161 | * |
162 | * bt_auto is ignored, so this is suitable for permissions and for block |
163 | * creation but not for display. |
164 | * |
165 | * @param stdClass $row |
166 | * @return BlockTarget|null |
167 | */ |
168 | public function newFromRowRaw( $row ): ?BlockTarget { |
169 | return $this->newFromRowInternal( $row, false ); |
170 | } |
171 | |
172 | /** |
173 | * Try to create a BlockTarget from a row which must contain bt_auto, |
174 | * bt_user, bt_address and bl_id, and optionally bt_user_text. |
175 | * |
176 | * If bt_auto is set, the address will be redacted to avoid disclosing it. |
177 | * The ID will be wrapped in an AutoblockTarget. |
178 | * |
179 | * @param stdClass $row |
180 | * @return BlockTarget|null |
181 | */ |
182 | public function newFromRowRedacted( $row ): ?BlockTarget { |
183 | return $this->newFromRowInternal( $row, true ); |
184 | } |
185 | |
186 | /** |
187 | * @param stdClass $row |
188 | * @param bool $redact |
189 | * @return BlockTarget|null |
190 | */ |
191 | private function newFromRowInternal( $row, $redact ): ?BlockTarget { |
192 | if ( $redact && $row->bt_auto ) { |
193 | return $this->newAutoBlockTarget( $row->bl_id ); |
194 | } elseif ( isset( $row->bt_user ) ) { |
195 | if ( isset( $row->bt_user_text ) ) { |
196 | $user = new UserIdentityValue( $row->bt_user, $row->bt_user_text, $this->wikiId ); |
197 | } else { |
198 | $user = $this->userIdentityLookup->getUserIdentityByUserId( $row->bt_user ); |
199 | if ( !$user ) { |
200 | $user = $this->userIdentityLookup->getUserIdentityByUserId( |
201 | $row->bt_user, IDBAccessObject::READ_LATEST ); |
202 | if ( !$user ) { |
203 | throw new RuntimeException( |
204 | "Unable to find name for user ID {$row->bt_user}" ); |
205 | } |
206 | } |
207 | } |
208 | return new UserBlockTarget( $user ); |
209 | } elseif ( $row->bt_address === null ) { |
210 | return null; |
211 | } elseif ( IPUtils::isValid( $row->bt_address ) ) { |
212 | return $this->newAnonIpBlockTarget( IPUtils::sanitizeIP( $row->bt_address ) ); |
213 | } elseif ( IPUtils::isValidRange( $row->bt_address ) ) { |
214 | return $this->newRangeBlockTarget( IPUtils::sanitizeRange( $row->bt_address ) ); |
215 | } else { |
216 | return null; |
217 | } |
218 | } |
219 | |
220 | /** |
221 | * Create an AutoBlockTarget for the given ID |
222 | * |
223 | * A simple constructor proxy for pre-validated input. |
224 | * |
225 | * @param int $id |
226 | * @return AutoBlockTarget |
227 | */ |
228 | public function newAutoBlockTarget( int $id ): AutoBlockTarget { |
229 | return new AutoBlockTarget( $id, $this->wikiId ); |
230 | } |
231 | |
232 | /** |
233 | * Create a UserBlockTarget for the given user. |
234 | * |
235 | * A simple constructor proxy for pre-validated input. |
236 | * |
237 | * The user must be a real registered user. Use newFromUser() to create a |
238 | * block target from a UserIdentity which may represent an IP address. |
239 | * |
240 | * @param UserIdentity $user |
241 | * @return UserBlockTarget |
242 | */ |
243 | public function newUserBlockTarget( UserIdentity $user ): UserBlockTarget { |
244 | $this->assertWiki( $user->getWikiId() ); |
245 | if ( IPUtils::isValid( $user->getName() ) ) { |
246 | throw new InvalidArgumentException( 'IP address passed to newUserBlockTarget' ); |
247 | } |
248 | return new UserBlockTarget( $user ); |
249 | } |
250 | |
251 | /** |
252 | * Create an IP block target |
253 | * |
254 | * A simple constructor proxy for pre-validated input. |
255 | * |
256 | * @param string $ip |
257 | * @return AnonIpBlockTarget |
258 | */ |
259 | public function newAnonIpBlockTarget( string $ip ): AnonIpBlockTarget { |
260 | if ( !IPUtils::isValid( $ip ) ) { |
261 | throw new InvalidArgumentException( 'Invalid IP address for block target' ); |
262 | } |
263 | return new AnonIpBlockTarget( $ip, $this->wikiId ); |
264 | } |
265 | |
266 | /** |
267 | * Create a range block target. |
268 | * |
269 | * A simple constructor proxy for pre-validated input. |
270 | * |
271 | * @param string $cidr |
272 | * @return RangeBlockTarget |
273 | */ |
274 | public function newRangeBlockTarget( string $cidr ): RangeBlockTarget { |
275 | if ( !IPUtils::isValidRange( $cidr ) ) { |
276 | throw new InvalidArgumentException( 'Invalid IP range for block target' ); |
277 | } |
278 | return new RangeBlockTarget( $cidr, $this->rangePrefixLimits, $this->wikiId ); |
279 | } |
280 | } |