Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
1.67% |
7 / 418 |
|
8.33% |
2 / 24 |
CRAP | |
0.00% |
0 / 1 |
SpecialGlobalGroupPermissions | |
1.67% |
7 / 418 |
|
8.33% |
2 / 24 |
7618.68 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
userCanEdit | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
execute | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
56 | |||
buildMainView | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
12 | |||
getGlobalGroupsTable | |
0.00% |
0 / 58 |
|
0.00% |
0 / 1 |
42 | |||
getGroupInfo | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
buildGroupView | |
0.00% |
0 / 66 |
|
0.00% |
0 / 1 |
132 | |||
buildWikiSetSelector | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
buildCheckboxes | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
30 | |||
formatRight | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getAssignedRights | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doSubmit | |
0.00% |
0 / 83 |
|
0.00% |
0 / 1 |
600 | |||
revokeRightsFromGroup | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
grantRightsToGroup | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
showLogFragment | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
addPermissionLog | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
addRenameLog | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
addWikiSetLog | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
12 | |||
setRestrictions | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
getWikiSetName | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
invalidateRightsCache | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validateGroupName | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | # This file is part of MediaWiki. |
3 | |
4 | # MediaWiki is free software: you can redistribute it and/or modify |
5 | # it under the terms of version 2 of the GNU General Public License |
6 | # as published by the Free Software Foundation. |
7 | |
8 | # MediaWiki is distributed in the hope that it will be useful, |
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | # GNU General Public License for more details. |
12 | |
13 | namespace MediaWiki\Extension\CentralAuth\Special; |
14 | |
15 | use Exception; |
16 | use InvalidArgumentException; |
17 | use LogEventsList; |
18 | use LogPage; |
19 | use ManualLogEntry; |
20 | use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager; |
21 | use MediaWiki\Extension\CentralAuth\GlobalGroup\GlobalGroupLookup; |
22 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
23 | use MediaWiki\Extension\CentralAuth\WikiSet; |
24 | use MediaWiki\Html\Html; |
25 | use MediaWiki\MainConfigNames; |
26 | use MediaWiki\Output\OutputPage; |
27 | use MediaWiki\Permissions\PermissionManager; |
28 | use MediaWiki\SpecialPage\SpecialPage; |
29 | use MediaWiki\Status\Status; |
30 | use MediaWiki\Title\Title; |
31 | use MediaWiki\User\User; |
32 | use MediaWiki\User\UserGroupMembership; |
33 | use MediaWiki\Xml\Xml; |
34 | use MediaWiki\Xml\XmlSelect; |
35 | |
36 | /** |
37 | * Special page to allow managing global groups |
38 | * Prototype for a similar system in core. |
39 | * |
40 | * @file |
41 | * @ingroup Extensions |
42 | */ |
43 | |
44 | class SpecialGlobalGroupPermissions extends SpecialPage { |
45 | |
46 | private PermissionManager $permissionManager; |
47 | private CentralAuthDatabaseManager $databaseManager; |
48 | private GlobalGroupLookup $globalGroupLookup; |
49 | |
50 | public function __construct( |
51 | PermissionManager $permissionManager, |
52 | CentralAuthDatabaseManager $databaseManager, |
53 | GlobalGroupLookup $globalGroupLookup |
54 | ) { |
55 | parent::__construct( 'GlobalGroupPermissions' ); |
56 | $this->permissionManager = $permissionManager; |
57 | $this->databaseManager = $databaseManager; |
58 | $this->globalGroupLookup = $globalGroupLookup; |
59 | } |
60 | |
61 | public function doesWrites() { |
62 | return true; |
63 | } |
64 | |
65 | /** |
66 | * @param User $user |
67 | * @return bool |
68 | */ |
69 | public function userCanEdit( $user ) { |
70 | $globalUser = CentralAuthUser::getInstance( $user ); |
71 | |
72 | # Should be a global user |
73 | if ( !$globalUser->exists() || !$globalUser->isAttached() ) { |
74 | return false; |
75 | } |
76 | |
77 | return $user->isAllowed( 'globalgrouppermissions' ); |
78 | } |
79 | |
80 | /** @inheritDoc */ |
81 | public function execute( $subpage ) { |
82 | $this->addHelpLink( 'Extension:CentralAuth' ); |
83 | if ( !$this->userCanExecute( $this->getUser() ) ) { |
84 | $this->displayRestrictionError(); |
85 | } |
86 | |
87 | $this->getOutput()->setPageTitleMsg( $this->msg( 'globalgrouppermissions' ) ); |
88 | |
89 | $this->getOutput()->addModuleStyles( 'ext.centralauth.misc.styles' ); |
90 | $this->getOutput()->setRobotPolicy( "noindex,nofollow" ); |
91 | $this->getOutput()->setArticleRelated( false ); |
92 | $this->getOutput()->disableClientCache(); |
93 | |
94 | if ( $subpage == '' ) { |
95 | $subpage = $this->getRequest()->getVal( 'wpGroup' ); |
96 | } |
97 | |
98 | if ( |
99 | $subpage != '' |
100 | && $this->getUser()->matchEditToken( $this->getRequest()->getVal( 'wpEditToken' ) ) |
101 | && $this->getRequest()->wasPosted() |
102 | ) { |
103 | $this->doSubmit( $subpage ); |
104 | } elseif ( $subpage != '' ) { |
105 | $this->buildGroupView( $subpage ); |
106 | } else { |
107 | $this->buildMainView(); |
108 | } |
109 | } |
110 | |
111 | private function buildMainView() { |
112 | $out = $this->getOutput(); |
113 | $groups = $this->globalGroupLookup->getDefinedGroups(); |
114 | |
115 | if ( count( $groups ) ) { |
116 | $out->addHTML( |
117 | $this->msg( 'centralauth-globalgroupperms-groups-intro' )->parseAsBlock() |
118 | . $this->getGlobalGroupsTable( $groups ) |
119 | ); |
120 | } else { |
121 | $out->addWikiMsg( 'centralauth-globalgroupperms-nogroups' ); |
122 | } |
123 | |
124 | if ( $this->userCanEdit( $this->getUser() ) ) { |
125 | // "Create a group" prompt |
126 | // @todo Move this out of main view to a separate page |
127 | $html = Xml::fieldset( $this->msg( 'centralauth-newgroup-legend' )->text() ); |
128 | $html .= $this->msg( 'centralauth-newgroup-intro' )->parseAsBlock(); |
129 | $html .= Xml::openElement( 'form', [ |
130 | 'method' => 'post', |
131 | 'action' => $this->getConfig()->get( MainConfigNames::Script ), |
132 | 'name' => 'centralauth-globalgroups-newgroup' |
133 | ] ); |
134 | $html .= Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ); |
135 | |
136 | $fields = [ 'centralauth-globalgroupperms-newgroupname' => Xml::input( 'wpGroup' ) ]; |
137 | |
138 | $html .= Xml::buildForm( $fields, 'centralauth-globalgroupperms-creategroup-submit' ); |
139 | $html .= Xml::closeElement( 'form' ); |
140 | $html .= Xml::closeElement( 'fieldset' ); |
141 | |
142 | $out->addHTML( $html ); |
143 | } |
144 | } |
145 | |
146 | /** |
147 | * @param array $groups |
148 | * @return string HTML for the group permissions table |
149 | */ |
150 | protected function getGlobalGroupsTable( $groups ) { |
151 | $table = Html::openElement( 'table', |
152 | [ 'class' => 'mw-centralauth-groups-table wikitable' ] ); |
153 | |
154 | // Header stuff |
155 | $table .= Html::openElement( 'tr' ); |
156 | $table .= Html::element( 'th', [], |
157 | $this->msg( 'centralauth-globalgroupperms-group' )->text() |
158 | ); |
159 | $table .= Html::element( 'th', [], |
160 | $this->msg( 'centralauth-globalgroupperms-rights' )->text() |
161 | ); |
162 | $table .= Html::closeElement( 'tr' ); |
163 | |
164 | foreach ( $groups as $groupName ) { |
165 | $groupInfo = $this->getGroupInfo( $groupName ); |
166 | $wikiset = $groupInfo['wikiset']; |
167 | |
168 | $table .= Html::openElement( 'tr' ); |
169 | |
170 | // Column with group name, links and local disabled status |
171 | $table .= Html::openElement( 'td' ); |
172 | $table .= $this->getOutput()->parseInlineAsInterface( |
173 | UserGroupMembership::getLinkWiki( $groupName, $this->getContext() ) ) . '<br />'; |
174 | |
175 | $groupWithParentheses = $this->msg( 'parentheses' )->params( $groupName )->escaped(); |
176 | $table .= Html::openElement( 'code' ); |
177 | $table .= $groupWithParentheses; |
178 | $table .= Html::closeElement( 'code' ) . Html::element( 'br' ); |
179 | |
180 | $linkRenderer = $this->getLinkRenderer(); |
181 | $links = [ |
182 | $linkRenderer->makeKnownLink( |
183 | $this->getPageTitle( $groupName ), |
184 | $this->msg( 'centralauth-globalgroupperms-management' )->text() |
185 | ), |
186 | $linkRenderer->makeKnownLink( |
187 | SpecialPage::getTitleFor( 'GlobalUsers', $groupName ), |
188 | $this->msg( 'centralauth-globalgroupperms-group-listmembers' )->text() |
189 | ), |
190 | ]; |
191 | $table .= $this->msg( 'parentheses' ) |
192 | ->rawParams( $this->getLanguage()->pipeList( $links ) )->escaped(); |
193 | |
194 | if ( $wikiset !== null && !$wikiset['enabledHere'] ) { |
195 | $table .= '<br /><small>'; |
196 | $table .= $this->msg( 'centralauth-globalgroupperms-group-disabled' )->escaped() . |
197 | '</small>'; |
198 | } |
199 | $table .= Html::closeElement( 'td' ); |
200 | |
201 | // Column for wikiset info and group rights list |
202 | $table .= Html::openElement( 'td' ); |
203 | if ( $wikiset === null ) { |
204 | $table .= $this->msg( 'centralauth-globalgroupperms-wikiset-none' )->escaped(); |
205 | } else { |
206 | $table .= $this->msg( 'centralauth-globalgroupperms-group-wikiset' ) |
207 | ->rawParams( |
208 | $linkRenderer->makeKnownLink( |
209 | SpecialPage::getTitleFor( 'WikiSets', $wikiset['id'] ), |
210 | $wikiset['name'] |
211 | ) |
212 | )->escaped(); |
213 | } |
214 | |
215 | $table .= '<hr />'; |
216 | |
217 | $rightsList = ''; |
218 | foreach ( $groupInfo['rights'] as $right ) { |
219 | $rightsList .= Html::rawElement( 'li', [], $this->formatRight( $right ) ); |
220 | } |
221 | $table .= '<ul>' . $rightsList . '</ul>'; |
222 | $table .= Html::closeElement( 'td' ); |
223 | |
224 | $table .= Html::closeElement( 'tr' ); |
225 | } |
226 | |
227 | $table .= Html::closeElement( 'table' ); |
228 | |
229 | return $table; |
230 | } |
231 | |
232 | /** |
233 | * @param string $group The group's name |
234 | * @return array |
235 | * - rights: string The list of rights assigned to the group |
236 | * - wikiset: array|null Either array with id, name, enabledHere or |
237 | * null if the group is not associated to any wikiset |
238 | * @throws Exception |
239 | */ |
240 | protected function getGroupInfo( $group ) { |
241 | $info = [ 'rights' => $this->getAssignedRights( $group ) ]; |
242 | |
243 | $wikiset = WikiSet::getWikiSetForGroup( $group ); |
244 | if ( $wikiset !== 0 ) { |
245 | $wikiset = WikiSet::newFromID( $wikiset ); |
246 | if ( !$wikiset ) { |
247 | throw new InvalidArgumentException( "__METHOD__: $group with unknown wikiset." ); |
248 | } |
249 | $info['wikiset'] = [ |
250 | 'id' => $wikiset->getId(), |
251 | 'name' => $wikiset->getName(), |
252 | 'enabledHere' => $wikiset->inSet(), |
253 | ]; |
254 | } else { |
255 | $info['wikiset'] = null; |
256 | } |
257 | |
258 | return $info; |
259 | } |
260 | |
261 | /** |
262 | * @param string $group |
263 | */ |
264 | private function buildGroupView( $group ) { |
265 | $editable = $this->userCanEdit( $this->getUser() ); |
266 | $assignedRights = $this->getAssignedRights( $group ); |
267 | $this->getOutput()->addBacklinkSubtitle( $this->getPageTitle() ); |
268 | |
269 | if ( !$assignedRights ) { |
270 | // if the group doesn't exist and the user can not manage the global groups, |
271 | // an error message should be shown instead of the permission list box. |
272 | if ( !$editable ) { |
273 | $this->getOutput()->wrapWikiMsg( '<div class="error">$1</div>', |
274 | [ 'centralauth-editgroup-nonexistent', $group ] ); |
275 | $this->showLogFragment( $group, $this->getOutput() ); |
276 | return; |
277 | } |
278 | |
279 | $nameValidationResult = $this->validateGroupName( $group ); |
280 | if ( !$nameValidationResult->isGood() ) { |
281 | $this->getOutput()->wrapWikiMsg( '<div class="error">$1</div>', |
282 | $nameValidationResult->getMessage() ); |
283 | $this->showLogFragment( $group, $this->getOutput() ); |
284 | return; |
285 | } |
286 | } |
287 | |
288 | $fieldsetClass = $editable |
289 | ? 'mw-centralauth-editgroup' |
290 | : 'mw-centralauth-editgroup-readonly'; |
291 | $html = Xml::fieldset( |
292 | $this->msg( 'centralauth-editgroup-fieldset', $group )->text(), |
293 | false, |
294 | [ 'class' => $fieldsetClass ] |
295 | ); |
296 | |
297 | if ( $editable ) { |
298 | $html .= Xml::openElement( 'form', [ |
299 | 'method' => 'post', |
300 | 'action' => $this->getPageTitle( $group )->getLocalUrl(), |
301 | 'name' => 'centralauth-globalgroups-newgroup' |
302 | ] ); |
303 | $html .= Html::hidden( 'wpGroup', $group ); |
304 | $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ); |
305 | } |
306 | |
307 | $fields = []; |
308 | |
309 | if ( $editable ) { |
310 | $fields['centralauth-editgroup-name'] = Xml::input( 'wpGlobalGroupName', 50, $group ); |
311 | } else { |
312 | $fields['centralauth-editgroup-name'] = htmlspecialchars( $group ); |
313 | } |
314 | |
315 | $lang = $this->getLanguage(); |
316 | if ( $this->getContext()->getAuthority()->isAllowed( 'editinterface' ) ) { |
317 | # Show edit link only to user with the editinterface right |
318 | $fields['centralauth-editgroup-display'] = $this->msg( |
319 | 'centralauth-editgroup-display-edit', |
320 | $group, |
321 | $lang->getGroupName( $group ) |
322 | )->parse(); |
323 | $fields['centralauth-editgroup-member'] = $this->msg( |
324 | 'centralauth-editgroup-member-edit', |
325 | $group, |
326 | $lang->getGroupMemberName( $group, '#' ) |
327 | )->parse(); |
328 | } else { |
329 | $fields['centralauth-editgroup-display'] = |
330 | htmlspecialchars( $lang->getGroupName( $group ) ); |
331 | $fields['centralauth-editgroup-member'] = |
332 | htmlspecialchars( $lang->getGroupMemberName( $group, '#' ) ); |
333 | } |
334 | $fields['centralauth-editgroup-members'] = $this->msg( |
335 | 'centralauth-editgroup-members-link', |
336 | $group, |
337 | $lang->getGroupMemberName( $group, '#' ) |
338 | )->parse(); |
339 | $fields['centralauth-editgroup-restrictions'] = $this->buildWikiSetSelector( $group ); |
340 | $fields['centralauth-editgroup-perms'] = $this->buildCheckboxes( $group ); |
341 | |
342 | if ( $editable ) { |
343 | $fields['centralauth-editgroup-reason'] = Xml::input( 'wpReason', 60 ); |
344 | } |
345 | |
346 | $html .= Xml::buildForm( $fields, $editable ? 'centralauth-editgroup-submit' : null ); |
347 | |
348 | if ( $editable ) { |
349 | $html .= Xml::closeElement( 'form' ); |
350 | } |
351 | |
352 | $html .= Xml::closeElement( 'fieldset' ); |
353 | |
354 | $this->getOutput()->addHTML( $html ); |
355 | |
356 | $this->showLogFragment( $group, $this->getOutput() ); |
357 | } |
358 | |
359 | /** |
360 | * @param string $group |
361 | * @return string |
362 | */ |
363 | private function buildWikiSetSelector( $group ) { |
364 | $sets = WikiSet::getAllWikiSets(); |
365 | $default = WikiSet::getWikiSetForGroup( $group ); |
366 | |
367 | if ( !$this->userCanEdit( $this->getUser() ) ) { |
368 | $set = WikiSet::newFromID( $default ); |
369 | if ( $set ) { |
370 | return $this->getLinkRenderer()->makeLink( |
371 | SpecialPage::getTitleFor( 'WikiSets', (string)$set->getId() ), |
372 | $set->getName() |
373 | ); |
374 | } else { |
375 | return $this->msg( 'centralauth-editgroup-nowikiset' )->parse(); |
376 | } |
377 | } |
378 | |
379 | $select = new XmlSelect( 'set', 'wikiset', (string)$default ); |
380 | $select->addOption( $this->msg( 'centralauth-editgroup-noset' )->text(), '0' ); |
381 | foreach ( $sets as $set ) { |
382 | $select->addOption( $set->getName(), (string)$set->getID() ); |
383 | } |
384 | |
385 | $editlink = $this->msg( 'centralauth-editgroup-editsets' )->parse(); |
386 | return $select->getHTML() . " {$editlink}"; |
387 | } |
388 | |
389 | /** |
390 | * @param string $group |
391 | * @return string |
392 | */ |
393 | private function buildCheckboxes( $group ) { |
394 | $editable = $this->userCanEdit( $this->getUser() ); |
395 | |
396 | $assignedRights = $this->getAssignedRights( $group ); |
397 | |
398 | $checkboxes = []; |
399 | $attribs = []; |
400 | |
401 | if ( !$editable ) { |
402 | $attribs['disabled'] = 'disabled'; |
403 | } |
404 | |
405 | $rights = array_unique( |
406 | array_merge( |
407 | $this->permissionManager->getAllPermissions(), |
408 | $assignedRights |
409 | ) |
410 | ); |
411 | sort( $rights ); |
412 | |
413 | foreach ( $rights as $right ) { |
414 | // Build a checkbox |
415 | $checked = in_array( $right, $assignedRights ); |
416 | |
417 | $desc = $this->formatRight( $right ); |
418 | |
419 | $checkbox = Xml::check( "wpRightAssigned-$right", $checked, |
420 | array_merge( $attribs, [ 'id' => "wpRightAssigned-$right" ] ) ); |
421 | $label = Xml::tags( 'label', [ 'for' => "wpRightAssigned-$right" ], |
422 | $desc ); |
423 | |
424 | $liClass = $checked |
425 | ? 'mw-centralauth-editgroup-checked' |
426 | : 'mw-centralauth-editgroup-unchecked'; |
427 | $checkboxes[] = Html::rawElement( |
428 | 'li', [ 'class' => $liClass ], "$checkbox $label" ); |
429 | } |
430 | |
431 | $count = count( $checkboxes ); |
432 | |
433 | $html = Html::openElement( 'div', [ 'class' => 'mw-centralauth-rights' ] ) |
434 | . '<ul>'; |
435 | |
436 | foreach ( $checkboxes as $cb ) { |
437 | $html .= $cb; |
438 | } |
439 | |
440 | $html .= '</ul>' |
441 | . Html::closeElement( 'div' ); |
442 | |
443 | return $html; |
444 | } |
445 | |
446 | /** |
447 | * Given a user right name, return HTML with the description |
448 | * of the right and it's name for displaying to the user |
449 | * @param string $right |
450 | * @return string escaped html |
451 | */ |
452 | protected function formatRight( $right ) { |
453 | return $this->msg( 'listgrouprights-right-display' ) |
454 | ->params( User::getRightDescription( $right ) ) |
455 | ->rawParams( Html::element( |
456 | 'span', |
457 | [ 'class' => 'mw-listgrouprights-right-name' ], |
458 | $right |
459 | ) ) |
460 | ->parse(); |
461 | } |
462 | |
463 | /** |
464 | * @param string $group |
465 | * @return string[] |
466 | */ |
467 | private function getAssignedRights( $group ) { |
468 | return $this->globalGroupLookup->getRightsForGroup( $group ); |
469 | } |
470 | |
471 | /** |
472 | * @param string $group |
473 | */ |
474 | private function doSubmit( $group ) { |
475 | // It is important to check userCanEdit, as otherwise an |
476 | // unauthorized user could manually construct a POST request. |
477 | if ( !$this->userCanEdit( $this->getUser() ) ) { |
478 | return; |
479 | } |
480 | $reason = $this->getRequest()->getVal( 'wpReason', '' ); |
481 | |
482 | // Current name of the group |
483 | // XXX This is a horrible hack. We should not use Title for normalization. We need to prefix |
484 | // the group name so that the first letter doesn't get uppercased. |
485 | $group = Title::newFromText( "A/$group" ); |
486 | if ( !$group ) { |
487 | $this->getOutput()->addWikiMsg( 'centralauth-editgroup-invalid-name' ); |
488 | return; |
489 | } |
490 | $group = ltrim( substr( $group->getDBkey(), 2 ), '_' ); |
491 | |
492 | // (Potentially) New name of the group |
493 | $newname = $this->getRequest()->getVal( 'wpGlobalGroupName', $group ); |
494 | |
495 | $newname = Title::newFromText( "A/$newname" ); |
496 | if ( !$newname ) { |
497 | $this->getOutput()->addWikiMsg( 'centralauth-editgroup-invalid-name' ); |
498 | return; |
499 | } |
500 | $newname = ltrim( substr( $newname->getDBkey(), 2 ), '_' ); |
501 | |
502 | // all new group names should be lowercase: check all new and changed group names (T202095) |
503 | if ( |
504 | !in_array( $group, $this->globalGroupLookup->getDefinedGroups( DB_PRIMARY ) ) |
505 | || ( $group !== $newname ) |
506 | ) { |
507 | $nameValidationResult = $this->validateGroupName( $newname ); |
508 | if ( !$nameValidationResult->isGood() ) { |
509 | $this->getOutput()->wrapWikiMsg( '<div class="error">$1</div>', |
510 | $nameValidationResult->getMessage() ); |
511 | return; |
512 | } |
513 | } |
514 | |
515 | // Calculate permission changes already! We'll only save any changes |
516 | // here after processing a possible group rename, but want to add |
517 | // validation logic before that. |
518 | $addRights = []; |
519 | $removeRights = []; |
520 | $oldRights = $this->getAssignedRights( $group ); |
521 | $allRights = array_unique( |
522 | array_merge( |
523 | $this->permissionManager->getAllPermissions(), |
524 | $oldRights |
525 | ) |
526 | ); |
527 | |
528 | foreach ( $allRights as $right ) { |
529 | $alreadyAssigned = in_array( $right, $oldRights ); |
530 | $checked = $this->getRequest()->getCheck( "wpRightAssigned-$right" ); |
531 | |
532 | if ( !$alreadyAssigned && $checked ) { |
533 | $addRights[] = $right; |
534 | } elseif ( $alreadyAssigned && !$checked ) { |
535 | $removeRights[] = $right; |
536 | } |
537 | } |
538 | |
539 | // Disallow deleting existing groups with members in them |
540 | if ( |
541 | count( $oldRights ) !== 0 |
542 | && count( $addRights ) === 0 |
543 | && count( $removeRights ) === count( $oldRights ) |
544 | ) { |
545 | $dbr = $this->databaseManager->getCentralReplicaDB(); |
546 | $memberCount = $dbr->newSelectQueryBuilder() |
547 | ->select( 'gug_group' ) |
548 | ->from( 'global_user_groups' ) |
549 | ->where( [ 'gug_group' => $group ] ) |
550 | ->caller( __METHOD__ ) |
551 | ->fetchRow(); |
552 | |
553 | if ( $memberCount ) { |
554 | $this->getOutput()->addWikiMsg( 'centralauth-editgroup-delete-removemembers' ); |
555 | return; |
556 | } |
557 | } |
558 | |
559 | // Check if we need to rename the group |
560 | if ( $group != $newname ) { |
561 | if ( in_array( $newname, $this->globalGroupLookup->getDefinedGroups( DB_PRIMARY ) ) ) { |
562 | $this->getOutput()->addWikiMsg( 'centralauth-editgroup-rename-taken', $newname ); |
563 | return; |
564 | } |
565 | |
566 | $dbw = $this->databaseManager->getCentralPrimaryDB(); |
567 | $updates = [ |
568 | 'global_group_permissions' => 'ggp_group', |
569 | 'global_group_restrictions' => 'ggr_group', |
570 | 'global_user_groups' => 'gug_group' |
571 | ]; |
572 | |
573 | foreach ( $updates as $table => $field ) { |
574 | $dbw->newUpdateQueryBuilder() |
575 | ->update( $table ) |
576 | ->set( [ $field => $newname ] ) |
577 | ->where( [ $field => $group ] ) |
578 | ->caller( __METHOD__ ) |
579 | ->execute(); |
580 | } |
581 | $this->addRenameLog( $group, $newname, $reason ); |
582 | |
583 | // The rest of the changes here will be performed on the "new" group |
584 | $group = $newname; |
585 | } |
586 | |
587 | // Assign the rights. |
588 | if ( count( $addRights ) > 0 ) { |
589 | $this->grantRightsToGroup( $group, $addRights ); |
590 | } |
591 | if ( count( $removeRights ) > 0 ) { |
592 | $this->revokeRightsFromGroup( $group, $removeRights ); |
593 | } |
594 | |
595 | // Log it |
596 | if ( !( count( $addRights ) == 0 && count( $removeRights ) == 0 ) ) { |
597 | $this->addPermissionLog( $group, $addRights, $removeRights, $reason ); |
598 | } |
599 | |
600 | // Change set |
601 | $current = WikiSet::getWikiSetForGroup( $group ); |
602 | $new = $this->getRequest()->getInt( 'set' ); |
603 | if ( $current != $new ) { |
604 | $this->setRestrictions( $group, $new ); |
605 | $this->addWikiSetLog( $group, $current, $new, $reason ); |
606 | } |
607 | |
608 | $this->invalidateRightsCache( $group ); |
609 | |
610 | // Display success |
611 | $this->getOutput()->setSubTitle( $this->msg( 'centralauth-editgroup-success' ) ); |
612 | $this->getOutput()->addWikiMsg( 'centralauth-editgroup-success-text', $group ); |
613 | } |
614 | |
615 | /** |
616 | * @param string $group |
617 | * @param string[] $rights |
618 | */ |
619 | private function revokeRightsFromGroup( $group, $rights ) { |
620 | $dbw = $this->databaseManager->getCentralPrimaryDB(); |
621 | |
622 | # Delete from the DB |
623 | $dbw->newDeleteQueryBuilder() |
624 | ->deleteFrom( 'global_group_permissions' ) |
625 | ->where( [ 'ggp_group' => $group, 'ggp_permission' => $rights ] ) |
626 | ->caller( __METHOD__ ) |
627 | ->execute(); |
628 | } |
629 | |
630 | /** |
631 | * @param string $group |
632 | * @param string[]|string $rights |
633 | */ |
634 | private function grantRightsToGroup( $group, $rights ) { |
635 | $dbw = $this->databaseManager->getCentralPrimaryDB(); |
636 | |
637 | if ( !is_array( $rights ) ) { |
638 | $rights = [ $rights ]; |
639 | } |
640 | |
641 | $insertRows = []; |
642 | foreach ( $rights as $right ) { |
643 | $insertRows[] = [ 'ggp_group' => $group, 'ggp_permission' => $right ]; |
644 | } |
645 | |
646 | # Replace into the DB |
647 | $dbw->newReplaceQueryBuilder() |
648 | ->replaceInto( 'global_group_permissions' ) |
649 | ->uniqueIndexFields( [ 'ggp_group', 'ggp_permission' ] ) |
650 | ->rows( $insertRows ) |
651 | ->caller( __METHOD__ ) |
652 | ->execute(); |
653 | } |
654 | |
655 | /** |
656 | * @param string $group |
657 | * @param OutputPage $output |
658 | */ |
659 | protected function showLogFragment( $group, $output ) { |
660 | $title = SpecialPage::getTitleFor( 'GlobalUsers', $group ); |
661 | $logPage = new LogPage( 'gblrights' ); |
662 | $output->addHTML( Xml::element( 'h2', null, $logPage->getName()->text() . "\n" ) ); |
663 | LogEventsList::showLogExtract( $output, 'gblrights', $title->getPrefixedText() ); |
664 | } |
665 | |
666 | /** |
667 | * Log permission changes |
668 | * |
669 | * @param string $group |
670 | * @param string[] $addRights |
671 | * @param string[] $removeRights |
672 | * @param string $reason |
673 | */ |
674 | private function addPermissionLog( $group, $addRights, $removeRights, $reason ) { |
675 | $entry = new ManualLogEntry( 'gblrights', 'groupprms2' ); |
676 | $entry->setTarget( SpecialPage::getTitleFor( 'GlobalUsers', $group ) ); |
677 | $entry->setPerformer( $this->getUser() ); |
678 | $entry->setComment( $reason ); |
679 | $entry->setParameters( [ |
680 | 'addRights' => $addRights, |
681 | 'removeRights' => $removeRights, |
682 | ] ); |
683 | $logid = $entry->insert(); |
684 | $entry->publish( $logid ); |
685 | } |
686 | |
687 | /** |
688 | * Log the renaming of a global group |
689 | * |
690 | * @param string $oldName |
691 | * @param string $newName |
692 | * @param string $reason |
693 | */ |
694 | private function addRenameLog( $oldName, $newName, $reason ) { |
695 | $entry = new ManualLogEntry( 'gblrights', 'grouprename' ); |
696 | // This has to point to 'Special:GlobalUsers so that self::showLogFragment can find it |
697 | $entry->setTarget( SpecialPage::getTitleFor( 'GlobalUsers', $newName ) ); |
698 | $entry->setPerformer( $this->getUser() ); |
699 | $entry->setComment( $reason ); |
700 | $entry->setParameters( [ |
701 | 'newName' => $newName, |
702 | 'oldName' => $oldName, |
703 | ] ); |
704 | $logid = $entry->insert(); |
705 | $entry->publish( $logid ); |
706 | } |
707 | |
708 | /** |
709 | * Log wikiset changes |
710 | * |
711 | * @param string $group |
712 | * @param int $old |
713 | * @param int $new |
714 | * @param string $reason |
715 | */ |
716 | private function addWikiSetLog( $group, $old, $new, $reason ) { |
717 | $entry = new ManualLogEntry( 'gblrights', 'groupprms3' ); |
718 | $entry->setTarget( SpecialPage::getTitleFor( 'GlobalUsers', $group ) ); |
719 | $entry->setPerformer( $this->getUser() ); |
720 | $entry->setComment( $reason ); |
721 | $params = []; |
722 | $mapping = [ |
723 | 'old' => [ 4, $old ], |
724 | 'new' => [ 5, $new ], |
725 | ]; |
726 | foreach ( $mapping as $param => [ $id, $set ] ) { |
727 | $name = $this->getWikiSetName( $set ); |
728 | if ( $name !== null ) { |
729 | $params["$id::$param"] = $name; |
730 | } else { |
731 | $params["$id:msg:$param"] = 'centralauth-editgroup-noset'; |
732 | } |
733 | } |
734 | $entry->setParameters( $params ); |
735 | $logid = $entry->insert(); |
736 | $entry->publish( $logid ); |
737 | } |
738 | |
739 | /** |
740 | * @param string $group |
741 | * @param int $set |
742 | * @return bool |
743 | */ |
744 | private function setRestrictions( $group, $set ) { |
745 | $dbw = $this->databaseManager->getCentralPrimaryDB(); |
746 | if ( $set == 0 ) { |
747 | $dbw->newDeleteQueryBuilder() |
748 | ->deleteFrom( 'global_group_restrictions' ) |
749 | ->where( [ 'ggr_group' => $group ] ) |
750 | ->caller( __METHOD__ ) |
751 | ->execute(); |
752 | } else { |
753 | $dbw->newReplaceQueryBuilder() |
754 | ->replaceInto( 'global_group_restrictions' ) |
755 | ->uniqueIndexFields( 'ggr_group' ) |
756 | ->row( [ 'ggr_group' => $group, 'ggr_set' => $set, ] ) |
757 | ->caller( __METHOD__ ) |
758 | ->execute(); |
759 | } |
760 | return (bool)$dbw->affectedRows(); |
761 | } |
762 | |
763 | /** |
764 | * @param string|int $id |
765 | * @return string|null |
766 | */ |
767 | private function getWikiSetName( $id ) { |
768 | $wikiset = WikiSet::newFromID( $id ); |
769 | if ( $wikiset !== null ) { |
770 | return $wikiset->getName(); |
771 | } |
772 | return null; |
773 | } |
774 | |
775 | /** |
776 | * @param string $group |
777 | */ |
778 | private function invalidateRightsCache( $group ) { |
779 | // Figure out all the users in this group. |
780 | // Use the primary database over here as this could go horribly wrong with newly created or just |
781 | // renamed groups |
782 | $dbr = $this->databaseManager->getCentralPrimaryDB(); |
783 | |
784 | $res = $dbr->newSelectQueryBuilder() |
785 | ->select( 'gu_name' ) |
786 | ->from( 'globaluser' ) |
787 | ->join( 'global_user_groups', null, 'gu_id=gug_user' ) |
788 | ->where( [ 'gug_group' => $group ] ) |
789 | ->caller( __METHOD__ ) |
790 | ->fetchFieldValues(); |
791 | |
792 | // Invalidate their rights cache. |
793 | foreach ( $res as $name ) { |
794 | // Use READ_LATEST for paranoia, though the DB isn't used in this method |
795 | $cu = CentralAuthUser::getPrimaryInstanceByName( $name ); |
796 | $cu->quickInvalidateCache(); |
797 | } |
798 | } |
799 | |
800 | /** @inheritDoc */ |
801 | protected function getGroupName() { |
802 | return 'users'; |
803 | } |
804 | |
805 | private function validateGroupName( string $name ): Status { |
806 | // all new group names should be lowercase (T202095) |
807 | if ( $name !== strtolower( $name ) ) { |
808 | return Status::newFatal( 'centralauth-editgroup-invalid-name-lowercase' ); |
809 | } |
810 | |
811 | return Status::newGood(); |
812 | } |
813 | } |