MediaWiki  master
PermissionManager.php
Go to the documentation of this file.
1 <?php
20 namespace MediaWiki\Permissions;
21 
22 use Action;
23 use Article;
24 use Exception;
36 use NamespaceInfo;
37 use RequestContext;
38 use SpecialPage;
39 use Title;
40 use User;
41 use Wikimedia\ScopedCallback;
42 
50 
52  public const RIGOR_QUICK = 'quick';
53 
55  public const RIGOR_FULL = 'full';
56 
58  public const RIGOR_SECURE = 'secure';
59 
64  public const CONSTRUCTOR_OPTIONS = [
65  'WhitelistRead',
66  'WhitelistReadRegexp',
67  'EmailConfirmToEdit',
68  'BlockDisablesLogin',
69  'GroupPermissions',
70  'RevokePermissions',
71  'AvailableRights',
72  'NamespaceProtection',
73  'RestrictionLevels'
74  ];
75 
77  private $options;
78 
81 
83  private $revisionLookup;
84 
86  private $nsInfo;
87 
89  private $allRights;
90 
93 
95  private $hookRunner;
96 
98  private $usersRights = null;
99 
104  private $temporaryUserRights = [];
105 
107  private $cachedRights = [];
108 
115  private $coreRights = [
116  'apihighlimits',
117  'applychangetags',
118  'autoconfirmed',
119  'autocreateaccount',
120  'autopatrol',
121  'bigdelete',
122  'block',
123  'blockemail',
124  'bot',
125  'browsearchive',
126  'changetags',
127  'createaccount',
128  'createpage',
129  'createtalk',
130  'delete',
131  'deletechangetags',
132  'deletedhistory',
133  'deletedtext',
134  'deletelogentry',
135  'deleterevision',
136  'edit',
137  'editcontentmodel',
138  'editinterface',
139  'editprotected',
140  'editmyoptions',
141  'editmyprivateinfo',
142  'editmyusercss',
143  'editmyuserjson',
144  'editmyuserjs',
145  'editmyuserjsredirect',
146  'editmywatchlist',
147  'editsemiprotected',
148  'editsitecss',
149  'editsitejson',
150  'editsitejs',
151  'editusercss',
152  'edituserjson',
153  'edituserjs',
154  'hideuser',
155  'import',
156  'importupload',
157  'ipblock-exempt',
158  'managechangetags',
159  'markbotedits',
160  'mergehistory',
161  'minoredit',
162  'move',
163  'movefile',
164  'move-categorypages',
165  'move-rootuserpages',
166  'move-subpages',
167  'nominornewtalk',
168  'noratelimit',
169  'override-export-depth',
170  'pagelang',
171  'patrol',
172  'patrolmarks',
173  'protect',
174  'purge',
175  'read',
176  'reupload',
177  'reupload-own',
178  'reupload-shared',
179  'rollback',
180  'sendemail',
181  'siteadmin',
182  'suppressionlog',
183  'suppressredirect',
184  'suppressrevision',
185  'unblockself',
186  'undelete',
187  'unwatchedpages',
188  'upload',
189  'upload_by_url',
190  'userrights',
191  'userrights-interwiki',
192  'viewmyprivateinfo',
193  'viewmywatchlist',
194  'viewsuppressed',
195  'writeapi',
196  ];
197 
206  public function __construct(
212  HookContainer $hookContainer
213  ) {
214  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
215  $this->options = $options;
216  $this->specialPageFactory = $specialPageFactory;
217  $this->revisionLookup = $revisionLookup;
218  $this->nsInfo = $nsInfo;
219  $this->blockErrorFormatter = $blockErrorFormatter;
220  $this->hookRunner = new HookRunner( $hookContainer );
221  }
222 
242  public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
243  return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
244  }
245 
261  public function quickUserCan( $action, User $user, LinkTarget $page ) {
262  return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
263  }
264 
282  public function getPermissionErrors(
283  $action,
284  User $user,
285  LinkTarget $page,
286  $rigor = self::RIGOR_SECURE,
287  $ignoreErrors = []
288  ) {
289  $errors = $this->getPermissionErrorsInternal( $action, $user, $page, $rigor );
290 
291  // Remove the errors being ignored.
292  foreach ( $errors as $index => $error ) {
293  $errKey = is_array( $error ) ? $error[0] : $error;
294 
295  if ( in_array( $errKey, $ignoreErrors ) ) {
296  unset( $errors[$index] );
297  }
298  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
299  unset( $errors[$index] );
300  }
301  }
302 
303  return $errors;
304  }
305 
316  public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
317  $block = $user->getBlock( $fromReplica );
318  if ( !$block ) {
319  return false;
320  }
321 
322  // TODO: remove upon further migration to LinkTarget
323  $title = Title::newFromLinkTarget( $page );
324 
325  $blocked = $user->isHidden();
326  if ( !$blocked ) {
327  // Special handling for a user's own talk page. The block is not aware
328  // of the user, so this must be done here.
329  if ( $title->equals( $user->getTalkPage() ) ) {
330  $blocked = $block->appliesToUsertalk( $title );
331  } else {
332  $blocked = $block->appliesToTitle( $title );
333  }
334  }
335 
336  // only for the purpose of the hook. We really don't need this here.
337  $allowUsertalk = $user->isAllowUsertalk();
338 
339  // Allow extensions to let a blocked user access a particular page
340  $this->hookRunner->onUserIsBlockedFrom( $user, $title, $blocked, $allowUsertalk );
341 
342  return $blocked;
343  }
344 
362  private function getPermissionErrorsInternal(
363  $action,
364  User $user,
365  LinkTarget $page,
366  $rigor = self::RIGOR_SECURE,
367  $short = false
368  ) {
369  if ( !in_array( $rigor, [ self::RIGOR_QUICK, self::RIGOR_FULL, self::RIGOR_SECURE ] ) ) {
370  throw new Exception( "Invalid rigor parameter '$rigor'." );
371  }
372 
373  # Read has special handling
374  if ( $action == 'read' ) {
375  $checks = [
376  'checkPermissionHooks',
377  'checkReadPermissions',
378  'checkUserBlock', // for wgBlockDisablesLogin
379  ];
380  # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
381  # or checkUserConfigPermissions here as it will lead to duplicate
382  # error messages. This is okay to do since anywhere that checks for
383  # create will also check for edit, and those checks are called for edit.
384  } elseif ( $action == 'create' ) {
385  $checks = [
386  'checkQuickPermissions',
387  'checkPermissionHooks',
388  'checkPageRestrictions',
389  'checkCascadingSourcesRestrictions',
390  'checkActionPermissions',
391  'checkUserBlock'
392  ];
393  } else {
394  $checks = [
395  'checkQuickPermissions',
396  'checkPermissionHooks',
397  'checkSpecialsAndNSPermissions',
398  'checkSiteConfigPermissions',
399  'checkUserConfigPermissions',
400  'checkPageRestrictions',
401  'checkCascadingSourcesRestrictions',
402  'checkActionPermissions',
403  'checkUserBlock'
404  ];
405  }
406 
407  $errors = [];
408  foreach ( $checks as $method ) {
409  $errors = $this->$method( $action, $user, $errors, $rigor, $short, $page );
410 
411  if ( $short && $errors !== [] ) {
412  break;
413  }
414  }
415 
416  return $errors;
417  }
418 
435  private function checkPermissionHooks(
436  $action,
437  User $user,
438  $errors,
439  $rigor,
440  $short,
441  LinkTarget $page
442  ) {
443  // TODO: remove when LinkTarget usage will expand further
444  $title = Title::newFromLinkTarget( $page );
445  // Use getUserPermissionsErrors instead
446  $result = '';
447  if ( !$this->hookRunner->onUserCan( $title, $user, $action, $result ) ) {
448  return $result ? [] : [ [ 'badaccess-group0' ] ];
449  }
450  // Check getUserPermissionsErrors hook
451  if ( !$this->hookRunner->onGetUserPermissionsErrors( $title, $user, $action, $result ) ) {
452  $errors = $this->resultToError( $errors, $result );
453  }
454  // Check getUserPermissionsErrorsExpensive hook
455  if (
456  $rigor !== self::RIGOR_QUICK
457  && !( $short && count( $errors ) > 0 )
458  && !$this->hookRunner->onGetUserPermissionsErrorsExpensive(
459  $title, $user, $action, $result )
460  ) {
461  $errors = $this->resultToError( $errors, $result );
462  }
463 
464  return $errors;
465  }
466 
475  private function resultToError( $errors, $result ) {
476  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
477  // A single array representing an error
478  $errors[] = $result;
479  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
480  // A nested array representing multiple errors
481  $errors = array_merge( $errors, $result );
482  } elseif ( $result !== '' && is_string( $result ) ) {
483  // A string representing a message-id
484  $errors[] = [ $result ];
485  } elseif ( $result instanceof MessageSpecifier ) {
486  // A message specifier representing an error
487  $errors[] = [ $result ];
488  } elseif ( $result === false ) {
489  // a generic "We don't want them to do that"
490  $errors[] = [ 'badaccess-group0' ];
491  }
492  return $errors;
493  }
494 
511  private function checkReadPermissions(
512  $action,
513  User $user,
514  $errors,
515  $rigor,
516  $short,
517  LinkTarget $page
518  ) {
519  // TODO: remove when LinkTarget usage will expand further
520  $title = Title::newFromLinkTarget( $page );
521 
522  $whiteListRead = $this->options->get( 'WhitelistRead' );
523  $whitelisted = false;
524  if ( $this->isEveryoneAllowed( 'read' ) ) {
525  # Shortcut for public wikis, allows skipping quite a bit of code
526  $whitelisted = true;
527  } elseif ( $this->userHasRight( $user, 'read' ) ) {
528  # If the user is allowed to read pages, he is allowed to read all pages
529  $whitelisted = true;
530  } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
531  || $this->isSameSpecialPage( 'PasswordReset', $title )
532  || $this->isSameSpecialPage( 'Userlogout', $title )
533  ) {
534  # Always grant access to the login page.
535  # Even anons need to be able to log in.
536  $whitelisted = true;
537  } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
538  # Time to check the whitelist
539  # Only do these checks is there's something to check against
540  $name = $title->getPrefixedText();
541  $dbName = $title->getPrefixedDBkey();
542 
543  // Check for explicit whitelisting with and without underscores
544  if ( in_array( $name, $whiteListRead, true )
545  || in_array( $dbName, $whiteListRead, true ) ) {
546  $whitelisted = true;
547  } elseif ( $title->getNamespace() === NS_MAIN ) {
548  # Old settings might have the title prefixed with
549  # a colon for main-namespace pages
550  if ( in_array( ':' . $name, $whiteListRead ) ) {
551  $whitelisted = true;
552  }
553  } elseif ( $title->isSpecialPage() ) {
554  # If it's a special page, ditch the subpage bit and check again
555  $name = $title->getDBkey();
556  list( $name, /* $subpage */ ) =
557  $this->specialPageFactory->resolveAlias( $name );
558  if ( $name ) {
559  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
560  if ( in_array( $pure, $whiteListRead, true ) ) {
561  $whitelisted = true;
562  }
563  }
564  }
565  }
566 
567  $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
568  if ( !$whitelisted && is_array( $whitelistReadRegexp )
569  && !empty( $whitelistReadRegexp ) ) {
570  $name = $title->getPrefixedText();
571  // Check for regex whitelisting
572  foreach ( $whitelistReadRegexp as $listItem ) {
573  if ( preg_match( $listItem, $name ) ) {
574  $whitelisted = true;
575  break;
576  }
577  }
578  }
579 
580  if ( !$whitelisted ) {
581  # If the title is not whitelisted, give extensions a chance to do so...
582  $this->hookRunner->onTitleReadWhitelist( $title, $user, $whitelisted );
583  if ( !$whitelisted ) {
584  $errors[] = $this->missingPermissionError( $action, $short );
585  }
586  }
587 
588  return $errors;
589  }
590 
599  private function missingPermissionError( $action, $short ) {
600  // We avoid expensive display logic for quickUserCan's and such
601  if ( $short ) {
602  return [ 'badaccess-group0' ];
603  }
604 
605  // TODO: it would be a good idea to replace the method below with something else like
606  // maybe callback injection
607  return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
608  }
609 
618  private function isSameSpecialPage( $name, LinkTarget $page ) {
619  if ( $page->getNamespace() === NS_SPECIAL ) {
620  list( $thisName, /* $subpage */ ) =
621  $this->specialPageFactory->resolveAlias( $page->getDBkey() );
622  if ( $name == $thisName ) {
623  return true;
624  }
625  }
626  return false;
627  }
628 
645  private function checkUserBlock(
646  $action,
647  User $user,
648  $errors,
649  $rigor,
650  $short,
651  LinkTarget $page
652  ) {
653  // Account creation blocks handled at userlogin.
654  // Unblocking handled in SpecialUnblock
655  if ( $rigor === self::RIGOR_QUICK || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
656  return $errors;
657  }
658 
659  // Optimize for a very common case
660  if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
661  return $errors;
662  }
663 
664  if ( $this->options->get( 'EmailConfirmToEdit' )
665  && !$user->isEmailConfirmed()
666  && $action === 'edit'
667  ) {
668  $errors[] = [ 'confirmedittext' ];
669  }
670 
671  $useReplica = ( $rigor !== self::RIGOR_SECURE );
672  $block = $user->getBlock( $useReplica );
673 
674  // If the user does not have a block, or the block they do have explicitly
675  // allows the action (like "read" or "upload").
676  if ( !$block || $block->appliesToRight( $action ) === false ) {
677  return $errors;
678  }
679 
680  // Determine if the user is blocked from this action on this page.
681  // What gets passed into this method is a user right, not an action name.
682  // There is no way to instantiate an action by restriction. However, this
683  // will get the action where the restriction is the same. This may result
684  // in actions being blocked that shouldn't be.
685  $actionObj = null;
686  if ( Action::exists( $action ) ) {
687  // TODO: this drags a ton of dependencies in, would be good to avoid Article
688  // instantiation and decouple it creating an ActionPermissionChecker interface
689  // Creating an action will perform several database queries to ensure that
690  // the action has not been overridden by the content type.
691  // FIXME: avoid use of RequestContext since it drags in User and Title dependencies
692  // probably we may use fake context object since it's unlikely that Action uses it
693  // anyway. It would be nice if we could avoid instantiating the Action at all.
694  $title = Title::newFromLinkTarget( $page, 'clone' );
696  $actionObj = Action::factory(
697  $action,
699  $context
700  );
701  // Ensure that the retrieved action matches the restriction.
702  if ( $actionObj && $actionObj->getRestriction() !== $action ) {
703  $actionObj = null;
704  }
705  }
706 
707  // If no action object is returned, assume that the action requires unblock
708  // which is the default.
709  if ( !$actionObj || $actionObj->requiresUnblock() ) {
710  if ( $this->isBlockedFrom( $user, $page, $useReplica ) ) {
711  // @todo FIXME: Pass the relevant context into this function.
713  $message = $this->blockErrorFormatter->getMessage(
714  $block,
715  $context->getUser(),
717  $context->getRequest()->getIP()
718  );
719  $errors[] = array_merge( [ $message->getKey() ], $message->getParams() );
720  }
721  }
722 
723  return $errors;
724  }
725 
742  private function checkQuickPermissions(
743  $action,
744  User $user,
745  $errors,
746  $rigor,
747  $short,
748  LinkTarget $page
749  ) {
750  // TODO: remove when LinkTarget usage will expand further
751  $title = Title::newFromLinkTarget( $page );
752 
753  if ( !$this->hookRunner->onTitleQuickPermissions( $title, $user, $action,
754  $errors, $rigor !== self::RIGOR_QUICK, $short )
755  ) {
756  return $errors;
757  }
758 
759  $isSubPage = $this->nsInfo->hasSubpages( $title->getNamespace() ) ?
760  strpos( $title->getText(), '/' ) !== false : false;
761 
762  if ( $action == 'create' ) {
763  if (
764  ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
765  !$this->userHasRight( $user, 'createtalk' ) ) ||
766  ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
767  !$this->userHasRight( $user, 'createpage' ) )
768  ) {
769  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
770  }
771  } elseif ( $action == 'move' ) {
772  if ( !$this->userHasRight( $user, 'move-rootuserpages' )
773  && $title->getNamespace() === NS_USER && !$isSubPage ) {
774  // Show user page-specific message only if the user can move other pages
775  $errors[] = [ 'cant-move-user-page' ];
776  }
777 
778  // Check if user is allowed to move files if it's a file
779  if ( $title->getNamespace() === NS_FILE &&
780  !$this->userHasRight( $user, 'movefile' ) ) {
781  $errors[] = [ 'movenotallowedfile' ];
782  }
783 
784  // Check if user is allowed to move category pages if it's a category page
785  if ( $title->getNamespace() === NS_CATEGORY &&
786  !$this->userHasRight( $user, 'move-categorypages' ) ) {
787  $errors[] = [ 'cant-move-category-page' ];
788  }
789 
790  if ( !$this->userHasRight( $user, 'move' ) ) {
791  // User can't move anything
792  $userCanMove = $this->groupHasPermission( 'user', 'move' );
793  $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
794  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
795  // custom message if logged-in users without any special rights can move
796  $errors[] = [ 'movenologintext' ];
797  } else {
798  $errors[] = [ 'movenotallowed' ];
799  }
800  }
801  } elseif ( $action == 'move-target' ) {
802  if ( !$this->userHasRight( $user, 'move' ) ) {
803  // User can't move anything
804  $errors[] = [ 'movenotallowed' ];
805  } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
806  && $title->getNamespace() === NS_USER
807  && !$isSubPage
808  ) {
809  // Show user page-specific message only if the user can move other pages
810  $errors[] = [ 'cant-move-to-user-page' ];
811  } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
812  && $title->getNamespace() === NS_CATEGORY
813  ) {
814  // Show category page-specific message only if the user can move other pages
815  $errors[] = [ 'cant-move-to-category-page' ];
816  }
817  } elseif ( !$this->userHasRight( $user, $action ) ) {
818  $errors[] = $this->missingPermissionError( $action, $short );
819  }
820 
821  return $errors;
822  }
823 
842  private function checkPageRestrictions(
843  $action,
844  User $user,
845  $errors,
846  $rigor,
847  $short,
848  LinkTarget $page
849  ) {
850  // TODO: remove & rework upon further use of LinkTarget
851  $title = Title::newFromLinkTarget( $page );
852  foreach ( $title->getRestrictions( $action ) as $right ) {
853  // Backwards compatibility, rewrite sysop -> editprotected
854  if ( $right == 'sysop' ) {
855  $right = 'editprotected';
856  }
857  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
858  if ( $right == 'autoconfirmed' ) {
859  $right = 'editsemiprotected';
860  }
861  if ( $right == '' ) {
862  continue;
863  }
864  if ( !$this->userHasRight( $user, $right ) ) {
865  $errors[] = [ 'protectedpagetext', $right, $action ];
866  } elseif ( $title->areRestrictionsCascading() &&
867  !$this->userHasRight( $user, 'protect' )
868  ) {
869  $errors[] = [ 'protectedpagetext', 'protect', $action ];
870  }
871  }
872 
873  return $errors;
874  }
875 
893  $action,
894  UserIdentity $user,
895  $errors,
896  $rigor,
897  $short,
898  LinkTarget $page
899  ) {
900  // TODO: remove & rework upon further use of LinkTarget
901  $title = Title::newFromLinkTarget( $page );
902  if ( $rigor !== self::RIGOR_QUICK && !$title->isUserConfigPage() ) {
903  list( $cascadingSources, $restrictions ) = $title->getCascadeProtectionSources();
904  # Cascading protection depends on more than this page...
905  # Several cascading protected pages may include this page...
906  # Check each cascading level
907  # This is only for protection restrictions, not for all actions
908  if ( isset( $restrictions[$action] ) ) {
909  foreach ( $restrictions[$action] as $right ) {
910  // Backwards compatibility, rewrite sysop -> editprotected
911  if ( $right == 'sysop' ) {
912  $right = 'editprotected';
913  }
914  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
915  if ( $right == 'autoconfirmed' ) {
916  $right = 'editsemiprotected';
917  }
918  if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
919  $wikiPages = '';
921  foreach ( $cascadingSources as $wikiPage ) {
922  $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
923  }
924  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $wikiPages, $action ];
925  }
926  }
927  }
928  }
929 
930  return $errors;
931  }
932 
949  private function checkActionPermissions(
950  $action,
951  User $user,
952  $errors,
953  $rigor,
954  $short,
955  LinkTarget $page
956  ) {
958 
959  // TODO: remove & rework upon further use of LinkTarget
960  $title = Title::newFromLinkTarget( $page );
961 
962  if ( $action == 'protect' ) {
963  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
964  // If they can't edit, they shouldn't protect.
965  $errors[] = [ 'protect-cantedit' ];
966  }
967  } elseif ( $action == 'create' ) {
968  $title_protection = $title->getTitleProtection();
969  if ( $title_protection ) {
970  if ( $title_protection['permission'] == ''
971  || !$this->userHasRight( $user, $title_protection['permission'] )
972  ) {
973  $errors[] = [
974  'titleprotected',
975  // TODO: get rid of the User dependency
976  User::whoIs( $title_protection['user'] ),
977  $title_protection['reason']
978  ];
979  }
980  }
981  } elseif ( $action == 'move' ) {
982  // Check for immobile pages
983  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
984  // Specific message for this case
985  $nsText = $title->getNsText();
986  if ( $nsText === '' ) {
987  $nsText = wfMessage( 'blanknamespace' )->text();
988  }
989  $errors[] = [ 'immobile-source-namespace', $nsText ];
990  } elseif ( !$title->isMovable() ) {
991  // Less specific message for rarer cases
992  $errors[] = [ 'immobile-source-page' ];
993  }
994  } elseif ( $action == 'move-target' ) {
995  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
996  $nsText = $title->getNsText();
997  if ( $nsText === '' ) {
998  $nsText = wfMessage( 'blanknamespace' )->text();
999  }
1000  $errors[] = [ 'immobile-target-namespace', $nsText ];
1001  } elseif ( !$title->isMovable() ) {
1002  $errors[] = [ 'immobile-target-page' ];
1003  }
1004  } elseif ( $action == 'delete' ) {
1005  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true, $title );
1006  if ( !$tempErrors ) {
1007  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
1008  $user, $tempErrors, $rigor, true, $title );
1009  }
1010  if ( $tempErrors ) {
1011  // If protection keeps them from editing, they shouldn't be able to delete.
1012  $errors[] = [ 'deleteprotected' ];
1013  }
1014  if ( $rigor !== self::RIGOR_QUICK && $wgDeleteRevisionsLimit
1015  && !$this->userCan( 'bigdelete', $user, $title ) && $title->isBigDeletion()
1016  ) {
1017  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
1018  }
1019  } elseif ( $action === 'undelete' ) {
1020  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
1021  // Undeleting implies editing
1022  $errors[] = [ 'undelete-cantedit' ];
1023  }
1024  if ( !$title->exists()
1025  && count( $this->getPermissionErrorsInternal( 'create', $user, $title, $rigor, true ) )
1026  ) {
1027  // Undeleting where nothing currently exists implies creating
1028  $errors[] = [ 'undelete-cantcreate' ];
1029  }
1030  }
1031  return $errors;
1032  }
1033 
1051  $action,
1052  UserIdentity $user,
1053  $errors,
1054  $rigor,
1055  $short,
1056  LinkTarget $page
1057  ) {
1058  // TODO: remove & rework upon further use of LinkTarget
1059  $title = Title::newFromLinkTarget( $page );
1060 
1061  # Only 'createaccount' can be performed on special pages,
1062  # which don't actually exist in the DB.
1063  if ( $title->getNamespace() === NS_SPECIAL && $action !== 'createaccount' ) {
1064  $errors[] = [ 'ns-specialprotected' ];
1065  }
1066 
1067  # Check $wgNamespaceProtection for restricted namespaces
1068  if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
1069  $ns = $title->getNamespace() === NS_MAIN ?
1070  wfMessage( 'nstab-main' )->text() : $title->getNsText();
1071  $errors[] = $title->getNamespace() === NS_MEDIAWIKI ?
1072  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
1073  }
1074 
1075  return $errors;
1076  }
1077 
1094  private function checkSiteConfigPermissions(
1095  $action,
1096  User $user,
1097  $errors,
1098  $rigor,
1099  $short,
1100  LinkTarget $page
1101  ) {
1102  // TODO: remove & rework upon further use of LinkTarget
1103  $title = Title::newFromLinkTarget( $page );
1104 
1105  if ( $action != 'patrol' ) {
1106  $error = null;
1107  // Sitewide CSS/JSON/JS/RawHTML changes, like all NS_MEDIAWIKI changes, also require the
1108  // editinterface right. That's implemented as a restriction so no check needed here.
1109  if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
1110  $error = [ 'sitecssprotected', $action ];
1111  } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
1112  $error = [ 'sitejsonprotected', $action ];
1113  } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
1114  $error = [ 'sitejsprotected', $action ];
1115  }
1116  if ( $title->isRawHtmlMessage() && !$this->userCanEditRawHtmlPage( $user ) ) {
1117  $error = [ 'siterawhtmlprotected', $action ];
1118  }
1119 
1120  if ( $error ) {
1121  if ( $this->userHasRight( $user, 'editinterface' ) ) {
1122  // Most users / site admins will probably find out about the new, more restrictive
1123  // permissions by failing to edit something. Give them more info.
1124  // TODO remove this a few release cycles after 1.32
1125  $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
1126  }
1127  $errors[] = $error;
1128  }
1129  }
1130 
1131  return $errors;
1132  }
1133 
1150  private function checkUserConfigPermissions(
1151  $action,
1152  UserIdentity $user,
1153  $errors,
1154  $rigor,
1155  $short,
1156  LinkTarget $page
1157  ) {
1158  // TODO: remove & rework upon further use of LinkTarget
1159  $title = Title::newFromLinkTarget( $page );
1160 
1161  # Protect css/json/js subpages of user pages
1162  # XXX: this might be better using restrictions
1163 
1164  if ( $action === 'patrol' ) {
1165  return $errors;
1166  }
1167 
1168  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $title->getText() ) ) {
1169  // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
1170  if (
1171  $title->isUserCssConfigPage()
1172  && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
1173  ) {
1174  $errors[] = [ 'mycustomcssprotected', $action ];
1175  } elseif (
1176  $title->isUserJsonConfigPage()
1177  && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
1178  ) {
1179  $errors[] = [ 'mycustomjsonprotected', $action ];
1180  } elseif (
1181  $title->isUserJsConfigPage()
1182  && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
1183  ) {
1184  $errors[] = [ 'mycustomjsprotected', $action ];
1185  } elseif (
1186  $title->isUserJsConfigPage()
1187  && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
1188  ) {
1189  // T207750 - do not allow users to edit a redirect if they couldn't edit the target
1190  $rev = $this->revisionLookup->getRevisionByTitle( $title );
1191  $content = $rev ? $rev->getContent( 'main', RevisionRecord::RAW ) : null;
1192  $target = $content ? $content->getUltimateRedirectTarget() : null;
1193  if ( $target && (
1194  !$target->inNamespace( NS_USER )
1195  || !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $target->getText() )
1196  ) ) {
1197  $errors[] = [ 'mycustomjsredirectprotected', $action ];
1198  }
1199  }
1200  } else {
1201  // Users need edituser* to edit others' CSS/JSON/JS subpages, except for
1202  // deletion/suppression which cannot be used for attacks and we want to avoid the
1203  // situation where an unprivileged user can post abusive content on their subpages
1204  // and only very highly privileged users could remove it.
1205  if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
1206  if (
1207  $title->isUserCssConfigPage()
1208  && !$this->userHasRight( $user, 'editusercss' )
1209  ) {
1210  $errors[] = [ 'customcssprotected', $action ];
1211  } elseif (
1212  $title->isUserJsonConfigPage()
1213  && !$this->userHasRight( $user, 'edituserjson' )
1214  ) {
1215  $errors[] = [ 'customjsonprotected', $action ];
1216  } elseif (
1217  $title->isUserJsConfigPage()
1218  && !$this->userHasRight( $user, 'edituserjs' )
1219  ) {
1220  $errors[] = [ 'customjsprotected', $action ];
1221  }
1222  }
1223  }
1224 
1225  return $errors;
1226  }
1227 
1238  public function userHasRight( UserIdentity $user, $action = '' ) {
1239  if ( $action === '' ) {
1240  return true; // In the spirit of DWIM
1241  }
1242  // Use strict parameter to avoid matching numeric 0 accidentally inserted
1243  // by misconfiguration: 0 == 'foo'
1244  return in_array( $action, $this->getUserPermissions( $user ), true );
1245  }
1246 
1255  public function userHasAnyRight( UserIdentity $user, ...$actions ) {
1256  foreach ( $actions as $action ) {
1257  if ( $this->userHasRight( $user, $action ) ) {
1258  return true;
1259  }
1260  }
1261  return false;
1262  }
1263 
1272  public function userHasAllRights( UserIdentity $user, ...$actions ) {
1273  foreach ( $actions as $action ) {
1274  if ( !$this->userHasRight( $user, $action ) ) {
1275  return false;
1276  }
1277  }
1278  return true;
1279  }
1280 
1290  public function getUserPermissions( UserIdentity $user ) {
1291  $user = User::newFromIdentity( $user );
1292  $rightsCacheKey = $this->getRightsCacheKey( $user );
1293  if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1294  $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
1295  $user->getEffectiveGroups()
1296  );
1297  $this->hookRunner->onUserGetRights( $user, $this->usersRights[ $rightsCacheKey ] );
1298 
1299  // Deny any rights denied by the user's session, unless this
1300  // endpoint has no sessions.
1301  if ( !defined( 'MW_NO_SESSION' ) ) {
1302  // FIXME: $user->getRequest().. need to be replaced with something else
1303  $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
1304  if ( $allowedRights !== null ) {
1305  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1306  $this->usersRights[ $rightsCacheKey ],
1307  $allowedRights
1308  );
1309  }
1310  }
1311 
1312  $this->hookRunner->onUserGetRightsRemove(
1313  $user, $this->usersRights[ $rightsCacheKey ] );
1314  // Force reindexation of rights when a hook has unset one of them
1315  $this->usersRights[ $rightsCacheKey ] = array_values(
1316  array_unique( $this->usersRights[ $rightsCacheKey ] )
1317  );
1318 
1319  if (
1320  $user->isLoggedIn() &&
1321  $this->options->get( 'BlockDisablesLogin' ) &&
1322  $user->getBlock()
1323  ) {
1324  $anon = new User;
1325  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1326  $this->usersRights[ $rightsCacheKey ],
1327  $this->getUserPermissions( $anon )
1328  );
1329  }
1330  }
1331  $rights = $this->usersRights[ $rightsCacheKey ];
1332  foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
1333  $rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
1334  }
1335  return $rights;
1336  }
1337 
1346  public function invalidateUsersRightsCache( $user = null ) {
1347  if ( $user !== null ) {
1348  $rightsCacheKey = $this->getRightsCacheKey( $user );
1349  if ( isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1350  unset( $this->usersRights[ $rightsCacheKey ] );
1351  }
1352  } else {
1353  $this->usersRights = null;
1354  }
1355  }
1356 
1362  private function getRightsCacheKey( UserIdentity $user ) {
1363  return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
1364  }
1365 
1380  public function groupHasPermission( $group, $role ) {
1381  $groupPermissions = $this->options->get( 'GroupPermissions' );
1382  $revokePermissions = $this->options->get( 'RevokePermissions' );
1383  return isset( $groupPermissions[$group][$role] ) && $groupPermissions[$group][$role] &&
1384  !( isset( $revokePermissions[$group][$role] ) && $revokePermissions[$group][$role] );
1385  }
1386 
1395  public function getGroupPermissions( $groups ) {
1396  $rights = [];
1397  // grant every granted permission first
1398  foreach ( $groups as $group ) {
1399  if ( isset( $this->options->get( 'GroupPermissions' )[$group] ) ) {
1400  $rights = array_merge( $rights,
1401  // array_filter removes empty items
1402  array_keys( array_filter( $this->options->get( 'GroupPermissions' )[$group] ) ) );
1403  }
1404  }
1405  // now revoke the revoked permissions
1406  foreach ( $groups as $group ) {
1407  if ( isset( $this->options->get( 'RevokePermissions' )[$group] ) ) {
1408  $rights = array_diff( $rights,
1409  array_keys( array_filter( $this->options->get( 'RevokePermissions' )[$group] ) ) );
1410  }
1411  }
1412  return array_unique( $rights );
1413  }
1414 
1423  public function getGroupsWithPermission( $role ) {
1424  $allowedGroups = [];
1425  foreach ( array_keys( $this->options->get( 'GroupPermissions' ) ) as $group ) {
1426  if ( $this->groupHasPermission( $group, $role ) ) {
1427  $allowedGroups[] = $group;
1428  }
1429  }
1430  return $allowedGroups;
1431  }
1432 
1448  public function isEveryoneAllowed( $right ) {
1449  // Use the cached results, except in unit tests which rely on
1450  // being able change the permission mid-request
1451  if ( isset( $this->cachedRights[$right] ) ) {
1452  return $this->cachedRights[$right];
1453  }
1454 
1455  if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
1456  || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
1457  $this->cachedRights[$right] = false;
1458  return false;
1459  }
1460 
1461  // If it's revoked anywhere, then everyone doesn't have it
1462  foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
1463  if ( isset( $rights[$right] ) && $rights[$right] ) {
1464  $this->cachedRights[$right] = false;
1465  return false;
1466  }
1467  }
1468 
1469  // Remove any rights that aren't allowed to the global-session user,
1470  // unless there are no sessions for this endpoint.
1471  if ( !defined( 'MW_NO_SESSION' ) ) {
1472 
1473  // XXX: think what could be done with the below
1474  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
1475  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
1476  $this->cachedRights[$right] = false;
1477  return false;
1478  }
1479  }
1480 
1481  // Allow extensions to say false
1482  if ( !$this->hookRunner->onUserIsEveryoneAllowed( $right ) ) {
1483  $this->cachedRights[$right] = false;
1484  return false;
1485  }
1486 
1487  $this->cachedRights[$right] = true;
1488  return true;
1489  }
1490 
1498  public function getAllPermissions() {
1499  if ( $this->allRights === null ) {
1500  if ( count( $this->options->get( 'AvailableRights' ) ) ) {
1501  $this->allRights = array_unique( array_merge(
1502  $this->coreRights,
1503  $this->options->get( 'AvailableRights' )
1504  ) );
1505  } else {
1506  $this->allRights = $this->coreRights;
1507  }
1508  $this->hookRunner->onUserGetAllRights( $this->allRights );
1509  }
1510  return $this->allRights;
1511  }
1512 
1519  private function isNamespaceProtected( $index, UserIdentity $user ) {
1520  $namespaceProtection = $this->options->get( 'NamespaceProtection' );
1521  if ( isset( $namespaceProtection[$index] ) ) {
1522  return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
1523  }
1524  return false;
1525  }
1526 
1535  public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
1536  if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
1537  // All levels are valid if there's no namespace restriction.
1538  // But still filter by user, if necessary
1539  $levels = $this->options->get( 'RestrictionLevels' );
1540  if ( $user ) {
1541  $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
1542  $right = $level;
1543  if ( $right == 'sysop' ) {
1544  $right = 'editprotected'; // BC
1545  }
1546  if ( $right == 'autoconfirmed' ) {
1547  $right = 'editsemiprotected'; // BC
1548  }
1549  return $this->userHasRight( $user, $right );
1550  } ) );
1551  }
1552  return $levels;
1553  }
1554 
1555  // $wgNamespaceProtection can require one or more rights to edit the namespace, which
1556  // may be satisfied by membership in multiple groups each giving a subset of those rights.
1557  // A restriction level is redundant if, for any one of the namespace rights, all groups
1558  // giving that right also give the restriction level's right. Or, conversely, a
1559  // restriction level is not redundant if, for every namespace right, there's at least one
1560  // group giving that right without the restriction level's right.
1561  //
1562  // First, for each right, get a list of groups with that right.
1563  $namespaceRightGroups = [];
1564  foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
1565  if ( $right == 'sysop' ) {
1566  $right = 'editprotected'; // BC
1567  }
1568  if ( $right == 'autoconfirmed' ) {
1569  $right = 'editsemiprotected'; // BC
1570  }
1571  if ( $right != '' ) {
1572  $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
1573  }
1574  }
1575 
1576  // Now, go through the protection levels one by one.
1577  $usableLevels = [ '' ];
1578  foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
1579  $right = $level;
1580  if ( $right == 'sysop' ) {
1581  $right = 'editprotected'; // BC
1582  }
1583  if ( $right == 'autoconfirmed' ) {
1584  $right = 'editsemiprotected'; // BC
1585  }
1586 
1587  if ( $right != '' &&
1588  !isset( $namespaceRightGroups[$right] ) &&
1589  ( !$user || $this->userHasRight( $user, $right ) )
1590  ) {
1591  // Do any of the namespace rights imply the restriction right? (see explanation above)
1592  foreach ( $namespaceRightGroups as $groups ) {
1593  if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
1594  // Yes, this one does.
1595  continue 2;
1596  }
1597  }
1598  // No, keep the restriction level
1599  $usableLevels[] = $level;
1600  }
1601  }
1602 
1603  return $usableLevels;
1604  }
1605 
1614  private function userCanEditRawHtmlPage( UserIdentity $user ) {
1615  return $this->userHasAllRights( $user, 'editsitecss', 'editsitejs' );
1616  }
1617 
1632  public function addTemporaryUserRights( UserIdentity $user, $rights ) {
1633  $userId = $user->getId();
1634  $nextKey = count( $this->temporaryUserRights[$userId] ?? [] );
1635  $this->temporaryUserRights[$userId][$nextKey] = (array)$rights;
1636  return new ScopedCallback( function () use ( $userId, $nextKey ) {
1637  unset( $this->temporaryUserRights[$userId][$nextKey] );
1638  } );
1639  }
1640 
1651  public function overrideUserRightsForTesting( $user, $rights = [] ) {
1652  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
1653  throw new Exception( __METHOD__ . ' can not be called outside of tests' );
1654  }
1655  $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
1656  is_array( $rights ) ? $rights : [ $rights ];
1657  }
1658 
1659 }
MediaWiki\Permissions\PermissionManager\$cachedRights
bool[] $cachedRights
Cached rights for isEveryoneAllowed, [ right => allowed ].
Definition: PermissionManager.php:107
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
MediaWiki\Permissions\PermissionManager\checkCascadingSourcesRestrictions
checkCascadingSourcesRestrictions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check restrictions on cascading pages.
Definition: PermissionManager.php:892
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:2922
MediaWiki\Permissions\PermissionManager\checkSpecialsAndNSPermissions
checkSpecialsAndNSPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check permissions on special pages & namespaces.
Definition: PermissionManager.php:1050
MediaWiki\Permissions\PermissionManager\getPermissionErrorsInternal
getPermissionErrorsInternal( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE, $short=false)
Can $user perform $action on a page? This is an internal function, with multiple levels of checks dep...
Definition: PermissionManager.php:362
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:4374
MediaWiki\Permissions\PermissionManager\userCan
userCan( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE)
Can $user perform $action on a page?
Definition: PermissionManager.php:242
MediaWiki\Block\BlockErrorFormatter
A service class for getting formatted information about a block.
Definition: BlockErrorFormatter.php:34
MediaWiki\SpecialPage\SpecialPageFactory
Factory for handling the special page list and generating SpecialPage objects.
Definition: SpecialPageFactory.php:65
MessageSpecifier
Stable for implementing.
Definition: MessageSpecifier.php:24
Action\exists
static exists(string $name)
Check if a given action is recognised, even if it's disabled.
Definition: Action.php:206
MediaWiki\Permissions\PermissionManager\missingPermissionError
missingPermissionError( $action, $short)
Get a description array when the user doesn't have the right to perform $action (i....
Definition: PermissionManager.php:599
MediaWiki\Permissions\PermissionManager\checkPermissionHooks
checkPermissionHooks( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check various permission hooks.
Definition: PermissionManager.php:435
NS_FILE
const NS_FILE
Definition: Defines.php:75
MediaWiki\Permissions\PermissionManager\userHasRight
userHasRight(UserIdentity $user, $action='')
Testing a permission.
Definition: PermissionManager.php:1238
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1219
MediaWiki\Permissions\PermissionManager\$coreRights
$coreRights
Array of Strings Core rights.
Definition: PermissionManager.php:115
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:92
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:594
Action\factory
static factory(?string $action, Page $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:115
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
Revision\RevisionLookup
Service for looking up page revisions.
Definition: RevisionLookup.php:38
Action
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition: Action.php:43
NS_MAIN
const NS_MAIN
Definition: Defines.php:69
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:58
MediaWiki\Permissions\PermissionManager\invalidateUsersRightsCache
invalidateUsersRightsCache( $user=null)
Clears users permissions cache, if specific user is provided it tries to clear permissions cache only...
Definition: PermissionManager.php:1346
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:3648
MediaWiki\Permissions\PermissionManager\resultToError
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition: PermissionManager.php:475
MediaWiki\Permissions\PermissionManager\$revisionLookup
RevisionLookup $revisionLookup
Definition: PermissionManager.php:83
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:25
MediaWiki\Permissions\PermissionManager\$hookRunner
HookRunner $hookRunner
Definition: PermissionManager.php:95
MediaWiki\User\UserIdentity\isRegistered
isRegistered()
MediaWiki\Permissions\PermissionManager\checkUserConfigPermissions
checkUserConfigPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check CSS/JSON/JS sub-page permissions.
Definition: PermissionManager.php:1150
MediaWiki\Permissions\PermissionManager\$usersRights
string[][] $usersRights
Cached user rights.
Definition: PermissionManager.php:98
MediaWiki\Permissions\PermissionManager\$allRights
string[] null $allRights
Cached results of getAllRights()
Definition: PermissionManager.php:89
$wgLang
$wgLang
Definition: Setup.php:776
MediaWiki\Permissions\PermissionManager\isSameSpecialPage
isSameSpecialPage( $name, LinkTarget $page)
Returns true if this title resolves to the named special page.
Definition: PermissionManager.php:618
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:1913
MediaWiki\Permissions\PermissionManager\getRightsCacheKey
getRightsCacheKey(UserIdentity $user)
Gets a unique key for user rights cache.
Definition: PermissionManager.php:1362
MediaWiki\Permissions\PermissionManager\getNamespaceRestrictionLevels
getNamespaceRestrictionLevels( $index, UserIdentity $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
Definition: PermissionManager.php:1535
MediaWiki\Permissions\PermissionManager\isNamespaceProtected
isNamespaceProtected( $index, UserIdentity $user)
Determines if $user is unable to edit pages in namespace because it has been protected.
Definition: PermissionManager.php:1519
Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:60
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:38
MediaWiki\Permissions\PermissionManager\checkPageRestrictions
checkPageRestrictions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check against page_restrictions table requirements on this page.
Definition: PermissionManager.php:842
MediaWiki\Permissions\PermissionManager\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: PermissionManager.php:80
MediaWiki\Permissions\PermissionManager\$nsInfo
NamespaceInfo $nsInfo
Definition: PermissionManager.php:86
MediaWiki\Permissions\PermissionManager\checkQuickPermissions
checkQuickPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Permissions checks that fail most often, and which are easiest to test.
Definition: PermissionManager.php:742
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:83
RequestContext
Group all the pieces relevant to the context of a request into one instance @newable.
Definition: RequestContext.php:38
$wgDeleteRevisionsLimit
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
Definition: DefaultSettings.php:5956
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1795
MediaWiki\Permissions\PermissionManager\$blockErrorFormatter
BlockErrorFormatter $blockErrorFormatter
Definition: PermissionManager.php:92
MediaWiki\Permissions\PermissionManager\checkUserBlock
checkUserBlock( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check that the user isn't blocked from editing.
Definition: PermissionManager.php:645
MediaWiki\Permissions\PermissionManager\checkActionPermissions
checkActionPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check action permissions not already checked in checkQuickPermissions.
Definition: PermissionManager.php:949
MediaWiki\Permissions\PermissionManager\isEveryoneAllowed
isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: PermissionManager.php:1448
MediaWiki\Permissions\PermissionManager\checkReadPermissions
checkReadPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check that the user is allowed to read this page.
Definition: PermissionManager.php:511
MediaWiki\Permissions\PermissionManager\getUserPermissions
getUserPermissions(UserIdentity $user)
Get the permissions this user has.
Definition: PermissionManager.php:1290
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
Get the "global" session.
Definition: SessionManager.php:114
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:49
$content
$content
Definition: router.php:76
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:864
MediaWiki\Permissions\PermissionManager\getAllPermissions
getAllPermissions()
Get a list of all available permissions.
Definition: PermissionManager.php:1498
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:41
MediaWiki\Permissions\PermissionManager\getGroupsWithPermission
getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: PermissionManager.php:1423
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:52
MediaWiki\Permissions\PermissionManager\quickUserCan
quickUserCan( $action, User $user, LinkTarget $page)
A convenience method for calling PermissionManager::userCan with PermissionManager::RIGOR_QUICK.
Definition: PermissionManager.php:261
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
MediaWiki\Permissions\PermissionManager\addTemporaryUserRights
addTemporaryUserRights(UserIdentity $user, $rights)
Add temporary user rights, only valid for the current scope.
Definition: PermissionManager.php:1632
IContextSource\getUser
getUser()
MediaWiki\Permissions\PermissionManager\getPermissionErrors
getPermissionErrors( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on a page?
Definition: PermissionManager.php:282
MediaWiki\Permissions\PermissionManager\userCanEditRawHtmlPage
userCanEditRawHtmlPage(UserIdentity $user)
Check if user is allowed to edit sitewide pages that contain raw HTML.
Definition: PermissionManager.php:1614
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:453
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:281
MediaWiki\Permissions\PermissionManager\checkSiteConfigPermissions
checkSiteConfigPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check sitewide CSS/JSON/JS permissions.
Definition: PermissionManager.php:1094
MediaWiki\Permissions\PermissionManager\groupHasPermission
groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: PermissionManager.php:1380
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:45
Title
Represents a title within MediaWiki.
Definition: Title.php:42
MediaWiki\User\UserIdentity\getId
getId()
MediaWiki\Permissions\PermissionManager\__construct
__construct(ServiceOptions $options, SpecialPageFactory $specialPageFactory, RevisionLookup $revisionLookup, NamespaceInfo $nsInfo, BlockErrorFormatter $blockErrorFormatter, HookContainer $hookContainer)
Definition: PermissionManager.php:206
MediaWiki\Permissions\PermissionManager\userHasAllRights
userHasAllRights(UserIdentity $user,... $actions)
Check if user is allowed to make all actions.
Definition: PermissionManager.php:1272
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:3950
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:4430
NS_USER
const NS_USER
Definition: Defines.php:71
IContextSource\getRequest
getRequest()
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:44
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:35
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:77
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:570
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:46
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:40
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:55
MediaWiki\Permissions\PermissionManager\$temporaryUserRights
string[][][] $temporaryUserRights
Temporary user rights, valid for the current request only.
Definition: PermissionManager.php:104
MediaWiki\Permissions\PermissionManager\userHasAnyRight
userHasAnyRight(UserIdentity $user,... $actions)
Check if user is allowed to make any action.
Definition: PermissionManager.php:1255
MediaWiki\Permissions\PermissionManager\getGroupPermissions
getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: PermissionManager.php:1395
MediaWiki\Permissions\PermissionManager\$options
ServiceOptions $options
Definition: PermissionManager.php:60
IContextSource\getLanguage
getLanguage()
MediaWiki\Permissions\PermissionManager\overrideUserRightsForTesting
overrideUserRightsForTesting( $user, $rights=[])
Overrides user permissions cache.
Definition: PermissionManager.php:1651
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:62
MediaWiki\Permissions
Article\newFromTitle
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:194
MediaWiki\Permissions\PermissionManager\isBlockedFrom
isBlockedFrom(User $user, LinkTarget $page, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: PermissionManager.php:316