MediaWiki  master
PermissionManager.php
Go to the documentation of this file.
1 <?php
21 
22 use Action;
23 use Exception;
24 use Hooks;
34 use NamespaceInfo;
35 use RequestContext;
36 use SpecialPage;
37 use Title;
38 use User;
39 use Wikimedia\ScopedCallback;
40 use WikiPage;
41 
49 
51  public const RIGOR_QUICK = 'quick';
52 
54  public const RIGOR_FULL = 'full';
55 
57  public const RIGOR_SECURE = 'secure';
58 
63  public const CONSTRUCTOR_OPTIONS = [
64  'WhitelistRead',
65  'WhitelistReadRegexp',
66  'EmailConfirmToEdit',
67  'BlockDisablesLogin',
68  'GroupPermissions',
69  'RevokePermissions',
70  'AvailableRights',
71  'NamespaceProtection',
72  'RestrictionLevels'
73  ];
74 
76  private $options;
77 
80 
82  private $revisionLookup;
83 
85  private $nsInfo;
86 
88  private $allRights;
89 
92 
94  private $usersRights = null;
95 
100  private $temporaryUserRights = [];
101 
103  private $cachedRights = [];
104 
111  private $coreRights = [
112  'apihighlimits',
113  'applychangetags',
114  'autoconfirmed',
115  'autocreateaccount',
116  'autopatrol',
117  'bigdelete',
118  'block',
119  'blockemail',
120  'bot',
121  'browsearchive',
122  'changetags',
123  'createaccount',
124  'createpage',
125  'createtalk',
126  'delete',
127  'deletechangetags',
128  'deletedhistory',
129  'deletedtext',
130  'deletelogentry',
131  'deleterevision',
132  'edit',
133  'editcontentmodel',
134  'editinterface',
135  'editprotected',
136  'editmyoptions',
137  'editmyprivateinfo',
138  'editmyusercss',
139  'editmyuserjson',
140  'editmyuserjs',
141  'editmyuserjsredirect',
142  'editmywatchlist',
143  'editsemiprotected',
144  'editsitecss',
145  'editsitejson',
146  'editsitejs',
147  'editusercss',
148  'edituserjson',
149  'edituserjs',
150  'hideuser',
151  'import',
152  'importupload',
153  'ipblock-exempt',
154  'managechangetags',
155  'markbotedits',
156  'mergehistory',
157  'minoredit',
158  'move',
159  'movefile',
160  'move-categorypages',
161  'move-rootuserpages',
162  'move-subpages',
163  'nominornewtalk',
164  'noratelimit',
165  'override-export-depth',
166  'pagelang',
167  'patrol',
168  'patrolmarks',
169  'protect',
170  'purge',
171  'read',
172  'reupload',
173  'reupload-own',
174  'reupload-shared',
175  'rollback',
176  'sendemail',
177  'siteadmin',
178  'suppressionlog',
179  'suppressredirect',
180  'suppressrevision',
181  'unblockself',
182  'undelete',
183  'unwatchedpages',
184  'upload',
185  'upload_by_url',
186  'userrights',
187  'userrights-interwiki',
188  'viewmyprivateinfo',
189  'viewmywatchlist',
190  'viewsuppressed',
191  'writeapi',
192  ];
193 
201  public function __construct(
207  ) {
208  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
209  $this->options = $options;
210  $this->specialPageFactory = $specialPageFactory;
211  $this->revisionLookup = $revisionLookup;
212  $this->nsInfo = $nsInfo;
213  $this->blockErrorFormatter = $blockErrorFormatter;
214  }
215 
235  public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
236  return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
237  }
238 
254  public function quickUserCan( $action, User $user, LinkTarget $page ) {
255  return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
256  }
257 
275  public function getPermissionErrors(
276  $action,
277  User $user,
278  LinkTarget $page,
279  $rigor = self::RIGOR_SECURE,
280  $ignoreErrors = []
281  ) {
282  $errors = $this->getPermissionErrorsInternal( $action, $user, $page, $rigor );
283 
284  // Remove the errors being ignored.
285  foreach ( $errors as $index => $error ) {
286  $errKey = is_array( $error ) ? $error[0] : $error;
287 
288  if ( in_array( $errKey, $ignoreErrors ) ) {
289  unset( $errors[$index] );
290  }
291  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
292  unset( $errors[$index] );
293  }
294  }
295 
296  return $errors;
297  }
298 
309  public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
310  $block = $user->getBlock( $fromReplica );
311  if ( !$block ) {
312  return false;
313  }
314 
315  // TODO: remove upon further migration to LinkTarget
316  $title = Title::newFromLinkTarget( $page );
317 
318  $blocked = $user->isHidden();
319  if ( !$blocked ) {
320  // Special handling for a user's own talk page. The block is not aware
321  // of the user, so this must be done here.
322  if ( $title->equals( $user->getTalkPage() ) ) {
323  $blocked = $block->appliesToUsertalk( $title );
324  } else {
325  $blocked = $block->appliesToTitle( $title );
326  }
327  }
328 
329  // only for the purpose of the hook. We really don't need this here.
330  $allowUsertalk = $user->isAllowUsertalk();
331 
332  // Allow extensions to let a blocked user access a particular page
333  Hooks::run( 'UserIsBlockedFrom', [ $user, $title, &$blocked, &$allowUsertalk ] );
334 
335  return $blocked;
336  }
337 
355  private function getPermissionErrorsInternal(
356  $action,
357  User $user,
358  LinkTarget $page,
359  $rigor = self::RIGOR_SECURE,
360  $short = false
361  ) {
362  if ( !in_array( $rigor, [ self::RIGOR_QUICK, self::RIGOR_FULL, self::RIGOR_SECURE ] ) ) {
363  throw new Exception( "Invalid rigor parameter '$rigor'." );
364  }
365 
366  # Read has special handling
367  if ( $action == 'read' ) {
368  $checks = [
369  'checkPermissionHooks',
370  'checkReadPermissions',
371  'checkUserBlock', // for wgBlockDisablesLogin
372  ];
373  # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
374  # or checkUserConfigPermissions here as it will lead to duplicate
375  # error messages. This is okay to do since anywhere that checks for
376  # create will also check for edit, and those checks are called for edit.
377  } elseif ( $action == 'create' ) {
378  $checks = [
379  'checkQuickPermissions',
380  'checkPermissionHooks',
381  'checkPageRestrictions',
382  'checkCascadingSourcesRestrictions',
383  'checkActionPermissions',
384  'checkUserBlock'
385  ];
386  } else {
387  $checks = [
388  'checkQuickPermissions',
389  'checkPermissionHooks',
390  'checkSpecialsAndNSPermissions',
391  'checkSiteConfigPermissions',
392  'checkUserConfigPermissions',
393  'checkPageRestrictions',
394  'checkCascadingSourcesRestrictions',
395  'checkActionPermissions',
396  'checkUserBlock'
397  ];
398  }
399 
400  $errors = [];
401  foreach ( $checks as $method ) {
402  $errors = $this->$method( $action, $user, $errors, $rigor, $short, $page );
403 
404  if ( $short && $errors !== [] ) {
405  break;
406  }
407  }
408 
409  return $errors;
410  }
411 
428  private function checkPermissionHooks(
429  $action,
430  User $user,
431  $errors,
432  $rigor,
433  $short,
434  LinkTarget $page
435  ) {
436  // TODO: remove when LinkTarget usage will expand further
437  $title = Title::newFromLinkTarget( $page );
438  // Use getUserPermissionsErrors instead
439  $result = '';
440  if ( !Hooks::run( 'userCan', [ &$title, &$user, $action, &$result ] ) ) {
441  return $result ? [] : [ [ 'badaccess-group0' ] ];
442  }
443  // Check getUserPermissionsErrors hook
444  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$title, &$user, $action, &$result ] ) ) {
445  $errors = $this->resultToError( $errors, $result );
446  }
447  // Check getUserPermissionsErrorsExpensive hook
448  if (
449  $rigor !== self::RIGOR_QUICK
450  && !( $short && count( $errors ) > 0 )
451  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$title, &$user, $action, &$result ] )
452  ) {
453  $errors = $this->resultToError( $errors, $result );
454  }
455 
456  return $errors;
457  }
458 
467  private function resultToError( $errors, $result ) {
468  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
469  // A single array representing an error
470  $errors[] = $result;
471  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
472  // A nested array representing multiple errors
473  $errors = array_merge( $errors, $result );
474  } elseif ( $result !== '' && is_string( $result ) ) {
475  // A string representing a message-id
476  $errors[] = [ $result ];
477  } elseif ( $result instanceof MessageSpecifier ) {
478  // A message specifier representing an error
479  $errors[] = [ $result ];
480  } elseif ( $result === false ) {
481  // a generic "We don't want them to do that"
482  $errors[] = [ 'badaccess-group0' ];
483  }
484  return $errors;
485  }
486 
503  private function checkReadPermissions(
504  $action,
505  User $user,
506  $errors,
507  $rigor,
508  $short,
509  LinkTarget $page
510  ) {
511  // TODO: remove when LinkTarget usage will expand further
512  $title = Title::newFromLinkTarget( $page );
513 
514  $whiteListRead = $this->options->get( 'WhitelistRead' );
515  $whitelisted = false;
516  if ( $this->isEveryoneAllowed( 'read' ) ) {
517  # Shortcut for public wikis, allows skipping quite a bit of code
518  $whitelisted = true;
519  } elseif ( $this->userHasRight( $user, 'read' ) ) {
520  # If the user is allowed to read pages, he is allowed to read all pages
521  $whitelisted = true;
522  } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
523  || $this->isSameSpecialPage( 'PasswordReset', $title )
524  || $this->isSameSpecialPage( 'Userlogout', $title )
525  ) {
526  # Always grant access to the login page.
527  # Even anons need to be able to log in.
528  $whitelisted = true;
529  } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
530  # Time to check the whitelist
531  # Only do these checks is there's something to check against
532  $name = $title->getPrefixedText();
533  $dbName = $title->getPrefixedDBkey();
534 
535  // Check for explicit whitelisting with and without underscores
536  if ( in_array( $name, $whiteListRead, true )
537  || in_array( $dbName, $whiteListRead, true ) ) {
538  $whitelisted = true;
539  } elseif ( $title->getNamespace() == NS_MAIN ) {
540  # Old settings might have the title prefixed with
541  # a colon for main-namespace pages
542  if ( in_array( ':' . $name, $whiteListRead ) ) {
543  $whitelisted = true;
544  }
545  } elseif ( $title->isSpecialPage() ) {
546  # If it's a special page, ditch the subpage bit and check again
547  $name = $title->getDBkey();
548  list( $name, /* $subpage */ ) =
549  $this->specialPageFactory->resolveAlias( $name );
550  if ( $name ) {
551  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
552  if ( in_array( $pure, $whiteListRead, true ) ) {
553  $whitelisted = true;
554  }
555  }
556  }
557  }
558 
559  $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
560  if ( !$whitelisted && is_array( $whitelistReadRegexp )
561  && !empty( $whitelistReadRegexp ) ) {
562  $name = $title->getPrefixedText();
563  // Check for regex whitelisting
564  foreach ( $whitelistReadRegexp as $listItem ) {
565  if ( preg_match( $listItem, $name ) ) {
566  $whitelisted = true;
567  break;
568  }
569  }
570  }
571 
572  if ( !$whitelisted ) {
573  # If the title is not whitelisted, give extensions a chance to do so...
574  Hooks::run( 'TitleReadWhitelist', [ $title, $user, &$whitelisted ] );
575  if ( !$whitelisted ) {
576  $errors[] = $this->missingPermissionError( $action, $short );
577  }
578  }
579 
580  return $errors;
581  }
582 
591  private function missingPermissionError( $action, $short ) {
592  // We avoid expensive display logic for quickUserCan's and such
593  if ( $short ) {
594  return [ 'badaccess-group0' ];
595  }
596 
597  // TODO: it would be a good idea to replace the method below with something else like
598  // maybe callback injection
599  return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
600  }
601 
610  private function isSameSpecialPage( $name, LinkTarget $page ) {
611  if ( $page->getNamespace() == NS_SPECIAL ) {
612  list( $thisName, /* $subpage */ ) =
613  $this->specialPageFactory->resolveAlias( $page->getDBkey() );
614  if ( $name == $thisName ) {
615  return true;
616  }
617  }
618  return false;
619  }
620 
637  private function checkUserBlock(
638  $action,
639  User $user,
640  $errors,
641  $rigor,
642  $short,
643  LinkTarget $page
644  ) {
645  // Account creation blocks handled at userlogin.
646  // Unblocking handled in SpecialUnblock
647  if ( $rigor === self::RIGOR_QUICK || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
648  return $errors;
649  }
650 
651  // Optimize for a very common case
652  if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
653  return $errors;
654  }
655 
656  if ( $this->options->get( 'EmailConfirmToEdit' )
657  && !$user->isEmailConfirmed()
658  && $action === 'edit'
659  ) {
660  $errors[] = [ 'confirmedittext' ];
661  }
662 
663  $useReplica = ( $rigor !== self::RIGOR_SECURE );
664  $block = $user->getBlock( $useReplica );
665 
666  // If the user does not have a block, or the block they do have explicitly
667  // allows the action (like "read" or "upload").
668  if ( !$block || $block->appliesToRight( $action ) === false ) {
669  return $errors;
670  }
671 
672  // Determine if the user is blocked from this action on this page.
673  // What gets passed into this method is a user right, not an action name.
674  // There is no way to instantiate an action by restriction. However, this
675  // will get the action where the restriction is the same. This may result
676  // in actions being blocked that shouldn't be.
677  $actionObj = null;
678  if ( Action::exists( $action ) ) {
679  // TODO: this drags a ton of dependencies in, would be good to avoid WikiPage
680  // instantiation and decouple it creating an ActionPermissionChecker interface
681  $wikiPage = WikiPage::factory( Title::newFromLinkTarget( $page, 'clone' ) );
682  // Creating an action will perform several database queries to ensure that
683  // the action has not been overridden by the content type.
684  // FIXME: avoid use of RequestContext since it drags in User and Title dependencies
685  // probably we may use fake context object since it's unlikely that Action uses it
686  // anyway. It would be nice if we could avoid instantiating the Action at all.
687  $actionObj = Action::factory( $action, $wikiPage, RequestContext::getMain() );
688  // Ensure that the retrieved action matches the restriction.
689  if ( $actionObj && $actionObj->getRestriction() !== $action ) {
690  $actionObj = null;
691  }
692  }
693 
694  // If no action object is returned, assume that the action requires unblock
695  // which is the default.
696  if ( !$actionObj || $actionObj->requiresUnblock() ) {
697  if ( $this->isBlockedFrom( $user, $page, $useReplica ) ) {
698  // @todo FIXME: Pass the relevant context into this function.
700  $message = $this->blockErrorFormatter->getMessage(
701  $block,
702  $context->getUser(),
704  $context->getRequest()->getIP()
705  );
706  $errors[] = array_merge( [ $message->getKey() ], $message->getParams() );
707  }
708  }
709 
710  return $errors;
711  }
712 
729  private function checkQuickPermissions(
730  $action,
731  User $user,
732  $errors,
733  $rigor,
734  $short,
735  LinkTarget $page
736  ) {
737  // TODO: remove when LinkTarget usage will expand further
738  $title = Title::newFromLinkTarget( $page );
739 
740  if ( !Hooks::run( 'TitleQuickPermissions',
741  [ $title, $user, $action, &$errors, ( $rigor !== self::RIGOR_QUICK ), $short ] )
742  ) {
743  return $errors;
744  }
745 
746  $isSubPage = $this->nsInfo->hasSubpages( $title->getNamespace() ) ?
747  strpos( $title->getText(), '/' ) !== false : false;
748 
749  if ( $action == 'create' ) {
750  if (
751  ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
752  !$this->userHasRight( $user, 'createtalk' ) ) ||
753  ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
754  !$this->userHasRight( $user, 'createpage' ) )
755  ) {
756  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
757  }
758  } elseif ( $action == 'move' ) {
759  if ( !$this->userHasRight( $user, 'move-rootuserpages' )
760  && $title->getNamespace() == NS_USER && !$isSubPage ) {
761  // Show user page-specific message only if the user can move other pages
762  $errors[] = [ 'cant-move-user-page' ];
763  }
764 
765  // Check if user is allowed to move files if it's a file
766  if ( $title->getNamespace() == NS_FILE &&
767  !$this->userHasRight( $user, 'movefile' ) ) {
768  $errors[] = [ 'movenotallowedfile' ];
769  }
770 
771  // Check if user is allowed to move category pages if it's a category page
772  if ( $title->getNamespace() == NS_CATEGORY &&
773  !$this->userHasRight( $user, 'move-categorypages' ) ) {
774  $errors[] = [ 'cant-move-category-page' ];
775  }
776 
777  if ( !$this->userHasRight( $user, 'move' ) ) {
778  // User can't move anything
779  $userCanMove = $this->groupHasPermission( 'user', 'move' );
780  $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
781  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
782  // custom message if logged-in users without any special rights can move
783  $errors[] = [ 'movenologintext' ];
784  } else {
785  $errors[] = [ 'movenotallowed' ];
786  }
787  }
788  } elseif ( $action == 'move-target' ) {
789  if ( !$this->userHasRight( $user, 'move' ) ) {
790  // User can't move anything
791  $errors[] = [ 'movenotallowed' ];
792  } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
793  && $title->getNamespace() == NS_USER && !$isSubPage ) {
794  // Show user page-specific message only if the user can move other pages
795  $errors[] = [ 'cant-move-to-user-page' ];
796  } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
797  && $title->getNamespace() == NS_CATEGORY ) {
798  // Show category page-specific message only if the user can move other pages
799  $errors[] = [ 'cant-move-to-category-page' ];
800  }
801  } elseif ( !$this->userHasRight( $user, $action ) ) {
802  $errors[] = $this->missingPermissionError( $action, $short );
803  }
804 
805  return $errors;
806  }
807 
826  private function checkPageRestrictions(
827  $action,
828  User $user,
829  $errors,
830  $rigor,
831  $short,
832  LinkTarget $page
833  ) {
834  // TODO: remove & rework upon further use of LinkTarget
835  $title = Title::newFromLinkTarget( $page );
836  foreach ( $title->getRestrictions( $action ) as $right ) {
837  // Backwards compatibility, rewrite sysop -> editprotected
838  if ( $right == 'sysop' ) {
839  $right = 'editprotected';
840  }
841  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
842  if ( $right == 'autoconfirmed' ) {
843  $right = 'editsemiprotected';
844  }
845  if ( $right == '' ) {
846  continue;
847  }
848  if ( !$this->userHasRight( $user, $right ) ) {
849  $errors[] = [ 'protectedpagetext', $right, $action ];
850  } elseif ( $title->areRestrictionsCascading() &&
851  !$this->userHasRight( $user, 'protect' ) ) {
852  $errors[] = [ 'protectedpagetext', 'protect', $action ];
853  }
854  }
855 
856  return $errors;
857  }
858 
876  $action,
877  UserIdentity $user,
878  $errors,
879  $rigor,
880  $short,
881  LinkTarget $page
882  ) {
883  // TODO: remove & rework upon further use of LinkTarget
884  $title = Title::newFromLinkTarget( $page );
885  if ( $rigor !== self::RIGOR_QUICK && !$title->isUserConfigPage() ) {
886  list( $cascadingSources, $restrictions ) = $title->getCascadeProtectionSources();
887  # Cascading protection depends on more than this page...
888  # Several cascading protected pages may include this page...
889  # Check each cascading level
890  # This is only for protection restrictions, not for all actions
891  if ( isset( $restrictions[$action] ) ) {
892  foreach ( $restrictions[$action] as $right ) {
893  // Backwards compatibility, rewrite sysop -> editprotected
894  if ( $right == 'sysop' ) {
895  $right = 'editprotected';
896  }
897  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
898  if ( $right == 'autoconfirmed' ) {
899  $right = 'editsemiprotected';
900  }
901  if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
902  $wikiPages = '';
904  foreach ( $cascadingSources as $wikiPage ) {
905  $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
906  }
907  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $wikiPages, $action ];
908  }
909  }
910  }
911  }
912 
913  return $errors;
914  }
915 
932  private function checkActionPermissions(
933  $action,
934  User $user,
935  $errors,
936  $rigor,
937  $short,
938  LinkTarget $page
939  ) {
941 
942  // TODO: remove & rework upon further use of LinkTarget
943  $title = Title::newFromLinkTarget( $page );
944 
945  if ( $action == 'protect' ) {
946  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
947  // If they can't edit, they shouldn't protect.
948  $errors[] = [ 'protect-cantedit' ];
949  }
950  } elseif ( $action == 'create' ) {
951  $title_protection = $title->getTitleProtection();
952  if ( $title_protection ) {
953  if ( $title_protection['permission'] == ''
954  || !$this->userHasRight( $user, $title_protection['permission'] )
955  ) {
956  $errors[] = [
957  'titleprotected',
958  // TODO: get rid of the User dependency
959  User::whoIs( $title_protection['user'] ),
960  $title_protection['reason']
961  ];
962  }
963  }
964  } elseif ( $action == 'move' ) {
965  // Check for immobile pages
966  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
967  // Specific message for this case
968  $errors[] = [ 'immobile-source-namespace', $title->getNsText() ];
969  } elseif ( !$title->isMovable() ) {
970  // Less specific message for rarer cases
971  $errors[] = [ 'immobile-source-page' ];
972  }
973  } elseif ( $action == 'move-target' ) {
974  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
975  $errors[] = [ 'immobile-target-namespace', $title->getNsText() ];
976  } elseif ( !$title->isMovable() ) {
977  $errors[] = [ 'immobile-target-page' ];
978  }
979  } elseif ( $action == 'delete' ) {
980  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true, $title );
981  if ( !$tempErrors ) {
982  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
983  $user, $tempErrors, $rigor, true, $title );
984  }
985  if ( $tempErrors ) {
986  // If protection keeps them from editing, they shouldn't be able to delete.
987  $errors[] = [ 'deleteprotected' ];
988  }
989  if ( $rigor !== self::RIGOR_QUICK && $wgDeleteRevisionsLimit
990  && !$this->userCan( 'bigdelete', $user, $title ) && $title->isBigDeletion()
991  ) {
992  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
993  }
994  } elseif ( $action === 'undelete' ) {
995  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
996  // Undeleting implies editing
997  $errors[] = [ 'undelete-cantedit' ];
998  }
999  if ( !$title->exists()
1000  && count( $this->getPermissionErrorsInternal( 'create', $user, $title, $rigor, true ) )
1001  ) {
1002  // Undeleting where nothing currently exists implies creating
1003  $errors[] = [ 'undelete-cantcreate' ];
1004  }
1005  }
1006  return $errors;
1007  }
1008 
1026  $action,
1027  UserIdentity $user,
1028  $errors,
1029  $rigor,
1030  $short,
1031  LinkTarget $page
1032  ) {
1033  // TODO: remove & rework upon further use of LinkTarget
1034  $title = Title::newFromLinkTarget( $page );
1035 
1036  # Only 'createaccount' can be performed on special pages,
1037  # which don't actually exist in the DB.
1038  if ( $title->getNamespace() == NS_SPECIAL && $action !== 'createaccount' ) {
1039  $errors[] = [ 'ns-specialprotected' ];
1040  }
1041 
1042  # Check $wgNamespaceProtection for restricted namespaces
1043  if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
1044  $ns = $title->getNamespace() == NS_MAIN ?
1045  wfMessage( 'nstab-main' )->text() : $title->getNsText();
1046  $errors[] = $title->getNamespace() == NS_MEDIAWIKI ?
1047  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
1048  }
1049 
1050  return $errors;
1051  }
1052 
1069  private function checkSiteConfigPermissions(
1070  $action,
1071  User $user,
1072  $errors,
1073  $rigor,
1074  $short,
1075  LinkTarget $page
1076  ) {
1077  // TODO: remove & rework upon further use of LinkTarget
1078  $title = Title::newFromLinkTarget( $page );
1079 
1080  if ( $action != 'patrol' ) {
1081  $error = null;
1082  // Sitewide CSS/JSON/JS/RawHTML changes, like all NS_MEDIAWIKI changes, also require the
1083  // editinterface right. That's implemented as a restriction so no check needed here.
1084  if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
1085  $error = [ 'sitecssprotected', $action ];
1086  } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
1087  $error = [ 'sitejsonprotected', $action ];
1088  } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
1089  $error = [ 'sitejsprotected', $action ];
1090  }
1091  if ( $title->isRawHtmlMessage() && !$this->userCanEditRawHtmlPage( $user ) ) {
1092  $error = [ 'siterawhtmlprotected', $action ];
1093  }
1094 
1095  if ( $error ) {
1096  if ( $this->userHasRight( $user, 'editinterface' ) ) {
1097  // Most users / site admins will probably find out about the new, more restrictive
1098  // permissions by failing to edit something. Give them more info.
1099  // TODO remove this a few release cycles after 1.32
1100  $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
1101  }
1102  $errors[] = $error;
1103  }
1104  }
1105 
1106  return $errors;
1107  }
1108 
1125  private function checkUserConfigPermissions(
1126  $action,
1127  UserIdentity $user,
1128  $errors,
1129  $rigor,
1130  $short,
1131  LinkTarget $page
1132  ) {
1133  // TODO: remove & rework upon further use of LinkTarget
1134  $title = Title::newFromLinkTarget( $page );
1135 
1136  # Protect css/json/js subpages of user pages
1137  # XXX: this might be better using restrictions
1138 
1139  if ( $action === 'patrol' ) {
1140  return $errors;
1141  }
1142 
1143  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $title->getText() ) ) {
1144  // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
1145  if (
1146  $title->isUserCssConfigPage()
1147  && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
1148  ) {
1149  $errors[] = [ 'mycustomcssprotected', $action ];
1150  } elseif (
1151  $title->isUserJsonConfigPage()
1152  && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
1153  ) {
1154  $errors[] = [ 'mycustomjsonprotected', $action ];
1155  } elseif (
1156  $title->isUserJsConfigPage()
1157  && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
1158  ) {
1159  $errors[] = [ 'mycustomjsprotected', $action ];
1160  } elseif (
1161  $title->isUserJsConfigPage()
1162  && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
1163  ) {
1164  // T207750 - do not allow users to edit a redirect if they couldn't edit the target
1165  $rev = $this->revisionLookup->getRevisionByTitle( $title );
1166  $content = $rev ? $rev->getContent( 'main', RevisionRecord::RAW ) : null;
1167  $target = $content ? $content->getUltimateRedirectTarget() : null;
1168  if ( $target && (
1169  !$target->inNamespace( NS_USER )
1170  || !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $target->getText() )
1171  ) ) {
1172  $errors[] = [ 'mycustomjsredirectprotected', $action ];
1173  }
1174  }
1175  } else {
1176  // Users need edituser* to edit others' CSS/JSON/JS subpages, except for
1177  // deletion/suppression which cannot be used for attacks and we want to avoid the
1178  // situation where an unprivileged user can post abusive content on their subpages
1179  // and only very highly privileged users could remove it.
1180  if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
1181  if (
1182  $title->isUserCssConfigPage()
1183  && !$this->userHasRight( $user, 'editusercss' )
1184  ) {
1185  $errors[] = [ 'customcssprotected', $action ];
1186  } elseif (
1187  $title->isUserJsonConfigPage()
1188  && !$this->userHasRight( $user, 'edituserjson' )
1189  ) {
1190  $errors[] = [ 'customjsonprotected', $action ];
1191  } elseif (
1192  $title->isUserJsConfigPage()
1193  && !$this->userHasRight( $user, 'edituserjs' )
1194  ) {
1195  $errors[] = [ 'customjsprotected', $action ];
1196  }
1197  }
1198  }
1199 
1200  return $errors;
1201  }
1202 
1213  public function userHasRight( UserIdentity $user, $action = '' ) {
1214  if ( $action === '' ) {
1215  return true; // In the spirit of DWIM
1216  }
1217  // Use strict parameter to avoid matching numeric 0 accidentally inserted
1218  // by misconfiguration: 0 == 'foo'
1219  return in_array( $action, $this->getUserPermissions( $user ), true );
1220  }
1221 
1230  public function userHasAnyRight( UserIdentity $user, ...$actions ) {
1231  foreach ( $actions as $action ) {
1232  if ( $this->userHasRight( $user, $action ) ) {
1233  return true;
1234  }
1235  }
1236  return false;
1237  }
1238 
1247  public function userHasAllRights( UserIdentity $user, ...$actions ) {
1248  foreach ( $actions as $action ) {
1249  if ( !$this->userHasRight( $user, $action ) ) {
1250  return false;
1251  }
1252  }
1253  return true;
1254  }
1255 
1265  public function getUserPermissions( UserIdentity $user ) {
1266  $user = User::newFromIdentity( $user );
1267  $rightsCacheKey = $this->getRightsCacheKey( $user );
1268  if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1269  $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
1270  $user->getEffectiveGroups()
1271  );
1272  Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
1273 
1274  // Deny any rights denied by the user's session, unless this
1275  // endpoint has no sessions.
1276  if ( !defined( 'MW_NO_SESSION' ) ) {
1277  // FIXME: $user->getRequest().. need to be replaced with something else
1278  $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
1279  if ( $allowedRights !== null ) {
1280  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1281  $this->usersRights[ $rightsCacheKey ],
1282  $allowedRights
1283  );
1284  }
1285  }
1286 
1287  Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
1288  // Force reindexation of rights when a hook has unset one of them
1289  $this->usersRights[ $rightsCacheKey ] = array_values(
1290  array_unique( $this->usersRights[ $rightsCacheKey ] )
1291  );
1292 
1293  if (
1294  $user->isLoggedIn() &&
1295  $this->options->get( 'BlockDisablesLogin' ) &&
1296  $user->getBlock()
1297  ) {
1298  $anon = new User;
1299  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1300  $this->usersRights[ $rightsCacheKey ],
1301  $this->getUserPermissions( $anon )
1302  );
1303  }
1304  }
1305  $rights = $this->usersRights[ $rightsCacheKey ];
1306  foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
1307  $rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
1308  }
1309  return $rights;
1310  }
1311 
1320  public function invalidateUsersRightsCache( $user = null ) {
1321  if ( $user !== null ) {
1322  $rightsCacheKey = $this->getRightsCacheKey( $user );
1323  if ( isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1324  unset( $this->usersRights[ $rightsCacheKey ] );
1325  }
1326  } else {
1327  $this->usersRights = null;
1328  }
1329  }
1330 
1336  private function getRightsCacheKey( UserIdentity $user ) {
1337  return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
1338  }
1339 
1354  public function groupHasPermission( $group, $role ) {
1355  $groupPermissions = $this->options->get( 'GroupPermissions' );
1356  $revokePermissions = $this->options->get( 'RevokePermissions' );
1357  return isset( $groupPermissions[$group][$role] ) && $groupPermissions[$group][$role] &&
1358  !( isset( $revokePermissions[$group][$role] ) && $revokePermissions[$group][$role] );
1359  }
1360 
1369  public function getGroupPermissions( $groups ) {
1370  $rights = [];
1371  // grant every granted permission first
1372  foreach ( $groups as $group ) {
1373  if ( isset( $this->options->get( 'GroupPermissions' )[$group] ) ) {
1374  $rights = array_merge( $rights,
1375  // array_filter removes empty items
1376  array_keys( array_filter( $this->options->get( 'GroupPermissions' )[$group] ) ) );
1377  }
1378  }
1379  // now revoke the revoked permissions
1380  foreach ( $groups as $group ) {
1381  if ( isset( $this->options->get( 'RevokePermissions' )[$group] ) ) {
1382  $rights = array_diff( $rights,
1383  array_keys( array_filter( $this->options->get( 'RevokePermissions' )[$group] ) ) );
1384  }
1385  }
1386  return array_unique( $rights );
1387  }
1388 
1397  public function getGroupsWithPermission( $role ) {
1398  $allowedGroups = [];
1399  foreach ( array_keys( $this->options->get( 'GroupPermissions' ) ) as $group ) {
1400  if ( $this->groupHasPermission( $group, $role ) ) {
1401  $allowedGroups[] = $group;
1402  }
1403  }
1404  return $allowedGroups;
1405  }
1406 
1422  public function isEveryoneAllowed( $right ) {
1423  // Use the cached results, except in unit tests which rely on
1424  // being able change the permission mid-request
1425  if ( isset( $this->cachedRights[$right] ) ) {
1426  return $this->cachedRights[$right];
1427  }
1428 
1429  if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
1430  || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
1431  $this->cachedRights[$right] = false;
1432  return false;
1433  }
1434 
1435  // If it's revoked anywhere, then everyone doesn't have it
1436  foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
1437  if ( isset( $rights[$right] ) && $rights[$right] ) {
1438  $this->cachedRights[$right] = false;
1439  return false;
1440  }
1441  }
1442 
1443  // Remove any rights that aren't allowed to the global-session user,
1444  // unless there are no sessions for this endpoint.
1445  if ( !defined( 'MW_NO_SESSION' ) ) {
1446 
1447  // XXX: think what could be done with the below
1448  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
1449  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
1450  $this->cachedRights[$right] = false;
1451  return false;
1452  }
1453  }
1454 
1455  // Allow extensions to say false
1456  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
1457  $this->cachedRights[$right] = false;
1458  return false;
1459  }
1460 
1461  $this->cachedRights[$right] = true;
1462  return true;
1463  }
1464 
1472  public function getAllPermissions() {
1473  if ( $this->allRights === null ) {
1474  if ( count( $this->options->get( 'AvailableRights' ) ) ) {
1475  $this->allRights = array_unique( array_merge(
1476  $this->coreRights,
1477  $this->options->get( 'AvailableRights' )
1478  ) );
1479  } else {
1480  $this->allRights = $this->coreRights;
1481  }
1482  Hooks::run( 'UserGetAllRights', [ &$this->allRights ] );
1483  }
1484  return $this->allRights;
1485  }
1486 
1493  private function isNamespaceProtected( $index, UserIdentity $user ) {
1494  $namespaceProtection = $this->options->get( 'NamespaceProtection' );
1495  if ( isset( $namespaceProtection[$index] ) ) {
1496  return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
1497  }
1498  return false;
1499  }
1500 
1509  public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
1510  if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
1511  // All levels are valid if there's no namespace restriction.
1512  // But still filter by user, if necessary
1513  $levels = $this->options->get( 'RestrictionLevels' );
1514  if ( $user ) {
1515  $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
1516  $right = $level;
1517  if ( $right == 'sysop' ) {
1518  $right = 'editprotected'; // BC
1519  }
1520  if ( $right == 'autoconfirmed' ) {
1521  $right = 'editsemiprotected'; // BC
1522  }
1523  return $this->userHasRight( $user, $right );
1524  } ) );
1525  }
1526  return $levels;
1527  }
1528 
1529  // $wgNamespaceProtection can require one or more rights to edit the namespace, which
1530  // may be satisfied by membership in multiple groups each giving a subset of those rights.
1531  // A restriction level is redundant if, for any one of the namespace rights, all groups
1532  // giving that right also give the restriction level's right. Or, conversely, a
1533  // restriction level is not redundant if, for every namespace right, there's at least one
1534  // group giving that right without the restriction level's right.
1535  //
1536  // First, for each right, get a list of groups with that right.
1537  $namespaceRightGroups = [];
1538  foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
1539  if ( $right == 'sysop' ) {
1540  $right = 'editprotected'; // BC
1541  }
1542  if ( $right == 'autoconfirmed' ) {
1543  $right = 'editsemiprotected'; // BC
1544  }
1545  if ( $right != '' ) {
1546  $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
1547  }
1548  }
1549 
1550  // Now, go through the protection levels one by one.
1551  $usableLevels = [ '' ];
1552  foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
1553  $right = $level;
1554  if ( $right == 'sysop' ) {
1555  $right = 'editprotected'; // BC
1556  }
1557  if ( $right == 'autoconfirmed' ) {
1558  $right = 'editsemiprotected'; // BC
1559  }
1560 
1561  if ( $right != '' &&
1562  !isset( $namespaceRightGroups[$right] ) &&
1563  ( !$user || $this->userHasRight( $user, $right ) )
1564  ) {
1565  // Do any of the namespace rights imply the restriction right? (see explanation above)
1566  foreach ( $namespaceRightGroups as $groups ) {
1567  if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
1568  // Yes, this one does.
1569  continue 2;
1570  }
1571  }
1572  // No, keep the restriction level
1573  $usableLevels[] = $level;
1574  }
1575  }
1576 
1577  return $usableLevels;
1578  }
1579 
1588  private function userCanEditRawHtmlPage( UserIdentity $user ) {
1589  return $this->userHasAllRights( $user, 'editsitecss', 'editsitejs' );
1590  }
1591 
1606  public function addTemporaryUserRights( UserIdentity $user, $rights ) {
1607  $userId = $user->getId();
1608  $nextKey = count( $this->temporaryUserRights[$userId] ?? [] );
1609  $this->temporaryUserRights[$userId][$nextKey] = (array)$rights;
1610  return new ScopedCallback( function () use ( $userId, $nextKey ) {
1611  unset( $this->temporaryUserRights[$userId][$nextKey] );
1612  } );
1613  }
1614 
1625  public function overrideUserRightsForTesting( $user, $rights = [] ) {
1626  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
1627  throw new Exception( __METHOD__ . ' can not be called outside of tests' );
1628  }
1629  $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
1630  is_array( $rights ) ? $rights : [ $rights ];
1631  }
1632 
1633 }
MediaWiki\Permissions\PermissionManager\$cachedRights
bool[] $cachedRights
Cached rights for isEveryoneAllowed, [ right => allowed ].
Definition: PermissionManager.php:103
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
MediaWiki\Permissions\PermissionManager\checkCascadingSourcesRestrictions
checkCascadingSourcesRestrictions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check restrictions on cascading pages.
Definition: PermissionManager.php:875
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:3589
MediaWiki\Permissions\PermissionManager\checkSpecialsAndNSPermissions
checkSpecialsAndNSPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check permissions on special pages & namespaces.
Definition: PermissionManager.php:1025
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:355
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5347
MediaWiki\Permissions\PermissionManager\userCan
userCan( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE)
Can $user perform $action on a page?
Definition: PermissionManager.php:235
MediaWiki\Block\BlockErrorFormatter
A service class for getting formatted information about a block.
Definition: BlockErrorFormatter.php:34
MessageSpecifier
Definition: MessageSpecifier.php:21
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:46
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:591
MediaWiki\Permissions\PermissionManager\checkPermissionHooks
checkPermissionHooks( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check various permission hooks.
Definition: PermissionManager.php:428
NS_FILE
const NS_FILE
Definition: Defines.php:66
MediaWiki\Permissions\PermissionManager\userHasRight
userHasRight(UserIdentity $user, $action='')
Testing a permission.
Definition: PermissionManager.php:1213
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1263
MediaWiki\Permissions\PermissionManager\$coreRights
$coreRights
Array of Strings Core rights.
Definition: PermissionManager.php:111
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:83
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:593
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
Revision\RevisionLookup
Service for looking up page revisions.
Definition: RevisionLookup.php:38
Action
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition: Action.php:39
NS_MAIN
const NS_MAIN
Definition: Defines.php:60
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:49
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:1320
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4360
MediaWiki\Permissions\PermissionManager\resultToError
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition: PermissionManager.php:467
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:141
MediaWiki\Permissions\PermissionManager\$revisionLookup
RevisionLookup $revisionLookup
Definition: PermissionManager.php:82
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:25
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:1125
MediaWiki\Permissions\PermissionManager\$usersRights
string[][] $usersRights
Cached user rights.
Definition: PermissionManager.php:94
MediaWiki\Permissions\PermissionManager\$allRights
string[] null $allRights
Cached results of getAllRights()
Definition: PermissionManager.php:88
$wgLang
$wgLang
Definition: Setup.php:846
MediaWiki\Permissions\PermissionManager\isSameSpecialPage
isSameSpecialPage( $name, LinkTarget $page)
Returns true if this title resolves to the named special page.
Definition: PermissionManager.php:610
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2238
MediaWiki\Permissions\PermissionManager\getRightsCacheKey
getRightsCacheKey(UserIdentity $user)
Gets a unique key for user rights cache.
Definition: PermissionManager.php:1336
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:1509
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:1493
Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:60
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:36
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:826
MediaWiki\Permissions\PermissionManager\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: PermissionManager.php:79
MediaWiki\Permissions\PermissionManager\$nsInfo
NamespaceInfo $nsInfo
Definition: PermissionManager.php:85
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:729
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:74
RequestContext
Group all the pieces relevant to the context of a request into one instance.
Definition: RequestContext.php:34
$wgDeleteRevisionsLimit
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
Definition: DefaultSettings.php:5525
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2118
MediaWiki\Permissions\PermissionManager\$blockErrorFormatter
BlockErrorFormatter $blockErrorFormatter
Definition: PermissionManager.php:91
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:637
MediaWiki\Permissions\PermissionManager\checkActionPermissions
checkActionPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check action permissions not already checked in checkQuickPermissions.
Definition: PermissionManager.php:932
MediaWiki\Permissions\PermissionManager\isEveryoneAllowed
isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: PermissionManager.php:1422
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:503
MediaWiki\Special\SpecialPageFactory
Factory for handling the special page list and generating SpecialPage objects.
Definition: SpecialPageFactory.php:64
MediaWiki\Permissions\PermissionManager\getUserPermissions
getUserPermissions(UserIdentity $user)
Get the permissions this user has.
Definition: PermissionManager.php:1265
MediaWiki\Session\SessionManager\getGlobalSession
static getGlobalSession()
Get the "global" session.
Definition: SessionManager.php:106
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:48
$content
$content
Definition: router.php:78
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:866
MediaWiki\Permissions\PermissionManager\getAllPermissions
getAllPermissions()
Get a list of all available permissions.
Definition: PermissionManager.php:1472
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:37
MediaWiki\Permissions\PermissionManager\getGroupsWithPermission
getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: PermissionManager.php:1397
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
MediaWiki\Permissions\PermissionManager\quickUserCan
quickUserCan( $action, User $user, LinkTarget $page)
A convenience method for calling PermissionManager::userCan with PermissionManager::RIGOR_QUICK.
Definition: PermissionManager.php:254
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:1606
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:275
MediaWiki\Permissions\PermissionManager\userCanEditRawHtmlPage
userCanEditRawHtmlPage(UserIdentity $user)
Check if user is allowed to edit sitewide pages that contain raw HTML.
Definition: PermissionManager.php:1588
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:447
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:269
MediaWiki\Permissions\PermissionManager\__construct
__construct(ServiceOptions $options, SpecialPageFactory $specialPageFactory, RevisionLookup $revisionLookup, NamespaceInfo $nsInfo, BlockErrorFormatter $blockErrorFormatter)
Definition: PermissionManager.php:201
MediaWiki\Permissions\PermissionManager\checkSiteConfigPermissions
checkSiteConfigPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check sitewide CSS/JSON/JS permissions.
Definition: PermissionManager.php:1069
MediaWiki\Permissions\PermissionManager\groupHasPermission
groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: PermissionManager.php:1354
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:42
Title
Represents a title within MediaWiki.
Definition: Title.php:42
MediaWiki\User\UserIdentity\getId
getId()
Action\exists
static exists( $name)
Check if a given action is recognised, even if it's disabled.
Definition: Action.php:170
MediaWiki\Permissions\PermissionManager\userHasAllRights
userHasAllRights(UserIdentity $user,... $actions)
Check if user is allowed to make all actions.
Definition: PermissionManager.php:1247
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4710
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5403
NS_USER
const NS_USER
Definition: Defines.php:62
IContextSource\getRequest
getRequest()
NamespaceInfo
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Definition: NamespaceInfo.php:33
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:68
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
MediaWiki\$context
IContextSource $context
Definition: MediaWiki.php:37
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:52
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
MediaWiki\Permissions\PermissionManager\$temporaryUserRights
string[][][] $temporaryUserRights
Temporary user rights, valid for the current request only.
Definition: PermissionManager.php:100
MediaWiki\Permissions\PermissionManager\userHasAnyRight
userHasAnyRight(UserIdentity $user,... $actions)
Check if user is allowed to make any action.
Definition: PermissionManager.php:1230
Action\factory
static factory( $action, Page $page, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:97
MediaWiki\Permissions\PermissionManager\getGroupPermissions
getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: PermissionManager.php:1369
MediaWiki\Permissions\PermissionManager\$options
ServiceOptions $options
Definition: PermissionManager.php:59
Hooks
Hooks class.
Definition: Hooks.php:34
IContextSource\getLanguage
getLanguage()
MediaWiki\Permissions\PermissionManager\overrideUserRightsForTesting
overrideUserRightsForTesting( $user, $rights=[])
Overrides user permissions cache.
Definition: PermissionManager.php:1625
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:62
MediaWiki\Permissions
Definition: PermissionManager.php:20
MediaWiki\Permissions\PermissionManager\isBlockedFrom
isBlockedFrom(User $user, LinkTarget $page, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: PermissionManager.php:309