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