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;
37 use NamespaceInfo;
38 use RequestContext;
39 use SpecialPage;
40 use Title;
41 use User;
42 use UserCache;
43 use Wikimedia\ScopedCallback;
44 
52 
54  public const RIGOR_QUICK = 'quick';
55 
57  public const RIGOR_FULL = 'full';
58 
60  public const RIGOR_SECURE = 'secure';
61 
65  public const CONSTRUCTOR_OPTIONS = [
66  'WhitelistRead',
67  'WhitelistReadRegexp',
68  'EmailConfirmToEdit',
69  'BlockDisablesLogin',
70  'GroupPermissions',
71  'RevokePermissions',
72  'AvailableRights',
73  'NamespaceProtection',
74  'RestrictionLevels'
75  ];
76 
78  private $options;
79 
82 
84  private $revisionLookup;
85 
87  private $nsInfo;
88 
91 
94 
96  private $allRights;
97 
100 
102  private $hookRunner;
103 
105  private $userCache;
106 
108  private $usersRights = null;
109 
114  private $temporaryUserRights = [];
115 
117  private $cachedRights = [];
118 
125  private $coreRights = [
126  'apihighlimits',
127  'applychangetags',
128  'autoconfirmed',
129  'autocreateaccount',
130  'autopatrol',
131  'bigdelete',
132  'block',
133  'blockemail',
134  'bot',
135  'browsearchive',
136  'changetags',
137  'createaccount',
138  'createpage',
139  'createtalk',
140  'delete',
141  'delete-redirect',
142  'deletechangetags',
143  'deletedhistory',
144  'deletedtext',
145  'deletelogentry',
146  'deleterevision',
147  'edit',
148  'editcontentmodel',
149  'editinterface',
150  'editprotected',
151  'editmyoptions',
152  'editmyprivateinfo',
153  'editmyusercss',
154  'editmyuserjson',
155  'editmyuserjs',
156  'editmyuserjsredirect',
157  'editmywatchlist',
158  'editsemiprotected',
159  'editsitecss',
160  'editsitejson',
161  'editsitejs',
162  'editusercss',
163  'edituserjson',
164  'edituserjs',
165  'hideuser',
166  'import',
167  'importupload',
168  'ipblock-exempt',
169  'managechangetags',
170  'markbotedits',
171  'mergehistory',
172  'minoredit',
173  'move',
174  'movefile',
175  'move-categorypages',
176  'move-rootuserpages',
177  'move-subpages',
178  'nominornewtalk',
179  'noratelimit',
180  'override-export-depth',
181  'pagelang',
182  'patrol',
183  'patrolmarks',
184  'protect',
185  'purge',
186  'read',
187  'reupload',
188  'reupload-own',
189  'reupload-shared',
190  'rollback',
191  'sendemail',
192  'siteadmin',
193  'suppressionlog',
194  'suppressredirect',
195  'suppressrevision',
196  'unblockself',
197  'undelete',
198  'unwatchedpages',
199  'upload',
200  'upload_by_url',
201  'userrights',
202  'userrights-interwiki',
203  'viewmyprivateinfo',
204  'viewmywatchlist',
205  'viewsuppressed',
206  'writeapi',
207  ];
208 
220  public function __construct(
228  HookContainer $hookContainer,
230  ) {
231  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
232  $this->options = $options;
233  $this->specialPageFactory = $specialPageFactory;
234  $this->revisionLookup = $revisionLookup;
235  $this->nsInfo = $nsInfo;
236  $this->groupPermissionLookup = $groupPermissionLookup;
237  $this->userGroupManager = $userGroupManager;
238  $this->blockErrorFormatter = $blockErrorFormatter;
239  $this->hookRunner = new HookRunner( $hookContainer );
240  $this->userCache = $userCache;
241  }
242 
260  public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
261  return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
262  }
263 
279  public function quickUserCan( $action, User $user, LinkTarget $page ) {
280  return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
281  }
282 
300  public function getPermissionErrors(
301  $action,
302  User $user,
303  LinkTarget $page,
304  $rigor = self::RIGOR_SECURE,
305  $ignoreErrors = []
306  ) {
307  $errors = $this->getPermissionErrorsInternal( $action, $user, $page, $rigor );
308 
309  // Remove the errors being ignored.
310  foreach ( $errors as $index => $error ) {
311  $errKey = is_array( $error ) ? $error[0] : $error;
312 
313  if ( in_array( $errKey, $ignoreErrors ) ) {
314  unset( $errors[$index] );
315  }
316  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
317  unset( $errors[$index] );
318  }
319  }
320 
321  return $errors;
322  }
323 
334  public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
335  $block = $user->getBlock( $fromReplica );
336  if ( !$block ) {
337  return false;
338  }
339 
340  // TODO: remove upon further migration to LinkTarget
341  $title = Title::newFromLinkTarget( $page );
342 
343  $blocked = $user->isHidden();
344  if ( !$blocked ) {
345  // Special handling for a user's own talk page. The block is not aware
346  // of the user, so this must be done here.
347  if ( $title->equals( $user->getTalkPage() ) ) {
348  $blocked = $block->appliesToUsertalk( $title );
349  } else {
350  $blocked = $block->appliesToTitle( $title );
351  }
352  }
353 
354  // only for the purpose of the hook. We really don't need this here.
355  $allowUsertalk = $user->isAllowUsertalk();
356 
357  // Allow extensions to let a blocked user access a particular page
358  $this->hookRunner->onUserIsBlockedFrom( $user, $title, $blocked, $allowUsertalk );
359 
360  return $blocked;
361  }
362 
380  private function getPermissionErrorsInternal(
381  $action,
382  User $user,
383  LinkTarget $page,
384  $rigor = self::RIGOR_SECURE,
385  $short = false
386  ) {
387  if ( !in_array( $rigor, [ self::RIGOR_QUICK, self::RIGOR_FULL, self::RIGOR_SECURE ] ) ) {
388  throw new Exception( "Invalid rigor parameter '$rigor'." );
389  }
390 
391  # Read has special handling
392  if ( $action == 'read' ) {
393  $checks = [
394  'checkPermissionHooks',
395  'checkReadPermissions',
396  'checkUserBlock', // for wgBlockDisablesLogin
397  ];
398  # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
399  # or checkUserConfigPermissions here as it will lead to duplicate
400  # error messages. This is okay to do since anywhere that checks for
401  # create will also check for edit, and those checks are called for edit.
402  } elseif ( $action == 'create' ) {
403  $checks = [
404  'checkQuickPermissions',
405  'checkPermissionHooks',
406  'checkPageRestrictions',
407  'checkCascadingSourcesRestrictions',
408  'checkActionPermissions',
409  'checkUserBlock'
410  ];
411  } else {
412  $checks = [
413  'checkQuickPermissions',
414  'checkPermissionHooks',
415  'checkSpecialsAndNSPermissions',
416  'checkSiteConfigPermissions',
417  'checkUserConfigPermissions',
418  'checkPageRestrictions',
419  'checkCascadingSourcesRestrictions',
420  'checkActionPermissions',
421  'checkUserBlock'
422  ];
423 
424  // Exclude checkUserConfigPermissions on actions that cannot change the
425  // content of the configuration pages.
426  $skipUserConfigActions = [
427  // Allow patrolling per T21818
428  'patrol',
429 
430  // Allow admins and oversighters to delete. For user pages we want to avoid the
431  // situation where an unprivileged user can post abusive content on
432  // their subpages and only very highly privileged users could remove it.
433  // See T200176.
434  'delete',
435  'deleterevision',
436  'suppressrevision',
437 
438  // Allow admins and oversighters to view deleted content, even if they
439  // cannot restore it. See T202989
440  'deletedhistory',
441  'deletedtext',
442  'viewsuppressed',
443  ];
444 
445  if ( in_array( $action, $skipUserConfigActions, true ) ) {
446  $checks = array_diff(
447  $checks,
448  [ 'checkUserConfigPermissions' ]
449  );
450  // Reset numbering
451  $checks = array_values( $checks );
452  }
453  }
454 
455  $errors = [];
456  foreach ( $checks as $method ) {
457  $errors = $this->$method( $action, $user, $errors, $rigor, $short, $page );
458 
459  if ( $short && $errors !== [] ) {
460  break;
461  }
462  }
463 
464  return $errors;
465  }
466 
483  private function checkPermissionHooks(
484  $action,
485  User $user,
486  $errors,
487  $rigor,
488  $short,
489  LinkTarget $page
490  ) {
491  // TODO: remove when LinkTarget usage will expand further
492  $title = Title::newFromLinkTarget( $page );
493  // Use getUserPermissionsErrors instead
494  $result = '';
495  if ( !$this->hookRunner->onUserCan( $title, $user, $action, $result ) ) {
496  return $result ? [] : [ [ 'badaccess-group0' ] ];
497  }
498  // Check getUserPermissionsErrors hook
499  if ( !$this->hookRunner->onGetUserPermissionsErrors( $title, $user, $action, $result ) ) {
500  $errors = $this->resultToError( $errors, $result );
501  }
502  // Check getUserPermissionsErrorsExpensive hook
503  if (
504  $rigor !== self::RIGOR_QUICK
505  && !( $short && count( $errors ) > 0 )
506  && !$this->hookRunner->onGetUserPermissionsErrorsExpensive(
507  $title, $user, $action, $result )
508  ) {
509  $errors = $this->resultToError( $errors, $result );
510  }
511 
512  return $errors;
513  }
514 
523  private function resultToError( $errors, $result ) {
524  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
525  // A single array representing an error
526  $errors[] = $result;
527  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
528  // A nested array representing multiple errors
529  $errors = array_merge( $errors, $result );
530  } elseif ( $result !== '' && is_string( $result ) ) {
531  // A string representing a message-id
532  $errors[] = [ $result ];
533  } elseif ( $result instanceof MessageSpecifier ) {
534  // A message specifier representing an error
535  $errors[] = [ $result ];
536  } elseif ( $result === false ) {
537  // a generic "We don't want them to do that"
538  $errors[] = [ 'badaccess-group0' ];
539  }
540  return $errors;
541  }
542 
559  private function checkReadPermissions(
560  $action,
561  User $user,
562  $errors,
563  $rigor,
564  $short,
565  LinkTarget $page
566  ) {
567  // TODO: remove when LinkTarget usage will expand further
568  $title = Title::newFromLinkTarget( $page );
569 
570  $whiteListRead = $this->options->get( 'WhitelistRead' );
571  $whitelisted = false;
572  if ( $this->isEveryoneAllowed( 'read' ) ) {
573  # Shortcut for public wikis, allows skipping quite a bit of code
574  $whitelisted = true;
575  } elseif ( $this->userHasRight( $user, 'read' ) ) {
576  # If the user is allowed to read pages, he is allowed to read all pages
577  $whitelisted = true;
578  } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
579  || $this->isSameSpecialPage( 'PasswordReset', $title )
580  || $this->isSameSpecialPage( 'Userlogout', $title )
581  ) {
582  # Always grant access to the login page.
583  # Even anons need to be able to log in.
584  $whitelisted = true;
585  } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
586  # Time to check the whitelist
587  # Only do these checks is there's something to check against
588  $name = $title->getPrefixedText();
589  $dbName = $title->getPrefixedDBkey();
590 
591  // Check for explicit whitelisting with and without underscores
592  if ( in_array( $name, $whiteListRead, true )
593  || in_array( $dbName, $whiteListRead, true ) ) {
594  $whitelisted = true;
595  } elseif ( $title->getNamespace() === NS_MAIN ) {
596  # Old settings might have the title prefixed with
597  # a colon for main-namespace pages
598  if ( in_array( ':' . $name, $whiteListRead ) ) {
599  $whitelisted = true;
600  }
601  } elseif ( $title->isSpecialPage() ) {
602  # If it's a special page, ditch the subpage bit and check again
603  $name = $title->getDBkey();
604  list( $name, /* $subpage */ ) =
605  $this->specialPageFactory->resolveAlias( $name );
606  if ( $name ) {
607  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
608  if ( in_array( $pure, $whiteListRead, true ) ) {
609  $whitelisted = true;
610  }
611  }
612  }
613  }
614 
615  $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
616  if ( !$whitelisted && is_array( $whitelistReadRegexp )
617  && !empty( $whitelistReadRegexp ) ) {
618  $name = $title->getPrefixedText();
619  // Check for regex whitelisting
620  foreach ( $whitelistReadRegexp as $listItem ) {
621  if ( preg_match( $listItem, $name ) ) {
622  $whitelisted = true;
623  break;
624  }
625  }
626  }
627 
628  if ( !$whitelisted ) {
629  # If the title is not whitelisted, give extensions a chance to do so...
630  $this->hookRunner->onTitleReadWhitelist( $title, $user, $whitelisted );
631  if ( !$whitelisted ) {
632  $errors[] = $this->missingPermissionError( $action, $short );
633  }
634  }
635 
636  return $errors;
637  }
638 
647  private function missingPermissionError( $action, $short ) {
648  // We avoid expensive display logic for quickUserCan's and such
649  if ( $short ) {
650  return [ 'badaccess-group0' ];
651  }
652 
653  // TODO: it would be a good idea to replace the method below with something else like
654  // maybe callback injection
655  return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
656  }
657 
666  private function isSameSpecialPage( $name, LinkTarget $page ) {
667  if ( $page->getNamespace() === NS_SPECIAL ) {
668  list( $thisName, /* $subpage */ ) =
669  $this->specialPageFactory->resolveAlias( $page->getDBkey() );
670  if ( $name == $thisName ) {
671  return true;
672  }
673  }
674  return false;
675  }
676 
693  private function checkUserBlock(
694  $action,
695  User $user,
696  $errors,
697  $rigor,
698  $short,
699  LinkTarget $page
700  ) {
701  // Account creation blocks handled at userlogin.
702  // Unblocking handled in SpecialUnblock
703  if ( $rigor === self::RIGOR_QUICK || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
704  return $errors;
705  }
706 
707  // Optimize for a very common case
708  if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
709  return $errors;
710  }
711 
712  if ( $this->options->get( 'EmailConfirmToEdit' )
713  && !$user->isEmailConfirmed()
714  && $action === 'edit'
715  ) {
716  $errors[] = [ 'confirmedittext' ];
717  }
718 
719  $useReplica = ( $rigor !== self::RIGOR_SECURE );
720  $block = $user->getBlock( $useReplica );
721 
722  // If the user does not have a block, or the block they do have explicitly
723  // allows the action (like "read" or "upload").
724  if ( !$block || $block->appliesToRight( $action ) === false ) {
725  return $errors;
726  }
727 
728  // Determine if the user is blocked from this action on this page.
729  // What gets passed into this method is a user right, not an action name.
730  // There is no way to instantiate an action by restriction. However, this
731  // will get the action where the restriction is the same. This may result
732  // in actions being blocked that shouldn't be.
733  $actionObj = null;
734  if ( Action::exists( $action ) ) {
735  // TODO: this drags a ton of dependencies in, would be good to avoid Article
736  // instantiation and decouple it creating an ActionPermissionChecker interface
737  // Creating an action will perform several database queries to ensure that
738  // the action has not been overridden by the content type.
739  // FIXME: avoid use of RequestContext since it drags in User and Title dependencies
740  // probably we may use fake context object since it's unlikely that Action uses it
741  // anyway. It would be nice if we could avoid instantiating the Action at all.
742  $title = Title::newFromLinkTarget( $page, 'clone' );
744  $actionObj = Action::factory(
745  $action,
747  $context
748  );
749  // Ensure that the retrieved action matches the restriction.
750  if ( $actionObj && $actionObj->getRestriction() !== $action ) {
751  $actionObj = null;
752  }
753  }
754 
755  // If no action object is returned, assume that the action requires unblock
756  // which is the default.
757  if ( !$actionObj || $actionObj->requiresUnblock() ) {
758  if ( $this->isBlockedFrom( $user, $page, $useReplica ) ) {
759  // @todo FIXME: Pass the relevant context into this function.
761  $message = $this->blockErrorFormatter->getMessage(
762  $block,
763  $context->getUser(),
765  $context->getRequest()->getIP()
766  );
767  $errors[] = array_merge( [ $message->getKey() ], $message->getParams() );
768  }
769  }
770 
771  return $errors;
772  }
773 
790  private function checkQuickPermissions(
791  $action,
792  User $user,
793  $errors,
794  $rigor,
795  $short,
796  LinkTarget $page
797  ) {
798  // TODO: remove when LinkTarget usage will expand further
799  $title = Title::newFromLinkTarget( $page );
800 
801  if ( !$this->hookRunner->onTitleQuickPermissions( $title, $user, $action,
802  $errors, $rigor !== self::RIGOR_QUICK, $short )
803  ) {
804  return $errors;
805  }
806 
807  $isSubPage = $this->nsInfo->hasSubpages( $title->getNamespace() ) ?
808  strpos( $title->getText(), '/' ) !== false : false;
809 
810  if ( $action == 'create' ) {
811  if (
812  ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
813  !$this->userHasRight( $user, 'createtalk' ) ) ||
814  ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
815  !$this->userHasRight( $user, 'createpage' ) )
816  ) {
817  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
818  }
819  } elseif ( $action == 'move' ) {
820  if ( !$this->userHasRight( $user, 'move-rootuserpages' )
821  && $title->getNamespace() === NS_USER && !$isSubPage ) {
822  // Show user page-specific message only if the user can move other pages
823  $errors[] = [ 'cant-move-user-page' ];
824  }
825 
826  // Check if user is allowed to move files if it's a file
827  if ( $title->getNamespace() === NS_FILE &&
828  !$this->userHasRight( $user, 'movefile' ) ) {
829  $errors[] = [ 'movenotallowedfile' ];
830  }
831 
832  // Check if user is allowed to move category pages if it's a category page
833  if ( $title->getNamespace() === NS_CATEGORY &&
834  !$this->userHasRight( $user, 'move-categorypages' ) ) {
835  $errors[] = [ 'cant-move-category-page' ];
836  }
837 
838  if ( !$this->userHasRight( $user, 'move' ) ) {
839  // User can't move anything
840  $userCanMove = $this->groupHasPermission( 'user', 'move' );
841  $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
842  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
843  // custom message if logged-in users without any special rights can move
844  $errors[] = [ 'movenologintext' ];
845  } else {
846  $errors[] = [ 'movenotallowed' ];
847  }
848  }
849  } elseif ( $action == 'move-target' ) {
850  if ( !$this->userHasRight( $user, 'move' ) ) {
851  // User can't move anything
852  $errors[] = [ 'movenotallowed' ];
853  } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
854  && $title->getNamespace() === NS_USER
855  && !$isSubPage
856  ) {
857  // Show user page-specific message only if the user can move other pages
858  $errors[] = [ 'cant-move-to-user-page' ];
859  } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
860  && $title->getNamespace() === NS_CATEGORY
861  ) {
862  // Show category page-specific message only if the user can move other pages
863  $errors[] = [ 'cant-move-to-category-page' ];
864  }
865  } elseif ( !$this->userHasRight( $user, $action ) ) {
866  $errors[] = $this->missingPermissionError( $action, $short );
867  }
868 
869  return $errors;
870  }
871 
890  private function checkPageRestrictions(
891  $action,
892  User $user,
893  $errors,
894  $rigor,
895  $short,
896  LinkTarget $page
897  ) {
898  // TODO: remove & rework upon further use of LinkTarget
899  $title = Title::newFromLinkTarget( $page );
900  foreach ( $title->getRestrictions( $action ) as $right ) {
901  // Backwards compatibility, rewrite sysop -> editprotected
902  if ( $right == 'sysop' ) {
903  $right = 'editprotected';
904  }
905  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
906  if ( $right == 'autoconfirmed' ) {
907  $right = 'editsemiprotected';
908  }
909  if ( $right == '' ) {
910  continue;
911  }
912  if ( !$this->userHasRight( $user, $right ) ) {
913  $errors[] = [ 'protectedpagetext', $right, $action ];
914  } elseif ( $title->areRestrictionsCascading() &&
915  !$this->userHasRight( $user, 'protect' )
916  ) {
917  $errors[] = [ 'protectedpagetext', 'protect', $action ];
918  }
919  }
920 
921  return $errors;
922  }
923 
941  $action,
942  UserIdentity $user,
943  $errors,
944  $rigor,
945  $short,
946  LinkTarget $page
947  ) {
948  // TODO: remove & rework upon further use of LinkTarget
949  $title = Title::newFromLinkTarget( $page );
950  if ( $rigor !== self::RIGOR_QUICK && !$title->isUserConfigPage() ) {
951  list( $cascadingSources, $restrictions ) = $title->getCascadeProtectionSources();
952  # Cascading protection depends on more than this page...
953  # Several cascading protected pages may include this page...
954  # Check each cascading level
955  # This is only for protection restrictions, not for all actions
956  if ( isset( $restrictions[$action] ) ) {
957  foreach ( $restrictions[$action] as $right ) {
958  // Backwards compatibility, rewrite sysop -> editprotected
959  if ( $right == 'sysop' ) {
960  $right = 'editprotected';
961  }
962  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
963  if ( $right == 'autoconfirmed' ) {
964  $right = 'editsemiprotected';
965  }
966  if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
967  $wikiPages = '';
969  foreach ( $cascadingSources as $wikiPage ) {
970  $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
971  }
972  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $wikiPages, $action ];
973  }
974  }
975  }
976  }
977 
978  return $errors;
979  }
980 
997  private function checkActionPermissions(
998  $action,
999  User $user,
1000  $errors,
1001  $rigor,
1002  $short,
1003  LinkTarget $page
1004  ) {
1006 
1007  // TODO: remove & rework upon further use of LinkTarget
1008  $title = Title::newFromLinkTarget( $page );
1009 
1010  if ( $action == 'protect' ) {
1011  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
1012  // If they can't edit, they shouldn't protect.
1013  $errors[] = [ 'protect-cantedit' ];
1014  }
1015  } elseif ( $action == 'create' ) {
1016  $title_protection = $title->getTitleProtection();
1017  if ( $title_protection ) {
1018  if ( $title_protection['permission'] == ''
1019  || !$this->userHasRight( $user, $title_protection['permission'] )
1020  ) {
1021  $errors[] = [
1022  'titleprotected',
1023  $this->userCache->getProp( $title_protection['user'], 'name' ),
1024  $title_protection['reason']
1025  ];
1026  }
1027  }
1028  } elseif ( $action == 'move' ) {
1029  // Check for immobile pages
1030  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
1031  // Specific message for this case
1032  $nsText = $title->getNsText();
1033  if ( $nsText === '' ) {
1034  $nsText = wfMessage( 'blanknamespace' )->text();
1035  }
1036  $errors[] = [ 'immobile-source-namespace', $nsText ];
1037  } elseif ( !$title->isMovable() ) {
1038  // Less specific message for rarer cases
1039  $errors[] = [ 'immobile-source-page' ];
1040  }
1041  } elseif ( $action == 'move-target' ) {
1042  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
1043  $nsText = $title->getNsText();
1044  if ( $nsText === '' ) {
1045  $nsText = wfMessage( 'blanknamespace' )->text();
1046  }
1047  $errors[] = [ 'immobile-target-namespace', $nsText ];
1048  } elseif ( !$title->isMovable() ) {
1049  $errors[] = [ 'immobile-target-page' ];
1050  }
1051  } elseif ( $action == 'delete' || $action == 'delete-redirect' ) {
1052  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true, $title );
1053  if ( !$tempErrors ) {
1054  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
1055  $user, $tempErrors, $rigor, true, $title );
1056  }
1057  if ( $tempErrors ) {
1058  // If protection keeps them from editing, they shouldn't be able to delete.
1059  $errors[] = [ 'deleteprotected' ];
1060  }
1061  if ( $rigor !== self::RIGOR_QUICK && $action == 'delete' && $wgDeleteRevisionsLimit
1062  && !$this->userCan( 'bigdelete', $user, $title ) && $title->isBigDeletion()
1063  ) {
1064  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
1065  }
1066  } elseif ( $action === 'undelete' ) {
1067  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
1068  // Undeleting implies editing
1069  $errors[] = [ 'undelete-cantedit' ];
1070  }
1071  if ( !$title->exists()
1072  && count( $this->getPermissionErrorsInternal( 'create', $user, $title, $rigor, true ) )
1073  ) {
1074  // Undeleting where nothing currently exists implies creating
1075  $errors[] = [ 'undelete-cantcreate' ];
1076  }
1077  }
1078  return $errors;
1079  }
1080 
1098  $action,
1099  UserIdentity $user,
1100  $errors,
1101  $rigor,
1102  $short,
1103  LinkTarget $page
1104  ) {
1105  // TODO: remove & rework upon further use of LinkTarget
1106  $title = Title::newFromLinkTarget( $page );
1107 
1108  # Only 'createaccount' can be performed on special pages,
1109  # which don't actually exist in the DB.
1110  if ( $title->getNamespace() === NS_SPECIAL && $action !== 'createaccount' ) {
1111  $errors[] = [ 'ns-specialprotected' ];
1112  }
1113 
1114  # Check $wgNamespaceProtection for restricted namespaces
1115  if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
1116  $ns = $title->getNamespace() === NS_MAIN ?
1117  wfMessage( 'nstab-main' )->text() : $title->getNsText();
1118  $errors[] = $title->getNamespace() === NS_MEDIAWIKI ?
1119  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
1120  }
1121 
1122  return $errors;
1123  }
1124 
1141  private function checkSiteConfigPermissions(
1142  $action,
1143  User $user,
1144  $errors,
1145  $rigor,
1146  $short,
1147  LinkTarget $page
1148  ) {
1149  // TODO: remove & rework upon further use of LinkTarget
1150  $title = Title::newFromLinkTarget( $page );
1151 
1152  if ( $action === 'patrol' ) {
1153  return $errors;
1154  }
1155 
1156  if ( in_array( $action, [ 'deletedhistory', 'deletedtext', 'viewsuppressed' ], true ) ) {
1157  // Allow admins and oversighters to view deleted content, even if they
1158  // cannot restore it. See T202989
1159  // Not using the same handling in `getPermissionErrorsInternal` as the checks
1160  // for skipping `checkUserConfigPermissions` since normal admins can delete
1161  // user scripts, but not sitedwide scripts
1162  return $errors;
1163  }
1164 
1165  // Sitewide CSS/JSON/JS/RawHTML changes, like all NS_MEDIAWIKI changes, also require the
1166  // editinterface right. That's implemented as a restriction so no check needed here.
1167  if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
1168  $errors[] = [ 'sitecssprotected', $action ];
1169  } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
1170  $errors[] = [ 'sitejsonprotected', $action ];
1171  } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
1172  $errors[] = [ 'sitejsprotected', $action ];
1173  }
1174  if ( $title->isRawHtmlMessage() && !$this->userCanEditRawHtmlPage( $user ) ) {
1175  $errors[] = [ 'siterawhtmlprotected', $action ];
1176  }
1177 
1178  return $errors;
1179  }
1180 
1197  private function checkUserConfigPermissions(
1198  $action,
1199  UserIdentity $user,
1200  $errors,
1201  $rigor,
1202  $short,
1203  LinkTarget $page
1204  ) {
1205  // TODO: remove & rework upon further use of LinkTarget
1206  $title = Title::newFromLinkTarget( $page );
1207 
1208  # Protect css/json/js subpages of user pages
1209  # XXX: this might be better using restrictions
1210  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $title->getText() ) ) {
1211  // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
1212  if (
1213  $title->isUserCssConfigPage()
1214  && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
1215  ) {
1216  $errors[] = [ 'mycustomcssprotected', $action ];
1217  } elseif (
1218  $title->isUserJsonConfigPage()
1219  && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
1220  ) {
1221  $errors[] = [ 'mycustomjsonprotected', $action ];
1222  } elseif (
1223  $title->isUserJsConfigPage()
1224  && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
1225  ) {
1226  $errors[] = [ 'mycustomjsprotected', $action ];
1227  } elseif (
1228  $title->isUserJsConfigPage()
1229  && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
1230  ) {
1231  // T207750 - do not allow users to edit a redirect if they couldn't edit the target
1232  $rev = $this->revisionLookup->getRevisionByTitle( $title );
1233  $content = $rev ? $rev->getContent( 'main', RevisionRecord::RAW ) : null;
1234  $target = $content ? $content->getUltimateRedirectTarget() : null;
1235  if ( $target && (
1236  !$target->inNamespace( NS_USER )
1237  || !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $target->getText() )
1238  ) ) {
1239  $errors[] = [ 'mycustomjsredirectprotected', $action ];
1240  }
1241  }
1242  } else {
1243  // Users need edituser* to edit others' CSS/JSON/JS subpages.
1244  // The checks to exclude deletion/suppression, which cannot be used for
1245  // attacks and should be excluded to avoid the situation where an
1246  // unprivileged user can post abusive content on their subpages
1247  // and only very highly privileged users could remove it,
1248  // are now a part of `getPermissionErrorsInternal` and this method isn't called.
1249  if (
1250  $title->isUserCssConfigPage()
1251  && !$this->userHasRight( $user, 'editusercss' )
1252  ) {
1253  $errors[] = [ 'customcssprotected', $action ];
1254  } elseif (
1255  $title->isUserJsonConfigPage()
1256  && !$this->userHasRight( $user, 'edituserjson' )
1257  ) {
1258  $errors[] = [ 'customjsonprotected', $action ];
1259  } elseif (
1260  $title->isUserJsConfigPage()
1261  && !$this->userHasRight( $user, 'edituserjs' )
1262  ) {
1263  $errors[] = [ 'customjsprotected', $action ];
1264  }
1265  }
1266 
1267  return $errors;
1268  }
1269 
1280  public function userHasRight( UserIdentity $user, $action = '' ) {
1281  if ( $action === '' ) {
1282  return true; // In the spirit of DWIM
1283  }
1284  // Use strict parameter to avoid matching numeric 0 accidentally inserted
1285  // by misconfiguration: 0 == 'foo'
1286  return in_array( $action, $this->getUserPermissions( $user ), true );
1287  }
1288 
1297  public function userHasAnyRight( UserIdentity $user, ...$actions ) {
1298  foreach ( $actions as $action ) {
1299  if ( $this->userHasRight( $user, $action ) ) {
1300  return true;
1301  }
1302  }
1303  return false;
1304  }
1305 
1314  public function userHasAllRights( UserIdentity $user, ...$actions ) {
1315  foreach ( $actions as $action ) {
1316  if ( !$this->userHasRight( $user, $action ) ) {
1317  return false;
1318  }
1319  }
1320  return true;
1321  }
1322 
1332  public function getUserPermissions( UserIdentity $user ) {
1333  $user = User::newFromIdentity( $user );
1334  $rightsCacheKey = $this->getRightsCacheKey( $user );
1335  if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1336  $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
1337  $this->userGroupManager->getUserEffectiveGroups( $user )
1338  );
1339  $this->hookRunner->onUserGetRights( $user, $this->usersRights[ $rightsCacheKey ] );
1340 
1341  // Deny any rights denied by the user's session, unless this
1342  // endpoint has no sessions.
1343  if ( !defined( 'MW_NO_SESSION' ) ) {
1344  // FIXME: $user->getRequest().. need to be replaced with something else
1345  $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
1346  if ( $allowedRights !== null ) {
1347  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1348  $this->usersRights[ $rightsCacheKey ],
1349  $allowedRights
1350  );
1351  }
1352  }
1353 
1354  $this->hookRunner->onUserGetRightsRemove(
1355  $user, $this->usersRights[ $rightsCacheKey ] );
1356  // Force reindexation of rights when a hook has unset one of them
1357  $this->usersRights[ $rightsCacheKey ] = array_values(
1358  array_unique( $this->usersRights[ $rightsCacheKey ] )
1359  );
1360 
1361  if (
1362  $user->isRegistered() &&
1363  $this->options->get( 'BlockDisablesLogin' ) &&
1364  $user->getBlock()
1365  ) {
1366  $anon = new User;
1367  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1368  $this->usersRights[ $rightsCacheKey ],
1369  $this->getUserPermissions( $anon )
1370  );
1371  }
1372  }
1373  $rights = $this->usersRights[ $rightsCacheKey ];
1374  foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
1375  $rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
1376  }
1377  return $rights;
1378  }
1379 
1388  public function invalidateUsersRightsCache( $user = null ) {
1389  if ( $user !== null ) {
1390  $rightsCacheKey = $this->getRightsCacheKey( $user );
1391  unset( $this->usersRights[ $rightsCacheKey ] );
1392  } else {
1393  $this->usersRights = null;
1394  }
1395  }
1396 
1402  private function getRightsCacheKey( UserIdentity $user ) {
1403  return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
1404  }
1405 
1421  public function groupHasPermission( $group, $role ) {
1422  return $this->groupPermissionLookup->groupHasPermission( $group, $role );
1423  }
1424 
1434  public function getGroupPermissions( $groups ) {
1435  return $this->groupPermissionLookup->getGroupPermissions( $groups );
1436  }
1437 
1447  public function getGroupsWithPermission( $role ) {
1448  return $this->groupPermissionLookup->getGroupsWithPermission( $role );
1449  }
1450 
1466  public function isEveryoneAllowed( $right ) {
1467  // Use the cached results, except in unit tests which rely on
1468  // being able change the permission mid-request
1469  if ( isset( $this->cachedRights[$right] ) ) {
1470  return $this->cachedRights[$right];
1471  }
1472 
1473  if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
1474  || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
1475  $this->cachedRights[$right] = false;
1476  return false;
1477  }
1478 
1479  // If it's revoked anywhere, then everyone doesn't have it
1480  foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
1481  if ( isset( $rights[$right] ) && $rights[$right] ) {
1482  $this->cachedRights[$right] = false;
1483  return false;
1484  }
1485  }
1486 
1487  // Remove any rights that aren't allowed to the global-session user,
1488  // unless there are no sessions for this endpoint.
1489  if ( !defined( 'MW_NO_SESSION' ) ) {
1490 
1491  // XXX: think what could be done with the below
1492  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
1493  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
1494  $this->cachedRights[$right] = false;
1495  return false;
1496  }
1497  }
1498 
1499  // Allow extensions to say false
1500  if ( !$this->hookRunner->onUserIsEveryoneAllowed( $right ) ) {
1501  $this->cachedRights[$right] = false;
1502  return false;
1503  }
1504 
1505  $this->cachedRights[$right] = true;
1506  return true;
1507  }
1508 
1516  public function getAllPermissions() {
1517  if ( $this->allRights === null ) {
1518  if ( count( $this->options->get( 'AvailableRights' ) ) ) {
1519  $this->allRights = array_unique( array_merge(
1520  $this->coreRights,
1521  $this->options->get( 'AvailableRights' )
1522  ) );
1523  } else {
1524  $this->allRights = $this->coreRights;
1525  }
1526  $this->hookRunner->onUserGetAllRights( $this->allRights );
1527  }
1528  return $this->allRights;
1529  }
1530 
1537  private function isNamespaceProtected( $index, UserIdentity $user ) {
1538  $namespaceProtection = $this->options->get( 'NamespaceProtection' );
1539  if ( isset( $namespaceProtection[$index] ) ) {
1540  return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
1541  }
1542  return false;
1543  }
1544 
1553  public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
1554  if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
1555  // All levels are valid if there's no namespace restriction.
1556  // But still filter by user, if necessary
1557  $levels = $this->options->get( 'RestrictionLevels' );
1558  if ( $user ) {
1559  $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
1560  $right = $level;
1561  if ( $right == 'sysop' ) {
1562  $right = 'editprotected'; // BC
1563  }
1564  if ( $right == 'autoconfirmed' ) {
1565  $right = 'editsemiprotected'; // BC
1566  }
1567  return $this->userHasRight( $user, $right );
1568  } ) );
1569  }
1570  return $levels;
1571  }
1572 
1573  // $wgNamespaceProtection can require one or more rights to edit the namespace, which
1574  // may be satisfied by membership in multiple groups each giving a subset of those rights.
1575  // A restriction level is redundant if, for any one of the namespace rights, all groups
1576  // giving that right also give the restriction level's right. Or, conversely, a
1577  // restriction level is not redundant if, for every namespace right, there's at least one
1578  // group giving that right without the restriction level's right.
1579  //
1580  // First, for each right, get a list of groups with that right.
1581  $namespaceRightGroups = [];
1582  foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
1583  if ( $right == 'sysop' ) {
1584  $right = 'editprotected'; // BC
1585  }
1586  if ( $right == 'autoconfirmed' ) {
1587  $right = 'editsemiprotected'; // BC
1588  }
1589  if ( $right != '' ) {
1590  $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
1591  }
1592  }
1593 
1594  // Now, go through the protection levels one by one.
1595  $usableLevels = [ '' ];
1596  foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
1597  $right = $level;
1598  if ( $right == 'sysop' ) {
1599  $right = 'editprotected'; // BC
1600  }
1601  if ( $right == 'autoconfirmed' ) {
1602  $right = 'editsemiprotected'; // BC
1603  }
1604 
1605  if ( $right != '' &&
1606  !isset( $namespaceRightGroups[$right] ) &&
1607  ( !$user || $this->userHasRight( $user, $right ) )
1608  ) {
1609  // Do any of the namespace rights imply the restriction right? (see explanation above)
1610  foreach ( $namespaceRightGroups as $groups ) {
1611  if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
1612  // Yes, this one does.
1613  continue 2;
1614  }
1615  }
1616  // No, keep the restriction level
1617  $usableLevels[] = $level;
1618  }
1619  }
1620 
1621  return $usableLevels;
1622  }
1623 
1632  private function userCanEditRawHtmlPage( UserIdentity $user ) {
1633  return $this->userHasAllRights( $user, 'editsitecss', 'editsitejs' );
1634  }
1635 
1650  public function addTemporaryUserRights( UserIdentity $user, $rights ) {
1651  $userId = $user->getId();
1652  $nextKey = count( $this->temporaryUserRights[$userId] ?? [] );
1653  $this->temporaryUserRights[$userId][$nextKey] = (array)$rights;
1654  return new ScopedCallback( function () use ( $userId, $nextKey ) {
1655  unset( $this->temporaryUserRights[$userId][$nextKey] );
1656  } );
1657  }
1658 
1669  public function overrideUserRightsForTesting( $user, $rights = [] ) {
1670  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
1671  throw new Exception( __METHOD__ . ' can not be called outside of tests' );
1672  }
1673  $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
1674  is_array( $rights ) ? $rights : [ $rights ];
1675  }
1676 
1677 }
MediaWiki\Permissions\PermissionManager\$cachedRights
bool[] $cachedRights
Cached rights for isEveryoneAllowed, [ right => allowed ].
Definition: PermissionManager.php:117
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:71
MediaWiki\Permissions\PermissionManager\checkCascadingSourcesRestrictions
checkCascadingSourcesRestrictions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check restrictions on cascading pages.
Definition: PermissionManager.php:940
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:2991
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:1097
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:380
MediaWiki\Permissions\GroupPermissionsLookup
Definition: GroupPermissionsLookup.php:30
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:4392
MediaWiki\Permissions\PermissionManager\userCan
userCan( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE)
Can $user perform $action on a page?
Definition: PermissionManager.php:260
MediaWiki\Block\BlockErrorFormatter
A service class for getting formatted information about a block.
Definition: BlockErrorFormatter.php:35
MediaWiki\SpecialPage\SpecialPageFactory
Factory for handling the special page list and generating SpecialPage objects.
Definition: SpecialPageFactory.php:61
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:647
MediaWiki\Permissions\PermissionManager\checkPermissionHooks
checkPermissionHooks( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check various permission hooks.
Definition: PermissionManager.php:483
MediaWiki\Permissions\PermissionManager\userHasRight
userHasRight(UserIdentity $user, $action='')
Testing a permission.
Definition: PermissionManager.php:1280
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1230
MediaWiki\Permissions\PermissionManager\$coreRights
$coreRights
Array of Strings Core rights.
Definition: PermissionManager.php:125
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:106
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:619
Action\factory
static factory(?string $action, Page $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:115
$wgLang
$wgLang
Definition: Setup.php:777
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
NS_MAIN
const NS_MAIN
Definition: Defines.php:63
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:52
MediaWiki\User\UserGroupManager
Managers user groups.
Definition: UserGroupManager.php:51
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:1388
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:3725
MediaWiki\Permissions\PermissionManager\resultToError
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition: PermissionManager.php:523
MediaWiki\Permissions\PermissionManager\$revisionLookup
RevisionLookup $revisionLookup
Definition: PermissionManager.php:84
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Permissions\PermissionManager\$hookRunner
HookRunner $hookRunner
Definition: PermissionManager.php:102
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:1197
MediaWiki\Permissions\PermissionManager\$usersRights
string[][] $usersRights
Cached user rights.
Definition: PermissionManager.php:108
MediaWiki\Permissions\PermissionManager\$allRights
string[] null $allRights
Cached results of getAllPermissions()
Definition: PermissionManager.php:96
MediaWiki\Permissions\PermissionManager\isSameSpecialPage
isSameSpecialPage( $name, LinkTarget $page)
Returns true if this title resolves to the named special page.
Definition: PermissionManager.php:666
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2014
MediaWiki\Permissions\PermissionManager\getRightsCacheKey
getRightsCacheKey(UserIdentity $user)
Gets a unique key for user rights cache.
Definition: PermissionManager.php:1402
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:1553
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:1537
Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:61
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:890
MediaWiki\Permissions\PermissionManager\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: PermissionManager.php:81
MediaWiki\Permissions\PermissionManager\$nsInfo
NamespaceInfo $nsInfo
Definition: PermissionManager.php:87
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:790
RequestContext
Group all the pieces relevant to the context of a request into one instance @newable.
Definition: RequestContext.php:39
$wgDeleteRevisionsLimit
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
Definition: DefaultSettings.php:5968
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:1896
MediaWiki\Permissions\PermissionManager\$blockErrorFormatter
BlockErrorFormatter $blockErrorFormatter
Definition: PermissionManager.php:99
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:693
MediaWiki\Permissions\PermissionManager\checkActionPermissions
checkActionPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check action permissions not already checked in checkQuickPermissions.
Definition: PermissionManager.php:997
MediaWiki\Permissions\PermissionManager\isEveryoneAllowed
isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: PermissionManager.php:1466
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:559
MediaWiki\Permissions\PermissionManager\getUserPermissions
getUserPermissions(UserIdentity $user)
Get the permissions this user has.
Definition: PermissionManager.php:1332
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
If PHP's session_id() has been set, returns that session.
Definition: SessionManager.php:113
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:51
$content
$content
Definition: router.php:76
MediaWiki\Permissions\PermissionManager\getAllPermissions
getAllPermissions()
Get a list of all available permissions.
Definition: PermissionManager.php:1516
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:42
MediaWiki\Permissions\PermissionManager\getGroupsWithPermission
getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: PermissionManager.php:1447
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:53
MediaWiki\Permissions\PermissionManager\quickUserCan
quickUserCan( $action, User $user, LinkTarget $page)
A convenience method for calling PermissionManager::userCan with PermissionManager::RIGOR_QUICK.
Definition: PermissionManager.php:279
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:1650
MediaWiki\Permissions\PermissionManager\$userGroupManager
UserGroupManager $userGroupManager
Definition: PermissionManager.php:93
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:300
NS_USER
const NS_USER
Definition: Defines.php:65
MediaWiki\Permissions\PermissionManager\userCanEditRawHtmlPage
userCanEditRawHtmlPage(UserIdentity $user)
Check if user is allowed to edit sitewide pages that contain raw HTML.
Definition: PermissionManager.php:1632
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:454
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:289
MediaWiki\Permissions\PermissionManager\checkSiteConfigPermissions
checkSiteConfigPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check sitewide CSS/JSON/JS permissions.
Definition: PermissionManager.php:1141
MediaWiki\Permissions\PermissionManager\groupHasPermission
groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: PermissionManager.php:1421
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:45
Title
Represents a title within MediaWiki.
Definition: Title.php:46
MediaWiki\User\UserIdentity\getId
getId()
MediaWiki\Permissions\PermissionManager\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: PermissionManager.php:65
MediaWiki\Permissions\PermissionManager\userHasAllRights
userHasAllRights(UserIdentity $user,... $actions)
Check if user is allowed to make all actions.
Definition: PermissionManager.php:1314
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4027
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:4448
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:77
MediaWiki\Permissions\PermissionManager\$userCache
UserCache $userCache
Definition: PermissionManager.php:105
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:571
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:47
NS_FILE
const NS_FILE
Definition: Defines.php:69
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:56
MediaWiki\Permissions\PermissionManager\$temporaryUserRights
string[][][] $temporaryUserRights
Temporary user rights, valid for the current request only.
Definition: PermissionManager.php:114
MediaWiki\Permissions\PermissionManager\userHasAnyRight
userHasAnyRight(UserIdentity $user,... $actions)
Check if user is allowed to make any action.
Definition: PermissionManager.php:1297
MediaWiki\Permissions\PermissionManager\getGroupPermissions
getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: PermissionManager.php:1434
MediaWiki\Permissions\PermissionManager\$options
ServiceOptions $options
Definition: PermissionManager.php:78
MediaWiki\Permissions\PermissionManager\$groupPermissionLookup
GroupPermissionsLookup $groupPermissionLookup
Definition: PermissionManager.php:90
IContextSource\getLanguage
getLanguage()
MediaWiki\Permissions\PermissionManager\__construct
__construct(ServiceOptions $options, SpecialPageFactory $specialPageFactory, RevisionLookup $revisionLookup, NamespaceInfo $nsInfo, GroupPermissionsLookup $groupPermissionLookup, UserGroupManager $userGroupManager, BlockErrorFormatter $blockErrorFormatter, HookContainer $hookContainer, UserCache $userCache)
Definition: PermissionManager.php:220
MediaWiki\Permissions\PermissionManager\overrideUserRightsForTesting
overrideUserRightsForTesting( $user, $rights=[])
Overrides user permissions cache.
Definition: PermissionManager.php:1669
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:66
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:158
MediaWiki\Permissions\PermissionManager\isBlockedFrom
isBlockedFrom(User $user, LinkTarget $page, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: PermissionManager.php:334