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