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;
39 use NamespaceInfo;
40 use RequestContext;
41 use SpecialPage;
42 use Title;
43 use User;
44 use UserCache;
45 use Wikimedia\ScopedCallback;
46 
54 
56  public const RIGOR_QUICK = 'quick';
57 
59  public const RIGOR_FULL = 'full';
60 
62  public const RIGOR_SECURE = 'secure';
63 
67  public const CONSTRUCTOR_OPTIONS = [
68  'WhitelistRead',
69  'WhitelistReadRegexp',
70  'EmailConfirmToEdit',
71  'BlockDisablesLogin',
72  'EnablePartialActionBlocks',
73  'GroupPermissions',
74  'RevokePermissions',
75  'AvailableRights',
76  'NamespaceProtection',
77  'RestrictionLevels',
78  'DeleteRevisionsLimit',
79  ];
80 
82  private $options;
83 
86 
88  private $revisionLookup;
89 
91  private $nsInfo;
92 
95 
98 
100  private $allRights;
101 
104 
106  private $hookRunner;
107 
109  private $userCache;
110 
112  private $usersRights = [];
113 
118  private $temporaryUserRights = [];
119 
121  private $cachedRights = [];
122 
129  private $coreRights = [
130  'apihighlimits',
131  'applychangetags',
132  'autoconfirmed',
133  'autocreateaccount',
134  'autopatrol',
135  'bigdelete',
136  'block',
137  'blockemail',
138  'bot',
139  'browsearchive',
140  'changetags',
141  'createaccount',
142  'createpage',
143  'createtalk',
144  'delete',
145  'delete-redirect',
146  'deletechangetags',
147  'deletedhistory',
148  'deletedtext',
149  'deletelogentry',
150  'deleterevision',
151  'edit',
152  'editcontentmodel',
153  'editinterface',
154  'editprotected',
155  'editmyoptions',
156  'editmyprivateinfo',
157  'editmyusercss',
158  'editmyuserjson',
159  'editmyuserjs',
160  'editmyuserjsredirect',
161  'editmywatchlist',
162  'editsemiprotected',
163  'editsitecss',
164  'editsitejson',
165  'editsitejs',
166  'editusercss',
167  'edituserjson',
168  'edituserjs',
169  'hideuser',
170  'import',
171  'importupload',
172  'ipblock-exempt',
173  'managechangetags',
174  'markbotedits',
175  'mergehistory',
176  'minoredit',
177  'move',
178  'movefile',
179  'move-categorypages',
180  'move-rootuserpages',
181  'move-subpages',
182  'nominornewtalk',
183  'noratelimit',
184  'override-export-depth',
185  'pagelang',
186  'patrol',
187  'patrolmarks',
188  'protect',
189  'purge',
190  'read',
191  'reupload',
192  'reupload-own',
193  'reupload-shared',
194  'rollback',
195  'sendemail',
196  'siteadmin',
197  'suppressionlog',
198  'suppressredirect',
199  'suppressrevision',
200  'unblockself',
201  'undelete',
202  'unwatchedpages',
203  'upload',
204  'upload_by_url',
205  'userrights',
206  'userrights-interwiki',
207  'viewmyprivateinfo',
208  'viewmywatchlist',
209  'viewsuppressed',
210  'writeapi',
211  ];
212 
224  public function __construct(
232  HookContainer $hookContainer,
234  ) {
235  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
236  $this->options = $options;
237  $this->specialPageFactory = $specialPageFactory;
238  $this->revisionLookup = $revisionLookup;
239  $this->nsInfo = $nsInfo;
240  $this->groupPermissionsLookup = $groupPermissionsLookup;
241  $this->userGroupManager = $userGroupManager;
242  $this->blockErrorFormatter = $blockErrorFormatter;
243  $this->hookRunner = new HookRunner( $hookContainer );
244  $this->userCache = $userCache;
245  }
246 
264  public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
265  return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
266  }
267 
283  public function quickUserCan( $action, User $user, LinkTarget $page ) {
284  return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
285  }
286 
304  public function getPermissionErrors(
305  $action,
306  User $user,
307  LinkTarget $page,
308  $rigor = self::RIGOR_SECURE,
309  $ignoreErrors = []
310  ) {
311  $errors = $this->getPermissionErrorsInternal( $action, $user, $page, $rigor );
312 
313  // Remove the errors being ignored.
314  foreach ( $errors as $index => $error ) {
315  $errKey = is_array( $error ) ? $error[0] : $error;
316 
317  if ( in_array( $errKey, $ignoreErrors ) ) {
318  unset( $errors[$index] );
319  }
320  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
321  unset( $errors[$index] );
322  }
323  }
324 
325  return $errors;
326  }
327 
338  public function isBlockedFrom( User $user, $page, $fromReplica = false ) {
339  $block = $user->getBlock( $fromReplica );
340  if ( !$block ) {
341  return false;
342  }
343 
344  if ( $page instanceof PageIdentity ) {
346  } else {
348  }
349 
350  $blocked = $user->isHidden();
351  if ( !$blocked ) {
352  // Special handling for a user's own talk page. The block is not aware
353  // of the user, so this must be done here.
354  if ( $title->equals( $user->getTalkPage() ) ) {
355  $blocked = $block->appliesToUsertalk( $title );
356  } else {
357  $blocked = $block->appliesToTitle( $title );
358  }
359  }
360 
361  // only for the purpose of the hook. We really don't need this here.
362  $allowUsertalk = $block->isUsertalkEditAllowed();
363 
364  // Allow extensions to let a blocked user access a particular page
365  $this->hookRunner->onUserIsBlockedFrom( $user, $title, $blocked, $allowUsertalk );
366 
367  return $blocked;
368  }
369 
387  private function getPermissionErrorsInternal(
388  $action,
389  User $user,
390  LinkTarget $page,
391  $rigor = self::RIGOR_SECURE,
392  $short = false
393  ) {
394  if ( !in_array( $rigor, [ self::RIGOR_QUICK, self::RIGOR_FULL, self::RIGOR_SECURE ] ) ) {
395  throw new Exception( "Invalid rigor parameter '$rigor'." );
396  }
397 
398  # Read has special handling
399  if ( $action == 'read' ) {
400  $checks = [
401  'checkPermissionHooks',
402  'checkReadPermissions',
403  'checkUserBlock', // for wgBlockDisablesLogin
404  ];
405  # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
406  # or checkUserConfigPermissions here as it will lead to duplicate
407  # error messages. This is okay to do since anywhere that checks for
408  # create will also check for edit, and those checks are called for edit.
409  } elseif ( $action == 'create' ) {
410  $checks = [
411  'checkQuickPermissions',
412  'checkPermissionHooks',
413  'checkPageRestrictions',
414  'checkCascadingSourcesRestrictions',
415  'checkActionPermissions',
416  'checkUserBlock'
417  ];
418  } else {
419  $checks = [
420  'checkQuickPermissions',
421  'checkPermissionHooks',
422  'checkSpecialsAndNSPermissions',
423  'checkSiteConfigPermissions',
424  'checkUserConfigPermissions',
425  'checkPageRestrictions',
426  'checkCascadingSourcesRestrictions',
427  'checkActionPermissions',
428  'checkUserBlock'
429  ];
430 
431  // Exclude checkUserConfigPermissions on actions that cannot change the
432  // content of the configuration pages.
433  $skipUserConfigActions = [
434  // Allow patrolling per T21818
435  'patrol',
436 
437  // Allow admins and oversighters to delete. For user pages we want to avoid the
438  // situation where an unprivileged user can post abusive content on
439  // their subpages and only very highly privileged users could remove it.
440  // See T200176.
441  'delete',
442  'deleterevision',
443  'suppressrevision',
444 
445  // Allow admins and oversighters to view deleted content, even if they
446  // cannot restore it. See T202989
447  'deletedhistory',
448  'deletedtext',
449  'viewsuppressed',
450  ];
451 
452  if ( in_array( $action, $skipUserConfigActions, true ) ) {
453  $checks = array_diff(
454  $checks,
455  [ 'checkUserConfigPermissions' ]
456  );
457  // Reset numbering
458  $checks = array_values( $checks );
459  }
460  }
461 
462  $errors = [];
463  foreach ( $checks as $method ) {
464  $errors = $this->$method( $action, $user, $errors, $rigor, $short, $page );
465 
466  if ( $short && $errors !== [] ) {
467  break;
468  }
469  }
470  // remove duplicate errors
471  $errors = array_unique( $errors, SORT_REGULAR );
472 
473  return $errors;
474  }
475 
492  private function checkPermissionHooks(
493  $action,
494  User $user,
495  $errors,
496  $rigor,
497  $short,
498  LinkTarget $page
499  ) {
500  // TODO: remove when LinkTarget usage will expand further
501  $title = Title::newFromLinkTarget( $page );
502  // Use getUserPermissionsErrors instead
503  $result = '';
504  if ( !$this->hookRunner->onUserCan( $title, $user, $action, $result ) ) {
505  return $result ? [] : [ [ 'badaccess-group0' ] ];
506  }
507  // Check getUserPermissionsErrors hook
508  if ( !$this->hookRunner->onGetUserPermissionsErrors( $title, $user, $action, $result ) ) {
509  $errors = $this->resultToError( $errors, $result );
510  }
511  // Check getUserPermissionsErrorsExpensive hook
512  if (
513  $rigor !== self::RIGOR_QUICK
514  && !( $short && count( $errors ) > 0 )
515  && !$this->hookRunner->onGetUserPermissionsErrorsExpensive(
516  $title, $user, $action, $result )
517  ) {
518  $errors = $this->resultToError( $errors, $result );
519  }
520 
521  return $errors;
522  }
523 
532  private function resultToError( $errors, $result ) {
533  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
534  // A single array representing an error
535  $errors[] = $result;
536  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
537  // A nested array representing multiple errors
538  $errors = array_merge( $errors, $result );
539  } elseif ( $result !== '' && is_string( $result ) ) {
540  // A string representing a message-id
541  $errors[] = [ $result ];
542  } elseif ( $result instanceof MessageSpecifier ) {
543  // A message specifier representing an error
544  $errors[] = [ $result ];
545  } elseif ( $result === false ) {
546  // a generic "We don't want them to do that"
547  $errors[] = [ 'badaccess-group0' ];
548  }
549  return $errors;
550  }
551 
568  private function checkReadPermissions(
569  $action,
570  User $user,
571  $errors,
572  $rigor,
573  $short,
574  LinkTarget $page
575  ) {
576  // TODO: remove when LinkTarget usage will expand further
577  $title = Title::newFromLinkTarget( $page );
578 
579  $whiteListRead = $this->options->get( 'WhitelistRead' );
580  $allowed = false;
581  if ( $this->isEveryoneAllowed( 'read' ) ) {
582  # Shortcut for public wikis, allows skipping quite a bit of code
583  $allowed = true;
584  } elseif ( $this->userHasRight( $user, 'read' ) ) {
585  # If the user is allowed to read pages, he is allowed to read all pages
586  $allowed = true;
587  } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
588  || $this->isSameSpecialPage( 'PasswordReset', $title )
589  || $this->isSameSpecialPage( 'Userlogout', $title )
590  ) {
591  # Always grant access to the login page.
592  # Even anons need to be able to log in.
593  $allowed = true;
594  } elseif ( $this->isSameSpecialPage( 'RunJobs', $title ) ) {
595  # relies on HMAC key signature alone
596  $allowed = true;
597  } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
598  # Time to check the whitelist
599  # Only do these checks is there's something to check against
600  $name = $title->getPrefixedText();
601  $dbName = $title->getPrefixedDBkey();
602 
603  // Check for explicit whitelisting with and without underscores
604  if ( in_array( $name, $whiteListRead, true )
605  || in_array( $dbName, $whiteListRead, true )
606  ) {
607  $allowed = true;
608  } elseif ( $title->getNamespace() === NS_MAIN ) {
609  # Old settings might have the title prefixed with
610  # a colon for main-namespace pages
611  if ( in_array( ':' . $name, $whiteListRead ) ) {
612  $allowed = true;
613  }
614  } elseif ( $title->isSpecialPage() ) {
615  # If it's a special page, ditch the subpage bit and check again
616  $name = $title->getDBkey();
617  list( $name, /* $subpage */ ) =
618  $this->specialPageFactory->resolveAlias( $name );
619  if ( $name ) {
620  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
621  if ( in_array( $pure, $whiteListRead, true ) ) {
622  $allowed = true;
623  }
624  }
625  }
626  }
627 
628  $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
629  if ( !$allowed && is_array( $whitelistReadRegexp )
630  && !empty( $whitelistReadRegexp )
631  ) {
632  $name = $title->getPrefixedText();
633  // Check for regex whitelisting
634  foreach ( $whitelistReadRegexp as $listItem ) {
635  if ( preg_match( $listItem, $name ) ) {
636  $allowed = true;
637  break;
638  }
639  }
640  }
641 
642  if ( !$allowed ) {
643  # If the title is not whitelisted, give extensions a chance to do so...
644  $this->hookRunner->onTitleReadWhitelist( $title, $user, $allowed );
645  if ( !$allowed ) {
646  $errors[] = $this->missingPermissionError( $action, $short );
647  }
648  }
649 
650  return $errors;
651  }
652 
661  private function missingPermissionError( $action, $short ) {
662  // We avoid expensive display logic for quickUserCan's and such
663  if ( $short ) {
664  return [ 'badaccess-group0' ];
665  }
666 
667  // TODO: it would be a good idea to replace the method below with something else like
668  // maybe callback injection
669  return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
670  }
671 
680  private function isSameSpecialPage( $name, LinkTarget $page ) {
681  if ( $page->getNamespace() === NS_SPECIAL ) {
682  list( $thisName, /* $subpage */ ) =
683  $this->specialPageFactory->resolveAlias( $page->getDBkey() );
684  if ( $name == $thisName ) {
685  return true;
686  }
687  }
688  return false;
689  }
690 
707  private function checkUserBlock(
708  $action,
709  User $user,
710  $errors,
711  $rigor,
712  $short,
713  LinkTarget $page
714  ) {
715  // Unblocking handled in SpecialUnblock
716  if ( $rigor === self::RIGOR_QUICK || in_array( $action, [ 'unblock' ] ) ) {
717  return $errors;
718  }
719 
720  // Optimize for a very common case
721  if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
722  return $errors;
723  }
724 
725  if ( $this->options->get( 'EmailConfirmToEdit' )
726  && !$user->isEmailConfirmed()
727  && $action === 'edit'
728  ) {
729  $errors[] = [ 'confirmedittext' ];
730  }
731 
732  switch ( $rigor ) {
733  case self::RIGOR_SECURE:
734  $blockInfoFreshness = Authority::READ_LATEST;
735  $useReplica = false;
736  break;
737  case self::RIGOR_FULL:
738  $blockInfoFreshness = Authority::READ_NORMAL;
739  $useReplica = true;
740  break;
741  default:
742  $useReplica = true;
743  $blockInfoFreshness = Authority::READ_NORMAL;
744  }
745 
746  $block = $user->getBlock( $blockInfoFreshness );
747 
748  if ( $action === 'createaccount' ) {
749  $applicableBlock = null;
750  if ( $block && $block->appliesToRight( 'createaccount' ) ) {
751  $applicableBlock = $block;
752  }
753 
754  # T15611: if the IP address the user is trying to create an account from is
755  # blocked with createaccount disabled, prevent new account creation there even
756  # when the user is logged in
757  if ( !$this->userHasRight( $user, 'ipblock-exempt' ) ) {
758  $ipBlock = DatabaseBlock::newFromTarget(
759  null, $user->getRequest()->getIP()
760  );
761  if ( $ipBlock && $ipBlock->appliesToRight( 'createaccount' ) ) {
762  $applicableBlock = $ipBlock;
763  }
764  }
765  // @todo FIXME: Pass the relevant context into this function.
766  if ( $applicableBlock ) {
768  $message = $this->blockErrorFormatter->getMessage(
769  $applicableBlock,
770  $context->getUser(),
772  $context->getRequest()->getIP()
773  );
774  $errors[] = array_merge( [ $message->getKey() ], $message->getParams() );
775  return $errors;
776  }
777  }
778 
779  // If the user does not have a block, or the block they do have explicitly
780  // allows the action (like "read" or "upload").
781  if ( !$block || $block->appliesToRight( $action ) === false ) {
782  return $errors;
783  }
784 
785  // Determine if the user is blocked from this action on this page.
786  // What gets passed into this method is a user right, not an action name.
787  // There is no way to instantiate an action by restriction. However, this
788  // will get the action where the restriction is the same. This may result
789  // in actions being blocked that shouldn't be.
790  $actionObj = null;
791  if ( Action::exists( $action ) ) {
792  // TODO: this drags a ton of dependencies in, would be good to avoid Article
793  // instantiation and decouple it creating an ActionPermissionChecker interface
794  // Creating an action will perform several database queries to ensure that
795  // the action has not been overridden by the content type.
796  // FIXME: avoid use of RequestContext since it drags in User and Title dependencies
797  // probably we may use fake context object since it's unlikely that Action uses it
798  // anyway. It would be nice if we could avoid instantiating the Action at all.
799  $title = Title::newFromLinkTarget( $page, 'clone' );
801  $actionObj = Action::factory(
802  $action,
804  $context
805  );
806  // Ensure that the retrieved action matches the restriction.
807  if ( $actionObj && $actionObj->getRestriction() !== $action ) {
808  $actionObj = null;
809  }
810  }
811 
812  // If no action object is returned, assume that the action requires unblock
813  // which is the default.
814  if ( !$actionObj || $actionObj->requiresUnblock() ) {
815  if (
816  $this->isBlockedFrom( $user, $page, $useReplica ) ||
817  (
818  $this->options->get( 'EnablePartialActionBlocks' ) &&
819  $block->appliesToRight( $action )
820  )
821  ) {
822  // @todo FIXME: Pass the relevant context into this function.
824  $message = $this->blockErrorFormatter->getMessage(
825  $block,
826  $context->getUser(),
828  $context->getRequest()->getIP()
829  );
830  $errors[] = array_merge( [ $message->getKey() ], $message->getParams() );
831  }
832  }
833 
834  return $errors;
835  }
836 
853  private function checkQuickPermissions(
854  $action,
855  User $user,
856  $errors,
857  $rigor,
858  $short,
859  LinkTarget $page
860  ) {
861  // TODO: remove when LinkTarget usage will expand further
862  $title = Title::newFromLinkTarget( $page );
863 
864  if ( !$this->hookRunner->onTitleQuickPermissions( $title, $user, $action,
865  $errors, $rigor !== self::RIGOR_QUICK, $short )
866  ) {
867  return $errors;
868  }
869 
870  $isSubPage = $this->nsInfo->hasSubpages( $title->getNamespace() ) ?
871  strpos( $title->getText(), '/' ) !== false : false;
872 
873  if ( $action == 'create' ) {
874  if (
875  ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
876  !$this->userHasRight( $user, 'createtalk' ) ) ||
877  ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
878  !$this->userHasRight( $user, 'createpage' ) )
879  ) {
880  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
881  }
882  } elseif ( $action == 'move' ) {
883  if ( !$this->userHasRight( $user, 'move-rootuserpages' )
884  && $title->getNamespace() === NS_USER && !$isSubPage
885  ) {
886  // Show user page-specific message only if the user can move other pages
887  $errors[] = [ 'cant-move-user-page' ];
888  }
889 
890  // Check if user is allowed to move files if it's a file
891  if ( $title->getNamespace() === NS_FILE &&
892  !$this->userHasRight( $user, 'movefile' )
893  ) {
894  $errors[] = [ 'movenotallowedfile' ];
895  }
896 
897  // Check if user is allowed to move category pages if it's a category page
898  if ( $title->getNamespace() === NS_CATEGORY &&
899  !$this->userHasRight( $user, 'move-categorypages' )
900  ) {
901  $errors[] = [ 'cant-move-category-page' ];
902  }
903 
904  if ( !$this->userHasRight( $user, 'move' ) ) {
905  // User can't move anything
906  $userCanMove = $this->groupHasPermission( 'user', 'move' );
907  $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
908  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
909  // custom message if logged-in users without any special rights can move
910  $errors[] = [ 'movenologintext' ];
911  } else {
912  $errors[] = [ 'movenotallowed' ];
913  }
914  }
915  } elseif ( $action == 'move-target' ) {
916  if ( !$this->userHasRight( $user, 'move' ) ) {
917  // User can't move anything
918  $errors[] = [ 'movenotallowed' ];
919  } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
920  && $title->getNamespace() === NS_USER
921  && !$isSubPage
922  ) {
923  // Show user page-specific message only if the user can move other pages
924  $errors[] = [ 'cant-move-to-user-page' ];
925  } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
926  && $title->getNamespace() === NS_CATEGORY
927  ) {
928  // Show category page-specific message only if the user can move other pages
929  $errors[] = [ 'cant-move-to-category-page' ];
930  }
931  } elseif ( !$this->userHasRight( $user, $action ) ) {
932  $errors[] = $this->missingPermissionError( $action, $short );
933  }
934 
935  return $errors;
936  }
937 
956  private function checkPageRestrictions(
957  $action,
958  User $user,
959  $errors,
960  $rigor,
961  $short,
962  LinkTarget $page
963  ) {
964  // TODO: remove & rework upon further use of LinkTarget
965  $title = Title::newFromLinkTarget( $page );
966  foreach ( $title->getRestrictions( $action ) as $right ) {
967  // Backwards compatibility, rewrite sysop -> editprotected
968  if ( $right == 'sysop' ) {
969  $right = 'editprotected';
970  }
971  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
972  if ( $right == 'autoconfirmed' ) {
973  $right = 'editsemiprotected';
974  }
975  if ( $right == '' ) {
976  continue;
977  }
978  if ( !$this->userHasRight( $user, $right ) ) {
979  $errors[] = [ 'protectedpagetext', $right, $action ];
980  } elseif ( $title->areRestrictionsCascading() &&
981  !$this->userHasRight( $user, 'protect' )
982  ) {
983  $errors[] = [ 'protectedpagetext', 'protect', $action ];
984  }
985  }
986 
987  return $errors;
988  }
989 
1007  $action,
1008  UserIdentity $user,
1009  $errors,
1010  $rigor,
1011  $short,
1012  LinkTarget $page
1013  ) {
1014  // TODO: remove & rework upon further use of LinkTarget
1015  $title = Title::newFromLinkTarget( $page );
1016  if ( $rigor !== self::RIGOR_QUICK && !$title->isUserConfigPage() ) {
1017  list( $cascadingSources, $restrictions ) = $title->getCascadeProtectionSources();
1018  # Cascading protection depends on more than this page...
1019  # Several cascading protected pages may include this page...
1020  # Check each cascading level
1021  # This is only for protection restrictions, not for all actions
1022  if ( isset( $restrictions[$action] ) ) {
1023  foreach ( $restrictions[$action] as $right ) {
1024  // Backwards compatibility, rewrite sysop -> editprotected
1025  if ( $right == 'sysop' ) {
1026  $right = 'editprotected';
1027  }
1028  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
1029  if ( $right == 'autoconfirmed' ) {
1030  $right = 'editsemiprotected';
1031  }
1032  if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
1033  $wikiPages = '';
1035  foreach ( $cascadingSources as $wikiPage ) {
1036  $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
1037  }
1038  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $wikiPages, $action ];
1039  }
1040  }
1041  }
1042  }
1043 
1044  return $errors;
1045  }
1046 
1063  private function checkActionPermissions(
1064  $action,
1065  User $user,
1066  $errors,
1067  $rigor,
1068  $short,
1069  LinkTarget $page
1070  ) {
1071  global $wgLang;
1072 
1073  // TODO: remove & rework upon further use of LinkTarget
1074  $title = Title::newFromLinkTarget( $page );
1075 
1076  if ( $action == 'protect' ) {
1077  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
1078  // If they can't edit, they shouldn't protect.
1079  $errors[] = [ 'protect-cantedit' ];
1080  }
1081  } elseif ( $action == 'create' ) {
1082  $title_protection = $title->getTitleProtection();
1083  if ( $title_protection ) {
1084  if ( $title_protection['permission'] == ''
1085  || !$this->userHasRight( $user, $title_protection['permission'] )
1086  ) {
1087  $errors[] = [
1088  'titleprotected',
1089  $this->userCache->getProp( $title_protection['user'], 'name' ),
1090  $title_protection['reason']
1091  ];
1092  }
1093  }
1094  } elseif ( $action == 'move' ) {
1095  // Check for immobile pages
1096  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
1097  // Specific message for this case
1098  $nsText = $title->getNsText();
1099  if ( $nsText === '' ) {
1100  $nsText = wfMessage( 'blanknamespace' )->text();
1101  }
1102  $errors[] = [ 'immobile-source-namespace', $nsText ];
1103  } elseif ( !$title->isMovable() ) {
1104  // Less specific message for rarer cases
1105  $errors[] = [ 'immobile-source-page' ];
1106  }
1107  } elseif ( $action == 'move-target' ) {
1108  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
1109  $nsText = $title->getNsText();
1110  if ( $nsText === '' ) {
1111  $nsText = wfMessage( 'blanknamespace' )->text();
1112  }
1113  $errors[] = [ 'immobile-target-namespace', $nsText ];
1114  } elseif ( !$title->isMovable() ) {
1115  $errors[] = [ 'immobile-target-page' ];
1116  }
1117  } elseif ( $action == 'delete' || $action == 'delete-redirect' ) {
1118  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true, $title );
1119  if ( !$tempErrors ) {
1120  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
1121  $user, $tempErrors, $rigor, true, $title );
1122  }
1123  if ( $tempErrors ) {
1124  // If protection keeps them from editing, they shouldn't be able to delete.
1125  $errors[] = [ 'deleteprotected' ];
1126  }
1127  if ( $rigor !== self::RIGOR_QUICK
1128  && $action == 'delete'
1129  && $this->options->get( 'DeleteRevisionsLimit' )
1130  && !$this->userCan( 'bigdelete', $user, $title )
1131  && $title->isBigDeletion()
1132  ) {
1133  // NOTE: This check is deprecated since 1.37, see T288759
1134  $errors[] = [
1135  'delete-toobig',
1136  $wgLang->formatNum( $this->options->get( 'DeleteRevisionsLimit' ) )
1137  ];
1138  }
1139  } elseif ( $action === 'undelete' ) {
1140  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
1141  // Undeleting implies editing
1142  $errors[] = [ 'undelete-cantedit' ];
1143  }
1144  if ( !$title->exists()
1145  && count( $this->getPermissionErrorsInternal( 'create', $user, $title, $rigor, true ) )
1146  ) {
1147  // Undeleting where nothing currently exists implies creating
1148  $errors[] = [ 'undelete-cantcreate' ];
1149  }
1150  } elseif ( $action === 'edit' ) {
1151  if ( !$title->exists() ) {
1152  $errors = array_merge(
1153  $errors,
1155  'create',
1156  $user,
1157  $title,
1158  $rigor,
1159  true
1160  )
1161  );
1162  }
1163  }
1164  return $errors;
1165  }
1166 
1184  $action,
1185  UserIdentity $user,
1186  $errors,
1187  $rigor,
1188  $short,
1189  LinkTarget $page
1190  ) {
1191  // TODO: remove & rework upon further use of LinkTarget
1192  $title = Title::newFromLinkTarget( $page );
1193 
1194  # Only 'createaccount' can be performed on special pages,
1195  # which don't actually exist in the DB.
1196  if ( $title->getNamespace() === NS_SPECIAL && $action !== 'createaccount' ) {
1197  $errors[] = [ 'ns-specialprotected' ];
1198  }
1199 
1200  # Check $wgNamespaceProtection for restricted namespaces
1201  if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
1202  $ns = $title->getNamespace() === NS_MAIN ?
1203  wfMessage( 'nstab-main' )->text() : $title->getNsText();
1204  $errors[] = $title->getNamespace() === NS_MEDIAWIKI ?
1205  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
1206  }
1207 
1208  return $errors;
1209  }
1210 
1227  private function checkSiteConfigPermissions(
1228  $action,
1229  User $user,
1230  $errors,
1231  $rigor,
1232  $short,
1233  LinkTarget $page
1234  ) {
1235  // TODO: remove & rework upon further use of LinkTarget
1236  $title = Title::newFromLinkTarget( $page );
1237 
1238  if ( $action === 'patrol' ) {
1239  return $errors;
1240  }
1241 
1242  if ( in_array( $action, [ 'deletedhistory', 'deletedtext', 'viewsuppressed' ], true ) ) {
1243  // Allow admins and oversighters to view deleted content, even if they
1244  // cannot restore it. See T202989
1245  // Not using the same handling in `getPermissionErrorsInternal` as the checks
1246  // for skipping `checkUserConfigPermissions` since normal admins can delete
1247  // user scripts, but not sitedwide scripts
1248  return $errors;
1249  }
1250 
1251  // Sitewide CSS/JSON/JS/RawHTML changes, like all NS_MEDIAWIKI changes, also require the
1252  // editinterface right. That's implemented as a restriction so no check needed here.
1253  if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
1254  $errors[] = [ 'sitecssprotected', $action ];
1255  } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
1256  $errors[] = [ 'sitejsonprotected', $action ];
1257  } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
1258  $errors[] = [ 'sitejsprotected', $action ];
1259  }
1260  if ( $title->isRawHtmlMessage() && !$this->userCanEditRawHtmlPage( $user ) ) {
1261  $errors[] = [ 'siterawhtmlprotected', $action ];
1262  }
1263 
1264  return $errors;
1265  }
1266 
1283  private function checkUserConfigPermissions(
1284  $action,
1285  UserIdentity $user,
1286  $errors,
1287  $rigor,
1288  $short,
1289  LinkTarget $page
1290  ) {
1291  // TODO: remove & rework upon further use of LinkTarget
1292  $title = Title::newFromLinkTarget( $page );
1293 
1294  # Protect css/json/js subpages of user pages
1295  # XXX: this might be better using restrictions
1296  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $title->getText() ) ) {
1297  // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
1298  if (
1299  $title->isUserCssConfigPage()
1300  && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
1301  ) {
1302  $errors[] = [ 'mycustomcssprotected', $action ];
1303  } elseif (
1304  $title->isUserJsonConfigPage()
1305  && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
1306  ) {
1307  $errors[] = [ 'mycustomjsonprotected', $action ];
1308  } elseif (
1309  $title->isUserJsConfigPage()
1310  && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
1311  ) {
1312  $errors[] = [ 'mycustomjsprotected', $action ];
1313  } elseif (
1314  $title->isUserJsConfigPage()
1315  && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
1316  ) {
1317  // T207750 - do not allow users to edit a redirect if they couldn't edit the target
1318  $rev = $this->revisionLookup->getRevisionByTitle( $title );
1319  $content = $rev ? $rev->getContent( 'main', RevisionRecord::RAW ) : null;
1320  $target = $content ? $content->getUltimateRedirectTarget() : null;
1321  if ( $target && (
1322  !$target->inNamespace( NS_USER )
1323  || !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $target->getText() )
1324  ) ) {
1325  $errors[] = [ 'mycustomjsredirectprotected', $action ];
1326  }
1327  }
1328  } else {
1329  // Users need edituser* to edit others' CSS/JSON/JS subpages.
1330  // The checks to exclude deletion/suppression, which cannot be used for
1331  // attacks and should be excluded to avoid the situation where an
1332  // unprivileged user can post abusive content on their subpages
1333  // and only very highly privileged users could remove it,
1334  // are now a part of `getPermissionErrorsInternal` and this method isn't called.
1335  if (
1336  $title->isUserCssConfigPage()
1337  && !$this->userHasRight( $user, 'editusercss' )
1338  ) {
1339  $errors[] = [ 'customcssprotected', $action ];
1340  } elseif (
1341  $title->isUserJsonConfigPage()
1342  && !$this->userHasRight( $user, 'edituserjson' )
1343  ) {
1344  $errors[] = [ 'customjsonprotected', $action ];
1345  } elseif (
1346  $title->isUserJsConfigPage()
1347  && !$this->userHasRight( $user, 'edituserjs' )
1348  ) {
1349  $errors[] = [ 'customjsprotected', $action ];
1350  }
1351  }
1352 
1353  return $errors;
1354  }
1355 
1366  public function userHasRight( UserIdentity $user, $action = '' ) {
1367  if ( $action === '' ) {
1368  return true; // In the spirit of DWIM
1369  }
1370  // Use strict parameter to avoid matching numeric 0 accidentally inserted
1371  // by misconfiguration: 0 == 'foo'
1372  return in_array( $action, $this->getUserPermissions( $user ), true );
1373  }
1374 
1383  public function userHasAnyRight( UserIdentity $user, ...$actions ) {
1384  foreach ( $actions as $action ) {
1385  if ( $this->userHasRight( $user, $action ) ) {
1386  return true;
1387  }
1388  }
1389  return false;
1390  }
1391 
1400  public function userHasAllRights( UserIdentity $user, ...$actions ) {
1401  foreach ( $actions as $action ) {
1402  if ( !$this->userHasRight( $user, $action ) ) {
1403  return false;
1404  }
1405  }
1406  return true;
1407  }
1408 
1418  public function getUserPermissions( UserIdentity $user ) {
1419  $rightsCacheKey = $this->getRightsCacheKey( $user );
1420  if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1421  $userObj = User::newFromIdentity( $user );
1422  $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
1423  $this->userGroupManager->getUserEffectiveGroups( $user )
1424  );
1425  // Hook requires a full User object
1426  $this->hookRunner->onUserGetRights( $userObj, $this->usersRights[ $rightsCacheKey ] );
1427 
1428  // Deny any rights denied by the user's session, unless this
1429  // endpoint has no sessions.
1430  if ( !defined( 'MW_NO_SESSION' ) ) {
1431  // FIXME: $userObj->getRequest().. need to be replaced with something else
1432  $allowedRights = $userObj->getRequest()->getSession()->getAllowedUserRights();
1433  if ( $allowedRights !== null ) {
1434  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1435  $this->usersRights[ $rightsCacheKey ],
1436  $allowedRights
1437  );
1438  }
1439  }
1440 
1441  // Hook requires a full User object
1442  $this->hookRunner->onUserGetRightsRemove(
1443  $userObj, $this->usersRights[ $rightsCacheKey ] );
1444  // Force reindexation of rights when a hook has unset one of them
1445  $this->usersRights[ $rightsCacheKey ] = array_values(
1446  array_unique( $this->usersRights[ $rightsCacheKey ] )
1447  );
1448 
1449  if (
1450  $userObj->isRegistered() &&
1451  $this->options->get( 'BlockDisablesLogin' ) &&
1452  $userObj->getBlock()
1453  ) {
1454  $anon = new User;
1455  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1456  $this->usersRights[ $rightsCacheKey ],
1457  $this->getUserPermissions( $anon )
1458  );
1459  }
1460  }
1461  $rights = $this->usersRights[ $rightsCacheKey ];
1462  foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
1463  $rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
1464  }
1465  return $rights;
1466  }
1467 
1476  public function invalidateUsersRightsCache( $user = null ) {
1477  if ( $user !== null ) {
1478  $rightsCacheKey = $this->getRightsCacheKey( $user );
1479  unset( $this->usersRights[ $rightsCacheKey ] );
1480  } else {
1481  $this->usersRights = [];
1482  }
1483  }
1484 
1490  private function getRightsCacheKey( UserIdentity $user ) {
1491  return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
1492  }
1493 
1509  public function groupHasPermission( $group, $role ) {
1510  return $this->groupPermissionsLookup->groupHasPermission( $group, $role );
1511  }
1512 
1522  public function getGroupPermissions( $groups ) {
1523  return $this->groupPermissionsLookup->getGroupPermissions( $groups );
1524  }
1525 
1535  public function getGroupsWithPermission( $role ) {
1536  return $this->groupPermissionsLookup->getGroupsWithPermission( $role );
1537  }
1538 
1554  public function isEveryoneAllowed( $right ) {
1555  // Use the cached results, except in unit tests which rely on
1556  // being able change the permission mid-request
1557  if ( isset( $this->cachedRights[$right] ) ) {
1558  return $this->cachedRights[$right];
1559  }
1560 
1561  if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
1562  || !$this->options->get( 'GroupPermissions' )['*'][$right]
1563  ) {
1564  $this->cachedRights[$right] = false;
1565  return false;
1566  }
1567 
1568  // If it's revoked anywhere, then everyone doesn't have it
1569  foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
1570  if ( isset( $rights[$right] ) && $rights[$right] ) {
1571  $this->cachedRights[$right] = false;
1572  return false;
1573  }
1574  }
1575 
1576  // Remove any rights that aren't allowed to the global-session user,
1577  // unless there are no sessions for this endpoint.
1578  if ( !defined( 'MW_NO_SESSION' ) ) {
1579 
1580  // XXX: think what could be done with the below
1581  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
1582  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
1583  $this->cachedRights[$right] = false;
1584  return false;
1585  }
1586  }
1587 
1588  // Allow extensions to say false
1589  if ( !$this->hookRunner->onUserIsEveryoneAllowed( $right ) ) {
1590  $this->cachedRights[$right] = false;
1591  return false;
1592  }
1593 
1594  $this->cachedRights[$right] = true;
1595  return true;
1596  }
1597 
1605  public function getAllPermissions() {
1606  if ( $this->allRights === null ) {
1607  if ( count( $this->options->get( 'AvailableRights' ) ) ) {
1608  $this->allRights = array_unique( array_merge(
1609  $this->coreRights,
1610  $this->options->get( 'AvailableRights' )
1611  ) );
1612  } else {
1613  $this->allRights = $this->coreRights;
1614  }
1615  $this->hookRunner->onUserGetAllRights( $this->allRights );
1616  }
1617  return $this->allRights;
1618  }
1619 
1626  private function isNamespaceProtected( $index, UserIdentity $user ) {
1627  $namespaceProtection = $this->options->get( 'NamespaceProtection' );
1628  if ( isset( $namespaceProtection[$index] ) ) {
1629  return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
1630  }
1631  return false;
1632  }
1633 
1642  public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
1643  if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
1644  // All levels are valid if there's no namespace restriction.
1645  // But still filter by user, if necessary
1646  $levels = $this->options->get( 'RestrictionLevels' );
1647  if ( $user ) {
1648  $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
1649  $right = $level;
1650  if ( $right == 'sysop' ) {
1651  $right = 'editprotected'; // BC
1652  }
1653  if ( $right == 'autoconfirmed' ) {
1654  $right = 'editsemiprotected'; // BC
1655  }
1656  return $this->userHasRight( $user, $right );
1657  } ) );
1658  }
1659  return $levels;
1660  }
1661 
1662  // $wgNamespaceProtection can require one or more rights to edit the namespace, which
1663  // may be satisfied by membership in multiple groups each giving a subset of those rights.
1664  // A restriction level is redundant if, for any one of the namespace rights, all groups
1665  // giving that right also give the restriction level's right. Or, conversely, a
1666  // restriction level is not redundant if, for every namespace right, there's at least one
1667  // group giving that right without the restriction level's right.
1668  //
1669  // First, for each right, get a list of groups with that right.
1670  $namespaceRightGroups = [];
1671  foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
1672  if ( $right == 'sysop' ) {
1673  $right = 'editprotected'; // BC
1674  }
1675  if ( $right == 'autoconfirmed' ) {
1676  $right = 'editsemiprotected'; // BC
1677  }
1678  if ( $right != '' ) {
1679  $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
1680  }
1681  }
1682 
1683  // Now, go through the protection levels one by one.
1684  $usableLevels = [ '' ];
1685  foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
1686  $right = $level;
1687  if ( $right == 'sysop' ) {
1688  $right = 'editprotected'; // BC
1689  }
1690  if ( $right == 'autoconfirmed' ) {
1691  $right = 'editsemiprotected'; // BC
1692  }
1693 
1694  if ( $right != '' &&
1695  !isset( $namespaceRightGroups[$right] ) &&
1696  ( !$user || $this->userHasRight( $user, $right ) )
1697  ) {
1698  // Do any of the namespace rights imply the restriction right? (see explanation above)
1699  foreach ( $namespaceRightGroups as $groups ) {
1700  if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
1701  // Yes, this one does.
1702  continue 2;
1703  }
1704  }
1705  // No, keep the restriction level
1706  $usableLevels[] = $level;
1707  }
1708  }
1709 
1710  return $usableLevels;
1711  }
1712 
1721  private function userCanEditRawHtmlPage( UserIdentity $user ) {
1722  return $this->userHasAllRights( $user, 'editsitecss', 'editsitejs' );
1723  }
1724 
1739  public function addTemporaryUserRights( UserIdentity $user, $rights ) {
1740  $userId = $user->getId();
1741  $nextKey = count( $this->temporaryUserRights[$userId] ?? [] );
1742  $this->temporaryUserRights[$userId][$nextKey] = (array)$rights;
1743  return new ScopedCallback( function () use ( $userId, $nextKey ) {
1744  unset( $this->temporaryUserRights[$userId][$nextKey] );
1745  } );
1746  }
1747 
1758  public function overrideUserRightsForTesting( $user, $rights = [] ) {
1759  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
1760  throw new Exception( __METHOD__ . ' can not be called outside of tests' );
1761  }
1762  $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
1763  is_array( $rights ) ? $rights : [ $rights ];
1764  }
1765 
1766 }
MediaWiki\Permissions\PermissionManager\$cachedRights
bool[] $cachedRights
Cached rights for isEveryoneAllowed, [ right => allowed ].
Definition: PermissionManager.php:121
MediaWiki\Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:64
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
MediaWiki\Permissions\PermissionManager\checkCascadingSourcesRestrictions
checkCascadingSourcesRestrictions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check restrictions on cascading pages.
Definition: PermissionManager.php:1006
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:2995
Action\factory
static factory(string $action, Article $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:85
UserCache
Definition: UserCache.php:32
MediaWiki\Permissions\PermissionManager\checkSpecialsAndNSPermissions
checkSpecialsAndNSPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check permissions on special pages & namespaces.
Definition: PermissionManager.php:1183
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:387
MediaWiki\Permissions\GroupPermissionsLookup
Definition: GroupPermissionsLookup.php:30
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:4140
MediaWiki\Permissions\PermissionManager\userCan
userCan( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE)
Can $user perform $action on a page?
Definition: PermissionManager.php:264
MediaWiki\Block\BlockErrorFormatter
A service class for getting formatted information about a block.
Definition: BlockErrorFormatter.php:36
MediaWiki\SpecialPage\SpecialPageFactory
Factory for handling the special page list and generating SpecialPage objects.
Definition: SpecialPageFactory.php:63
MessageSpecifier
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:121
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:661
MediaWiki\Permissions\PermissionManager\checkPermissionHooks
checkPermissionHooks( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check various permission hooks.
Definition: PermissionManager.php:492
MediaWiki\Permissions\PermissionManager\userHasRight
userHasRight(UserIdentity $user, $action='')
Testing a permission.
Definition: PermissionManager.php:1366
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1175
MediaWiki\Permissions\PermissionManager\$coreRights
$coreRights
Array of Strings Core rights.
Definition: PermissionManager.php:129
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:107
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:683
MediaWiki\User\UserIdentity\getId
getId( $wikiId=self::LOCAL)
User\getBlock
getBlock( $freshness=self::READ_NORMAL, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1940
$wgLang
$wgLang
Definition: Setup.php:834
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
MediaWiki\Revision\RevisionLookup
Service for looking up page revisions.
Definition: RevisionLookup.php:38
User\getRequest
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3084
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
Title\castFromPageIdentity
static castFromPageIdentity(?PageIdentity $pageIdentity)
Return a Title for a given PageIdentity.
Definition: Title.php:331
Action
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition: Action.php:43
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:53
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:50
MediaWiki\User\UserGroupManager
Managers user groups.
Definition: UserGroupManager.php:52
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:1476
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:3590
MediaWiki\Permissions\PermissionManager\resultToError
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition: PermissionManager.php:532
MediaWiki\Permissions\PermissionManager\$revisionLookup
RevisionLookup $revisionLookup
Definition: PermissionManager.php:88
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Permissions\PermissionManager\$hookRunner
HookRunner $hookRunner
Definition: PermissionManager.php:106
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:1283
MediaWiki\Permissions\PermissionManager\$usersRights
string[][] $usersRights
Cached user rights.
Definition: PermissionManager.php:112
MediaWiki\Permissions\PermissionManager\$allRights
string[] null $allRights
Cached results of getAllPermissions()
Definition: PermissionManager.php:100
MediaWiki\Permissions\PermissionManager\isSameSpecialPage
isSameSpecialPage( $name, LinkTarget $page)
Returns true if this title resolves to the named special page.
Definition: PermissionManager.php:680
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2075
MediaWiki\Permissions\PermissionManager\$groupPermissionsLookup
GroupPermissionsLookup $groupPermissionsLookup
Definition: PermissionManager.php:94
MediaWiki\Permissions\PermissionManager\getRightsCacheKey
getRightsCacheKey(UserIdentity $user)
Gets a unique key for user rights cache.
Definition: PermissionManager.php:1490
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:1642
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:1626
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:956
MediaWiki\Permissions\PermissionManager\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: PermissionManager.php:85
MediaWiki\Permissions\PermissionManager\$nsInfo
NamespaceInfo $nsInfo
Definition: PermissionManager.php:91
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:853
RequestContext
Group all the pieces relevant to the context of a request into one instance @newable.
Definition: RequestContext.php:41
MediaWiki\Permissions\PermissionManager\$blockErrorFormatter
BlockErrorFormatter $blockErrorFormatter
Definition: PermissionManager.php:103
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:707
MediaWiki\Permissions\PermissionManager\checkActionPermissions
checkActionPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check action permissions not already checked in checkQuickPermissions.
Definition: PermissionManager.php:1063
MediaWiki\Permissions\PermissionManager\isEveryoneAllowed
isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: PermissionManager.php:1554
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:568
MediaWiki\Block\DatabaseBlock\newFromTarget
static newFromTarget( $specificTarget, $vagueTarget=null, $fromPrimary=false)
Given a target and the target's type, get an existing block object if possible.
Definition: DatabaseBlock.php:863
MediaWiki\Permissions\PermissionManager\getUserPermissions
getUserPermissions(UserIdentity $user)
Get the permissions this user has.
Definition: PermissionManager.php:1418
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
If PHP's session_id() has been set, returns that session.
Definition: SessionManager.php:146
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:53
$content
$content
Definition: router.php:76
MediaWiki\Permissions\PermissionManager\getAllPermissions
getAllPermissions()
Get a list of all available permissions.
Definition: PermissionManager.php:1605
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:43
MediaWiki\Permissions\PermissionManager\getGroupsWithPermission
getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: PermissionManager.php:1535
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:83
MediaWiki\Permissions\PermissionManager\quickUserCan
quickUserCan( $action, User $user, LinkTarget $page)
A convenience method for calling PermissionManager::userCan with PermissionManager::RIGOR_QUICK.
Definition: PermissionManager.php:283
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:1739
MediaWiki\Permissions\PermissionManager\$userGroupManager
UserGroupManager $userGroupManager
Definition: PermissionManager.php:97
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:304
NS_USER
const NS_USER
Definition: Defines.php:66
MediaWiki\Permissions\PermissionManager\userCanEditRawHtmlPage
userCanEditRawHtmlPage(UserIdentity $user)
Check if user is allowed to edit sitewide pages that contain raw HTML.
Definition: PermissionManager.php:1721
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:294
MediaWiki\Permissions\PermissionManager\isBlockedFrom
isBlockedFrom(User $user, $page, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: PermissionManager.php:338
MediaWiki\Permissions\PermissionManager\checkSiteConfigPermissions
checkSiteConfigPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check sitewide CSS/JSON/JS permissions.
Definition: PermissionManager.php:1227
MediaWiki\Permissions\PermissionManager\groupHasPermission
groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: PermissionManager.php:1509
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:45
Title
Represents a title within MediaWiki.
Definition: Title.php:47
MediaWiki\Permissions\PermissionManager\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: PermissionManager.php:67
MediaWiki\Permissions\PermissionManager\userHasAllRights
userHasAllRights(UserIdentity $user,... $actions)
Check if user is allowed to make all actions.
Definition: PermissionManager.php:1400
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:3897
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
MediaWiki\Permissions\PermissionManager\$userCache
UserCache $userCache
Definition: PermissionManager.php:109
IContextSource\getRequest
getRequest()
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:35
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:557
Title\castFromLinkTarget
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
Definition: Title.php:318
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:49
NS_FILE
const NS_FILE
Definition: Defines.php:70
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:68
MediaWiki\Permissions\PermissionManager\$temporaryUserRights
string[][][] $temporaryUserRights
Temporary user rights, valid for the current request only.
Definition: PermissionManager.php:118
MediaWiki\Permissions\PermissionManager\userHasAnyRight
userHasAnyRight(UserIdentity $user,... $actions)
Check if user is allowed to make any action.
Definition: PermissionManager.php:1383
MediaWiki\Permissions\PermissionManager\getGroupPermissions
getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: PermissionManager.php:1522
MediaWiki\Permissions\PermissionManager\__construct
__construct(ServiceOptions $options, SpecialPageFactory $specialPageFactory, RevisionLookup $revisionLookup, NamespaceInfo $nsInfo, GroupPermissionsLookup $groupPermissionsLookup, UserGroupManager $userGroupManager, BlockErrorFormatter $blockErrorFormatter, HookContainer $hookContainer, UserCache $userCache)
Definition: PermissionManager.php:224
MediaWiki\Permissions\PermissionManager\$options
ServiceOptions $options
Definition: PermissionManager.php:82
IContextSource\getLanguage
getLanguage()
MediaWiki\Permissions\PermissionManager\overrideUserRightsForTesting
overrideUserRightsForTesting( $user, $rights=[])
Overrides user permissions cache.
Definition: PermissionManager.php:1758
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:71
MediaWiki\Permissions
Definition: Authority.php:21
Article\newFromTitle
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:163