MediaWiki  1.34.0
PermissionManager.php
Go to the documentation of this file.
1 <?php
21 
22 use Action;
23 use Exception;
24 use Hooks;
33 use NamespaceInfo;
34 use RequestContext;
35 use SpecialPage;
36 use Title;
37 use User;
38 use Wikimedia\ScopedCallback;
39 use WikiPage;
40 
48 
50  const RIGOR_QUICK = 'quick';
51 
53  const RIGOR_FULL = 'full';
54 
56  const RIGOR_SECURE = 'secure';
57 
62  public const CONSTRUCTOR_OPTIONS = [
63  'WhitelistRead',
64  'WhitelistReadRegexp',
65  'EmailConfirmToEdit',
66  'BlockDisablesLogin',
67  'GroupPermissions',
68  'RevokePermissions',
69  'AvailableRights',
70  'NamespaceProtection',
71  'RestrictionLevels'
72  ];
73 
75  private $options;
76 
79 
81  private $revisionLookup;
82 
84  private $nsInfo;
85 
87  private $allRights;
88 
90  private $usersRights = null;
91 
96  private $temporaryUserRights = [];
97 
99  private $cachedRights = [];
100 
107  private $coreRights = [
108  'apihighlimits',
109  'applychangetags',
110  'autoconfirmed',
111  'autocreateaccount',
112  'autopatrol',
113  'bigdelete',
114  'block',
115  'blockemail',
116  'bot',
117  'browsearchive',
118  'changetags',
119  'createaccount',
120  'createpage',
121  'createtalk',
122  'delete',
123  'deletechangetags',
124  'deletedhistory',
125  'deletedtext',
126  'deletelogentry',
127  'deleterevision',
128  'edit',
129  'editcontentmodel',
130  'editinterface',
131  'editprotected',
132  'editmyoptions',
133  'editmyprivateinfo',
134  'editmyusercss',
135  'editmyuserjson',
136  'editmyuserjs',
137  'editmyuserjsredirect',
138  'editmywatchlist',
139  'editsemiprotected',
140  'editsitecss',
141  'editsitejson',
142  'editsitejs',
143  'editusercss',
144  'edituserjson',
145  'edituserjs',
146  'hideuser',
147  'import',
148  'importupload',
149  'ipblock-exempt',
150  'managechangetags',
151  'markbotedits',
152  'mergehistory',
153  'minoredit',
154  'move',
155  'movefile',
156  'move-categorypages',
157  'move-rootuserpages',
158  'move-subpages',
159  'nominornewtalk',
160  'noratelimit',
161  'override-export-depth',
162  'pagelang',
163  'patrol',
164  'patrolmarks',
165  'protect',
166  'purge',
167  'read',
168  'reupload',
169  'reupload-own',
170  'reupload-shared',
171  'rollback',
172  'sendemail',
173  'siteadmin',
174  'suppressionlog',
175  'suppressredirect',
176  'suppressrevision',
177  'unblockself',
178  'undelete',
179  'unwatchedpages',
180  'upload',
181  'upload_by_url',
182  'userrights',
183  'userrights-interwiki',
184  'viewmyprivateinfo',
185  'viewmywatchlist',
186  'viewsuppressed',
187  'writeapi',
188  ];
189 
196  public function __construct(
201  ) {
202  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
203  $this->options = $options;
204  $this->specialPageFactory = $specialPageFactory;
205  $this->revisionLookup = $revisionLookup;
206  $this->nsInfo = $nsInfo;
207  }
208 
228  public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
229  return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
230  }
231 
247  public function quickUserCan( $action, User $user, LinkTarget $page ) {
248  return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
249  }
250 
268  public function getPermissionErrors(
269  $action,
270  User $user,
271  LinkTarget $page,
272  $rigor = self::RIGOR_SECURE,
273  $ignoreErrors = []
274  ) {
275  $errors = $this->getPermissionErrorsInternal( $action, $user, $page, $rigor );
276 
277  // Remove the errors being ignored.
278  foreach ( $errors as $index => $error ) {
279  $errKey = is_array( $error ) ? $error[0] : $error;
280 
281  if ( in_array( $errKey, $ignoreErrors ) ) {
282  unset( $errors[$index] );
283  }
284  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
285  unset( $errors[$index] );
286  }
287  }
288 
289  return $errors;
290  }
291 
302  public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
303  $block = $user->getBlock( $fromReplica );
304  if ( !$block ) {
305  return false;
306  }
307 
308  // TODO: remove upon further migration to LinkTarget
309  $title = Title::newFromLinkTarget( $page );
310 
311  $blocked = $user->isHidden();
312  if ( !$blocked ) {
313  // Special handling for a user's own talk page. The block is not aware
314  // of the user, so this must be done here.
315  if ( $title->equals( $user->getTalkPage() ) ) {
316  $blocked = $block->appliesToUsertalk( $title );
317  } else {
318  $blocked = $block->appliesToTitle( $title );
319  }
320  }
321 
322  // only for the purpose of the hook. We really don't need this here.
323  $allowUsertalk = $user->isAllowUsertalk();
324 
325  // Allow extensions to let a blocked user access a particular page
326  Hooks::run( 'UserIsBlockedFrom', [ $user, $title, &$blocked, &$allowUsertalk ] );
327 
328  return $blocked;
329  }
330 
348  private function getPermissionErrorsInternal(
349  $action,
350  User $user,
351  LinkTarget $page,
352  $rigor = self::RIGOR_SECURE,
353  $short = false
354  ) {
355  if ( !in_array( $rigor, [ self::RIGOR_QUICK, self::RIGOR_FULL, self::RIGOR_SECURE ] ) ) {
356  throw new Exception( "Invalid rigor parameter '$rigor'." );
357  }
358 
359  # Read has special handling
360  if ( $action == 'read' ) {
361  $checks = [
362  'checkPermissionHooks',
363  'checkReadPermissions',
364  'checkUserBlock', // for wgBlockDisablesLogin
365  ];
366  # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
367  # or checkUserConfigPermissions here as it will lead to duplicate
368  # error messages. This is okay to do since anywhere that checks for
369  # create will also check for edit, and those checks are called for edit.
370  } elseif ( $action == 'create' ) {
371  $checks = [
372  'checkQuickPermissions',
373  'checkPermissionHooks',
374  'checkPageRestrictions',
375  'checkCascadingSourcesRestrictions',
376  'checkActionPermissions',
377  'checkUserBlock'
378  ];
379  } else {
380  $checks = [
381  'checkQuickPermissions',
382  'checkPermissionHooks',
383  'checkSpecialsAndNSPermissions',
384  'checkSiteConfigPermissions',
385  'checkUserConfigPermissions',
386  'checkPageRestrictions',
387  'checkCascadingSourcesRestrictions',
388  'checkActionPermissions',
389  'checkUserBlock'
390  ];
391  }
392 
393  $errors = [];
394  foreach ( $checks as $method ) {
395  $errors = $this->$method( $action, $user, $errors, $rigor, $short, $page );
396 
397  if ( $short && $errors !== [] ) {
398  break;
399  }
400  }
401 
402  return $errors;
403  }
404 
421  private function checkPermissionHooks(
422  $action,
423  User $user,
424  $errors,
425  $rigor,
426  $short,
427  LinkTarget $page
428  ) {
429  // TODO: remove when LinkTarget usage will expand further
430  $title = Title::newFromLinkTarget( $page );
431  // Use getUserPermissionsErrors instead
432  $result = '';
433  if ( !Hooks::run( 'userCan', [ &$title, &$user, $action, &$result ] ) ) {
434  return $result ? [] : [ [ 'badaccess-group0' ] ];
435  }
436  // Check getUserPermissionsErrors hook
437  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$title, &$user, $action, &$result ] ) ) {
438  $errors = $this->resultToError( $errors, $result );
439  }
440  // Check getUserPermissionsErrorsExpensive hook
441  if (
442  $rigor !== self::RIGOR_QUICK
443  && !( $short && count( $errors ) > 0 )
444  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$title, &$user, $action, &$result ] )
445  ) {
446  $errors = $this->resultToError( $errors, $result );
447  }
448 
449  return $errors;
450  }
451 
460  private function resultToError( $errors, $result ) {
461  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
462  // A single array representing an error
463  $errors[] = $result;
464  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
465  // A nested array representing multiple errors
466  $errors = array_merge( $errors, $result );
467  } elseif ( $result !== '' && is_string( $result ) ) {
468  // A string representing a message-id
469  $errors[] = [ $result ];
470  } elseif ( $result instanceof MessageSpecifier ) {
471  // A message specifier representing an error
472  $errors[] = [ $result ];
473  } elseif ( $result === false ) {
474  // a generic "We don't want them to do that"
475  $errors[] = [ 'badaccess-group0' ];
476  }
477  return $errors;
478  }
479 
496  private function checkReadPermissions(
497  $action,
498  User $user,
499  $errors,
500  $rigor,
501  $short,
502  LinkTarget $page
503  ) {
504  // TODO: remove when LinkTarget usage will expand further
505  $title = Title::newFromLinkTarget( $page );
506 
507  $whiteListRead = $this->options->get( 'WhitelistRead' );
508  $whitelisted = false;
509  if ( $this->isEveryoneAllowed( 'read' ) ) {
510  # Shortcut for public wikis, allows skipping quite a bit of code
511  $whitelisted = true;
512  } elseif ( $this->userHasRight( $user, 'read' ) ) {
513  # If the user is allowed to read pages, he is allowed to read all pages
514  $whitelisted = true;
515  } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
516  || $this->isSameSpecialPage( 'PasswordReset', $title )
517  || $this->isSameSpecialPage( 'Userlogout', $title )
518  ) {
519  # Always grant access to the login page.
520  # Even anons need to be able to log in.
521  $whitelisted = true;
522  } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
523  # Time to check the whitelist
524  # Only do these checks is there's something to check against
525  $name = $title->getPrefixedText();
526  $dbName = $title->getPrefixedDBkey();
527 
528  // Check for explicit whitelisting with and without underscores
529  if ( in_array( $name, $whiteListRead, true )
530  || in_array( $dbName, $whiteListRead, true ) ) {
531  $whitelisted = true;
532  } elseif ( $title->getNamespace() == NS_MAIN ) {
533  # Old settings might have the title prefixed with
534  # a colon for main-namespace pages
535  if ( in_array( ':' . $name, $whiteListRead ) ) {
536  $whitelisted = true;
537  }
538  } elseif ( $title->isSpecialPage() ) {
539  # If it's a special page, ditch the subpage bit and check again
540  $name = $title->getDBkey();
541  list( $name, /* $subpage */ ) =
542  $this->specialPageFactory->resolveAlias( $name );
543  if ( $name ) {
544  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
545  if ( in_array( $pure, $whiteListRead, true ) ) {
546  $whitelisted = true;
547  }
548  }
549  }
550  }
551 
552  $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
553  if ( !$whitelisted && is_array( $whitelistReadRegexp )
554  && !empty( $whitelistReadRegexp ) ) {
555  $name = $title->getPrefixedText();
556  // Check for regex whitelisting
557  foreach ( $whitelistReadRegexp as $listItem ) {
558  if ( preg_match( $listItem, $name ) ) {
559  $whitelisted = true;
560  break;
561  }
562  }
563  }
564 
565  if ( !$whitelisted ) {
566  # If the title is not whitelisted, give extensions a chance to do so...
567  Hooks::run( 'TitleReadWhitelist', [ $title, $user, &$whitelisted ] );
568  if ( !$whitelisted ) {
569  $errors[] = $this->missingPermissionError( $action, $short );
570  }
571  }
572 
573  return $errors;
574  }
575 
584  private function missingPermissionError( $action, $short ) {
585  // We avoid expensive display logic for quickUserCan's and such
586  if ( $short ) {
587  return [ 'badaccess-group0' ];
588  }
589 
590  // TODO: it would be a good idea to replace the method below with something else like
591  // maybe callback injection
592  return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
593  }
594 
603  private function isSameSpecialPage( $name, LinkTarget $page ) {
604  if ( $page->getNamespace() == NS_SPECIAL ) {
605  list( $thisName, /* $subpage */ ) =
606  $this->specialPageFactory->resolveAlias( $page->getDBkey() );
607  if ( $name == $thisName ) {
608  return true;
609  }
610  }
611  return false;
612  }
613 
630  private function checkUserBlock(
631  $action,
632  User $user,
633  $errors,
634  $rigor,
635  $short,
636  LinkTarget $page
637  ) {
638  // Account creation blocks handled at userlogin.
639  // Unblocking handled in SpecialUnblock
640  if ( $rigor === self::RIGOR_QUICK || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
641  return $errors;
642  }
643 
644  // Optimize for a very common case
645  if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
646  return $errors;
647  }
648 
649  if ( $this->options->get( 'EmailConfirmToEdit' )
650  && !$user->isEmailConfirmed()
651  && $action === 'edit'
652  ) {
653  $errors[] = [ 'confirmedittext' ];
654  }
655 
656  $useReplica = ( $rigor !== self::RIGOR_SECURE );
657  $block = $user->getBlock( $useReplica );
658 
659  // If the user does not have a block, or the block they do have explicitly
660  // allows the action (like "read" or "upload").
661  if ( !$block || $block->appliesToRight( $action ) === false ) {
662  return $errors;
663  }
664 
665  // Determine if the user is blocked from this action on this page.
666  // What gets passed into this method is a user right, not an action name.
667  // There is no way to instantiate an action by restriction. However, this
668  // will get the action where the restriction is the same. This may result
669  // in actions being blocked that shouldn't be.
670  $actionObj = null;
671  if ( Action::exists( $action ) ) {
672  // TODO: this drags a ton of dependencies in, would be good to avoid WikiPage
673  // instantiation and decouple it creating an ActionPermissionChecker interface
674  $wikiPage = WikiPage::factory( Title::newFromLinkTarget( $page, 'clone' ) );
675  // Creating an action will perform several database queries to ensure that
676  // the action has not been overridden by the content type.
677  // FIXME: avoid use of RequestContext since it drags in User and Title dependencies
678  // probably we may use fake context object since it's unlikely that Action uses it
679  // anyway. It would be nice if we could avoid instantiating the Action at all.
680  $actionObj = Action::factory( $action, $wikiPage, RequestContext::getMain() );
681  // Ensure that the retrieved action matches the restriction.
682  if ( $actionObj && $actionObj->getRestriction() !== $action ) {
683  $actionObj = null;
684  }
685  }
686 
687  // If no action object is returned, assume that the action requires unblock
688  // which is the default.
689  if ( !$actionObj || $actionObj->requiresUnblock() ) {
690  if ( $this->isBlockedFrom( $user, $page, $useReplica ) ) {
691  // @todo FIXME: Pass the relevant context into this function.
692  $errors[] = $block->getPermissionsError( RequestContext::getMain() );
693  }
694  }
695 
696  return $errors;
697  }
698 
715  private function checkQuickPermissions(
716  $action,
717  User $user,
718  $errors,
719  $rigor,
720  $short,
721  LinkTarget $page
722  ) {
723  // TODO: remove when LinkTarget usage will expand further
724  $title = Title::newFromLinkTarget( $page );
725 
726  if ( !Hooks::run( 'TitleQuickPermissions',
727  [ $title, $user, $action, &$errors, ( $rigor !== self::RIGOR_QUICK ), $short ] )
728  ) {
729  return $errors;
730  }
731 
732  $isSubPage = $this->nsInfo->hasSubpages( $title->getNamespace() ) ?
733  strpos( $title->getText(), '/' ) !== false : false;
734 
735  if ( $action == 'create' ) {
736  if (
737  ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
738  !$this->userHasRight( $user, 'createtalk' ) ) ||
739  ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
740  !$this->userHasRight( $user, 'createpage' ) )
741  ) {
742  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
743  }
744  } elseif ( $action == 'move' ) {
745  if ( !$this->userHasRight( $user, 'move-rootuserpages' )
746  && $title->getNamespace() == NS_USER && !$isSubPage ) {
747  // Show user page-specific message only if the user can move other pages
748  $errors[] = [ 'cant-move-user-page' ];
749  }
750 
751  // Check if user is allowed to move files if it's a file
752  if ( $title->getNamespace() == NS_FILE &&
753  !$this->userHasRight( $user, 'movefile' ) ) {
754  $errors[] = [ 'movenotallowedfile' ];
755  }
756 
757  // Check if user is allowed to move category pages if it's a category page
758  if ( $title->getNamespace() == NS_CATEGORY &&
759  !$this->userHasRight( $user, 'move-categorypages' ) ) {
760  $errors[] = [ 'cant-move-category-page' ];
761  }
762 
763  if ( !$this->userHasRight( $user, 'move' ) ) {
764  // User can't move anything
765  $userCanMove = $this->groupHasPermission( 'user', 'move' );
766  $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
767  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
768  // custom message if logged-in users without any special rights can move
769  $errors[] = [ 'movenologintext' ];
770  } else {
771  $errors[] = [ 'movenotallowed' ];
772  }
773  }
774  } elseif ( $action == 'move-target' ) {
775  if ( !$this->userHasRight( $user, 'move' ) ) {
776  // User can't move anything
777  $errors[] = [ 'movenotallowed' ];
778  } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
779  && $title->getNamespace() == NS_USER && !$isSubPage ) {
780  // Show user page-specific message only if the user can move other pages
781  $errors[] = [ 'cant-move-to-user-page' ];
782  } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
783  && $title->getNamespace() == NS_CATEGORY ) {
784  // Show category page-specific message only if the user can move other pages
785  $errors[] = [ 'cant-move-to-category-page' ];
786  }
787  } elseif ( !$this->userHasRight( $user, $action ) ) {
788  $errors[] = $this->missingPermissionError( $action, $short );
789  }
790 
791  return $errors;
792  }
793 
812  private function checkPageRestrictions(
813  $action,
814  User $user,
815  $errors,
816  $rigor,
817  $short,
818  LinkTarget $page
819  ) {
820  // TODO: remove & rework upon further use of LinkTarget
821  $title = Title::newFromLinkTarget( $page );
822  foreach ( $title->getRestrictions( $action ) as $right ) {
823  // Backwards compatibility, rewrite sysop -> editprotected
824  if ( $right == 'sysop' ) {
825  $right = 'editprotected';
826  }
827  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
828  if ( $right == 'autoconfirmed' ) {
829  $right = 'editsemiprotected';
830  }
831  if ( $right == '' ) {
832  continue;
833  }
834  if ( !$this->userHasRight( $user, $right ) ) {
835  $errors[] = [ 'protectedpagetext', $right, $action ];
836  } elseif ( $title->areRestrictionsCascading() &&
837  !$this->userHasRight( $user, 'protect' ) ) {
838  $errors[] = [ 'protectedpagetext', 'protect', $action ];
839  }
840  }
841 
842  return $errors;
843  }
844 
862  $action,
863  UserIdentity $user,
864  $errors,
865  $rigor,
866  $short,
867  LinkTarget $page
868  ) {
869  // TODO: remove & rework upon further use of LinkTarget
870  $title = Title::newFromLinkTarget( $page );
871  if ( $rigor !== self::RIGOR_QUICK && !$title->isUserConfigPage() ) {
872  # We /could/ use the protection level on the source page, but it's
873  # fairly ugly as we have to establish a precedence hierarchy for pages
874  # included by multiple cascade-protected pages. So just restrict
875  # it to people with 'protect' permission, as they could remove the
876  # protection anyway.
877  list( $cascadingSources, $restrictions ) = $title->getCascadeProtectionSources();
878  # Cascading protection depends on more than this page...
879  # Several cascading protected pages may include this page...
880  # Check each cascading level
881  # This is only for protection restrictions, not for all actions
882  if ( isset( $restrictions[$action] ) ) {
883  foreach ( $restrictions[$action] as $right ) {
884  // Backwards compatibility, rewrite sysop -> editprotected
885  if ( $right == 'sysop' ) {
886  $right = 'editprotected';
887  }
888  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
889  if ( $right == 'autoconfirmed' ) {
890  $right = 'editsemiprotected';
891  }
892  if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
893  $wikiPages = '';
895  foreach ( $cascadingSources as $wikiPage ) {
896  $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
897  }
898  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $wikiPages, $action ];
899  }
900  }
901  }
902  }
903 
904  return $errors;
905  }
906 
923  private function checkActionPermissions(
924  $action,
925  User $user,
926  $errors,
927  $rigor,
928  $short,
929  LinkTarget $page
930  ) {
932 
933  // TODO: remove & rework upon further use of LinkTarget
934  $title = Title::newFromLinkTarget( $page );
935 
936  if ( $action == 'protect' ) {
937  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
938  // If they can't edit, they shouldn't protect.
939  $errors[] = [ 'protect-cantedit' ];
940  }
941  } elseif ( $action == 'create' ) {
942  $title_protection = $title->getTitleProtection();
943  if ( $title_protection ) {
944  if ( $title_protection['permission'] == ''
945  || !$this->userHasRight( $user, $title_protection['permission'] )
946  ) {
947  $errors[] = [
948  'titleprotected',
949  // TODO: get rid of the User dependency
950  User::whoIs( $title_protection['user'] ),
951  $title_protection['reason']
952  ];
953  }
954  }
955  } elseif ( $action == 'move' ) {
956  // Check for immobile pages
957  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
958  // Specific message for this case
959  $errors[] = [ 'immobile-source-namespace', $title->getNsText() ];
960  } elseif ( !$title->isMovable() ) {
961  // Less specific message for rarer cases
962  $errors[] = [ 'immobile-source-page' ];
963  }
964  } elseif ( $action == 'move-target' ) {
965  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
966  $errors[] = [ 'immobile-target-namespace', $title->getNsText() ];
967  } elseif ( !$title->isMovable() ) {
968  $errors[] = [ 'immobile-target-page' ];
969  }
970  } elseif ( $action == 'delete' ) {
971  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true, $title );
972  if ( !$tempErrors ) {
973  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
974  $user, $tempErrors, $rigor, true, $title );
975  }
976  if ( $tempErrors ) {
977  // If protection keeps them from editing, they shouldn't be able to delete.
978  $errors[] = [ 'deleteprotected' ];
979  }
980  if ( $rigor !== self::RIGOR_QUICK && $wgDeleteRevisionsLimit
981  && !$this->userCan( 'bigdelete', $user, $title ) && $title->isBigDeletion()
982  ) {
983  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
984  }
985  } elseif ( $action === 'undelete' ) {
986  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
987  // Undeleting implies editing
988  $errors[] = [ 'undelete-cantedit' ];
989  }
990  if ( !$title->exists()
991  && count( $this->getPermissionErrorsInternal( 'create', $user, $title, $rigor, true ) )
992  ) {
993  // Undeleting where nothing currently exists implies creating
994  $errors[] = [ 'undelete-cantcreate' ];
995  }
996  }
997  return $errors;
998  }
999 
1017  $action,
1018  UserIdentity $user,
1019  $errors,
1020  $rigor,
1021  $short,
1022  LinkTarget $page
1023  ) {
1024  // TODO: remove & rework upon further use of LinkTarget
1025  $title = Title::newFromLinkTarget( $page );
1026 
1027  # Only 'createaccount' can be performed on special pages,
1028  # which don't actually exist in the DB.
1029  if ( $title->getNamespace() == NS_SPECIAL && $action !== 'createaccount' ) {
1030  $errors[] = [ 'ns-specialprotected' ];
1031  }
1032 
1033  # Check $wgNamespaceProtection for restricted namespaces
1034  if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
1035  $ns = $title->getNamespace() == NS_MAIN ?
1036  wfMessage( 'nstab-main' )->text() : $title->getNsText();
1037  $errors[] = $title->getNamespace() == NS_MEDIAWIKI ?
1038  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
1039  }
1040 
1041  return $errors;
1042  }
1043 
1060  private function checkSiteConfigPermissions(
1061  $action,
1062  User $user,
1063  $errors,
1064  $rigor,
1065  $short,
1066  LinkTarget $page
1067  ) {
1068  // TODO: remove & rework upon further use of LinkTarget
1069  $title = Title::newFromLinkTarget( $page );
1070 
1071  if ( $action != 'patrol' ) {
1072  $error = null;
1073  // Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the
1074  // editinterface right. That's implemented as a restriction so no check needed here.
1075  if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
1076  $error = [ 'sitecssprotected', $action ];
1077  } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
1078  $error = [ 'sitejsonprotected', $action ];
1079  } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
1080  $error = [ 'sitejsprotected', $action ];
1081  } elseif ( $title->isRawHtmlMessage() ) {
1082  // Raw HTML can be used to deploy CSS or JS so require rights for both.
1083  if ( !$this->userHasRight( $user, 'editsitejs' ) ) {
1084  $error = [ 'sitejsprotected', $action ];
1085  } elseif ( !$this->userHasRight( $user, 'editsitecss' ) ) {
1086  $error = [ 'sitecssprotected', $action ];
1087  }
1088  }
1089 
1090  if ( $error ) {
1091  if ( $this->userHasRight( $user, 'editinterface' ) ) {
1092  // Most users / site admins will probably find out about the new, more restrictive
1093  // permissions by failing to edit something. Give them more info.
1094  // TODO remove this a few release cycles after 1.32
1095  $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
1096  }
1097  $errors[] = $error;
1098  }
1099  }
1100 
1101  return $errors;
1102  }
1103 
1120  private function checkUserConfigPermissions(
1121  $action,
1122  UserIdentity $user,
1123  $errors,
1124  $rigor,
1125  $short,
1126  LinkTarget $page
1127  ) {
1128  // TODO: remove & rework upon further use of LinkTarget
1129  $title = Title::newFromLinkTarget( $page );
1130 
1131  # Protect css/json/js subpages of user pages
1132  # XXX: this might be better using restrictions
1133 
1134  if ( $action === 'patrol' ) {
1135  return $errors;
1136  }
1137 
1138  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $title->getText() ) ) {
1139  // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
1140  if (
1141  $title->isUserCssConfigPage()
1142  && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
1143  ) {
1144  $errors[] = [ 'mycustomcssprotected', $action ];
1145  } elseif (
1146  $title->isUserJsonConfigPage()
1147  && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
1148  ) {
1149  $errors[] = [ 'mycustomjsonprotected', $action ];
1150  } elseif (
1151  $title->isUserJsConfigPage()
1152  && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
1153  ) {
1154  $errors[] = [ 'mycustomjsprotected', $action ];
1155  } elseif (
1156  $title->isUserJsConfigPage()
1157  && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
1158  ) {
1159  // T207750 - do not allow users to edit a redirect if they couldn't edit the target
1160  $rev = $this->revisionLookup->getRevisionByTitle( $title );
1161  $content = $rev ? $rev->getContent( 'main', RevisionRecord::RAW ) : null;
1162  $target = $content ? $content->getUltimateRedirectTarget() : null;
1163  if ( $target && (
1164  !$target->inNamespace( NS_USER )
1165  || !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $target->getText() )
1166  ) ) {
1167  $errors[] = [ 'mycustomjsredirectprotected', $action ];
1168  }
1169  }
1170  } else {
1171  // Users need editmyuser* to edit their own CSS/JSON/JS subpages, except for
1172  // deletion/suppression which cannot be used for attacks and we want to avoid the
1173  // situation where an unprivileged user can post abusive content on their subpages
1174  // and only very highly privileged users could remove it.
1175  if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
1176  if (
1177  $title->isUserCssConfigPage()
1178  && !$this->userHasRight( $user, 'editusercss' )
1179  ) {
1180  $errors[] = [ 'customcssprotected', $action ];
1181  } elseif (
1182  $title->isUserJsonConfigPage()
1183  && !$this->userHasRight( $user, 'edituserjson' )
1184  ) {
1185  $errors[] = [ 'customjsonprotected', $action ];
1186  } elseif (
1187  $title->isUserJsConfigPage()
1188  && !$this->userHasRight( $user, 'edituserjs' )
1189  ) {
1190  $errors[] = [ 'customjsprotected', $action ];
1191  }
1192  }
1193  }
1194 
1195  return $errors;
1196  }
1197 
1208  public function userHasRight( UserIdentity $user, $action = '' ) {
1209  if ( $action === '' ) {
1210  return true; // In the spirit of DWIM
1211  }
1212  // Use strict parameter to avoid matching numeric 0 accidentally inserted
1213  // by misconfiguration: 0 == 'foo'
1214  return in_array( $action, $this->getUserPermissions( $user ), true );
1215  }
1216 
1226  public function userHasAnyRight( UserIdentity $user ) {
1227  $actions = array_slice( func_get_args(), 1 );
1228  foreach ( $actions as $action ) {
1229  if ( $this->userHasRight( $user, $action ) ) {
1230  return true;
1231  }
1232  }
1233  return false;
1234  }
1235 
1245  public function userHasAllRights( UserIdentity $user ) {
1246  $actions = array_slice( func_get_args(), 1 );
1247  foreach ( $actions as $action ) {
1248  if ( !$this->userHasRight( $user, $action ) ) {
1249  return false;
1250  }
1251  }
1252  return true;
1253  }
1254 
1264  public function getUserPermissions( UserIdentity $user ) {
1265  $user = User::newFromIdentity( $user );
1266  $rightsCacheKey = $this->getRightsCacheKey( $user );
1267  if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1268  $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
1269  $user->getEffectiveGroups()
1270  );
1271  Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
1272 
1273  // Deny any rights denied by the user's session, unless this
1274  // endpoint has no sessions.
1275  if ( !defined( 'MW_NO_SESSION' ) ) {
1276  // FIXME: $user->getRequest().. need to be replaced with something else
1277  $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
1278  if ( $allowedRights !== null ) {
1279  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1280  $this->usersRights[ $rightsCacheKey ],
1281  $allowedRights
1282  );
1283  }
1284  }
1285 
1286  Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
1287  // Force reindexation of rights when a hook has unset one of them
1288  $this->usersRights[ $rightsCacheKey ] = array_values(
1289  array_unique( $this->usersRights[ $rightsCacheKey ] )
1290  );
1291 
1292  if (
1293  $user->isLoggedIn() &&
1294  $this->options->get( 'BlockDisablesLogin' ) &&
1295  $user->getBlock()
1296  ) {
1297  $anon = new User;
1298  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1299  $this->usersRights[ $rightsCacheKey ],
1300  $this->getUserPermissions( $anon )
1301  );
1302  }
1303  }
1304  $rights = $this->usersRights[ $rightsCacheKey ];
1305  foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
1306  $rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
1307  }
1308  return $rights;
1309  }
1310 
1319  public function invalidateUsersRightsCache( $user = null ) {
1320  if ( $user !== null ) {
1321  $rightsCacheKey = $this->getRightsCacheKey( $user );
1322  if ( isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1323  unset( $this->usersRights[ $rightsCacheKey ] );
1324  }
1325  } else {
1326  $this->usersRights = null;
1327  }
1328  }
1329 
1335  private function getRightsCacheKey( UserIdentity $user ) {
1336  return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
1337  }
1338 
1353  public function groupHasPermission( $group, $role ) {
1354  $groupPermissions = $this->options->get( 'GroupPermissions' );
1355  $revokePermissions = $this->options->get( 'RevokePermissions' );
1356  return isset( $groupPermissions[$group][$role] ) && $groupPermissions[$group][$role] &&
1357  !( isset( $revokePermissions[$group][$role] ) && $revokePermissions[$group][$role] );
1358  }
1359 
1368  public function getGroupPermissions( $groups ) {
1369  $rights = [];
1370  // grant every granted permission first
1371  foreach ( $groups as $group ) {
1372  if ( isset( $this->options->get( 'GroupPermissions' )[$group] ) ) {
1373  $rights = array_merge( $rights,
1374  // array_filter removes empty items
1375  array_keys( array_filter( $this->options->get( 'GroupPermissions' )[$group] ) ) );
1376  }
1377  }
1378  // now revoke the revoked permissions
1379  foreach ( $groups as $group ) {
1380  if ( isset( $this->options->get( 'RevokePermissions' )[$group] ) ) {
1381  $rights = array_diff( $rights,
1382  array_keys( array_filter( $this->options->get( 'RevokePermissions' )[$group] ) ) );
1383  }
1384  }
1385  return array_unique( $rights );
1386  }
1387 
1396  public function getGroupsWithPermission( $role ) {
1397  $allowedGroups = [];
1398  foreach ( array_keys( $this->options->get( 'GroupPermissions' ) ) as $group ) {
1399  if ( $this->groupHasPermission( $group, $role ) ) {
1400  $allowedGroups[] = $group;
1401  }
1402  }
1403  return $allowedGroups;
1404  }
1405 
1421  public function isEveryoneAllowed( $right ) {
1422  // Use the cached results, except in unit tests which rely on
1423  // being able change the permission mid-request
1424  if ( isset( $this->cachedRights[$right] ) ) {
1425  return $this->cachedRights[$right];
1426  }
1427 
1428  if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
1429  || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
1430  $this->cachedRights[$right] = false;
1431  return false;
1432  }
1433 
1434  // If it's revoked anywhere, then everyone doesn't have it
1435  foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
1436  if ( isset( $rights[$right] ) && $rights[$right] ) {
1437  $this->cachedRights[$right] = false;
1438  return false;
1439  }
1440  }
1441 
1442  // Remove any rights that aren't allowed to the global-session user,
1443  // unless there are no sessions for this endpoint.
1444  if ( !defined( 'MW_NO_SESSION' ) ) {
1445 
1446  // XXX: think what could be done with the below
1447  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
1448  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
1449  $this->cachedRights[$right] = false;
1450  return false;
1451  }
1452  }
1453 
1454  // Allow extensions to say false
1455  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
1456  $this->cachedRights[$right] = false;
1457  return false;
1458  }
1459 
1460  $this->cachedRights[$right] = true;
1461  return true;
1462  }
1463 
1471  public function getAllPermissions() {
1472  if ( $this->allRights === null ) {
1473  if ( count( $this->options->get( 'AvailableRights' ) ) ) {
1474  $this->allRights = array_unique( array_merge(
1475  $this->coreRights,
1476  $this->options->get( 'AvailableRights' )
1477  ) );
1478  } else {
1479  $this->allRights = $this->coreRights;
1480  }
1481  Hooks::run( 'UserGetAllRights', [ &$this->allRights ] );
1482  }
1483  return $this->allRights;
1484  }
1485 
1492  private function isNamespaceProtected( $index, UserIdentity $user ) {
1493  $namespaceProtection = $this->options->get( 'NamespaceProtection' );
1494  if ( isset( $namespaceProtection[$index] ) ) {
1495  return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
1496  }
1497  return false;
1498  }
1499 
1508  public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
1509  if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
1510  // All levels are valid if there's no namespace restriction.
1511  // But still filter by user, if necessary
1512  $levels = $this->options->get( 'RestrictionLevels' );
1513  if ( $user ) {
1514  $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
1515  $right = $level;
1516  if ( $right == 'sysop' ) {
1517  $right = 'editprotected'; // BC
1518  }
1519  if ( $right == 'autoconfirmed' ) {
1520  $right = 'editsemiprotected'; // BC
1521  }
1522  return $this->userHasRight( $user, $right );
1523  } ) );
1524  }
1525  return $levels;
1526  }
1527 
1528  // $wgNamespaceProtection can require one or more rights to edit the namespace, which
1529  // may be satisfied by membership in multiple groups each giving a subset of those rights.
1530  // A restriction level is redundant if, for any one of the namespace rights, all groups
1531  // giving that right also give the restriction level's right. Or, conversely, a
1532  // restriction level is not redundant if, for every namespace right, there's at least one
1533  // group giving that right without the restriction level's right.
1534  //
1535  // First, for each right, get a list of groups with that right.
1536  $namespaceRightGroups = [];
1537  foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
1538  if ( $right == 'sysop' ) {
1539  $right = 'editprotected'; // BC
1540  }
1541  if ( $right == 'autoconfirmed' ) {
1542  $right = 'editsemiprotected'; // BC
1543  }
1544  if ( $right != '' ) {
1545  $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
1546  }
1547  }
1548 
1549  // Now, go through the protection levels one by one.
1550  $usableLevels = [ '' ];
1551  foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
1552  $right = $level;
1553  if ( $right == 'sysop' ) {
1554  $right = 'editprotected'; // BC
1555  }
1556  if ( $right == 'autoconfirmed' ) {
1557  $right = 'editsemiprotected'; // BC
1558  }
1559 
1560  if ( $right != '' &&
1561  !isset( $namespaceRightGroups[$right] ) &&
1562  ( !$user || $this->userHasRight( $user, $right ) )
1563  ) {
1564  // Do any of the namespace rights imply the restriction right? (see explanation above)
1565  foreach ( $namespaceRightGroups as $groups ) {
1566  if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
1567  // Yes, this one does.
1568  continue 2;
1569  }
1570  }
1571  // No, keep the restriction level
1572  $usableLevels[] = $level;
1573  }
1574  }
1575 
1576  return $usableLevels;
1577  }
1578 
1593  public function addTemporaryUserRights( UserIdentity $user, $rights ) {
1594  $userId = $user->getId();
1595  $nextKey = count( $this->temporaryUserRights[$userId] ?? [] );
1596  $this->temporaryUserRights[$userId][$nextKey] = (array)$rights;
1597  return new ScopedCallback( function () use ( $userId, $nextKey ) {
1598  unset( $this->temporaryUserRights[$userId][$nextKey] );
1599  } );
1600  }
1601 
1612  public function overrideUserRightsForTesting( $user, $rights = [] ) {
1613  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
1614  throw new Exception( __METHOD__ . ' can not be called outside of tests' );
1615  }
1616  $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
1617  is_array( $rights ) ? $rights : [ $rights ];
1618  }
1619 
1620 }
MediaWiki\Permissions\PermissionManager\$cachedRights
bool[] $cachedRights
Cached rights for isEveryoneAllowed, [ right => allowed ].
Definition: PermissionManager.php:99
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:861
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:3532
MediaWiki\Permissions\PermissionManager\checkSpecialsAndNSPermissions
checkSpecialsAndNSPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check permissions on special pages & namespaces.
Definition: PermissionManager.php:1016
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:348
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5284
MediaWiki\Permissions\PermissionManager\userCan
userCan( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE)
Can $user perform $action on a page?
Definition: PermissionManager.php:228
MessageSpecifier
Definition: MessageSpecifier.php:21
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:47
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:584
MediaWiki\Permissions\PermissionManager\checkPermissionHooks
checkPermissionHooks( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check various permission hooks.
Definition: PermissionManager.php:421
NS_FILE
const NS_FILE
Definition: Defines.php:66
MediaWiki\Permissions\PermissionManager\userHasRight
userHasRight(UserIdentity $user, $action='')
Testing a permission.
Definition: PermissionManager.php:1208
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1264
MediaWiki\Permissions\PermissionManager\$coreRights
$coreRights
Array of Strings Core rights.
Definition: PermissionManager.php:107
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:571
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:1319
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
User\getTalkPage
getTalkPage()
Get this user's talk page title.
Definition: User.php:4284
MediaWiki\Permissions\PermissionManager\resultToError
resultToError( $errors, $result)
Add the resulting error code to the errors array.
Definition: PermissionManager.php:460
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
MediaWiki\Permissions\PermissionManager\$revisionLookup
RevisionLookup $revisionLookup
Definition: PermissionManager.php:81
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:1120
MediaWiki\Permissions\PermissionManager\$usersRights
string[][] $usersRights
Cached user rights.
Definition: PermissionManager.php:90
MediaWiki\Permissions\PermissionManager\userHasAllRights
userHasAllRights(UserIdentity $user)
Check if user is allowed to make all actions.
Definition: PermissionManager.php:1245
MediaWiki\Permissions\PermissionManager\$allRights
string[] null $allRights
Cached results of getAllRights()
Definition: PermissionManager.php:87
$wgLang
$wgLang
Definition: Setup.php:881
MediaWiki\Permissions\PermissionManager\isSameSpecialPage
isSameSpecialPage( $name, LinkTarget $page)
Returns true if this title resolves to the named special page.
Definition: PermissionManager.php:603
User\isHidden
isHidden()
Check if user account is hidden.
Definition: User.php:2186
MediaWiki\Permissions\PermissionManager\getRightsCacheKey
getRightsCacheKey(UserIdentity $user)
Gets a unique key for user rights cache.
Definition: PermissionManager.php:1335
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:1508
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:1492
Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:60
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:34
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:812
MediaWiki\Permissions\PermissionManager\$specialPageFactory
SpecialPageFactory $specialPageFactory
Definition: PermissionManager.php:78
MediaWiki\Permissions\PermissionManager\$nsInfo
NamespaceInfo $nsInfo
Definition: PermissionManager.php:84
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:715
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:33
$wgDeleteRevisionsLimit
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
Definition: DefaultSettings.php:5533
User\getBlock
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2068
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:630
MediaWiki\Permissions\PermissionManager\checkActionPermissions
checkActionPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check action permissions not already checked in checkQuickPermissions.
Definition: PermissionManager.php:923
MediaWiki\Permissions\PermissionManager\isEveryoneAllowed
isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
Definition: PermissionManager.php:1421
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:496
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:1264
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:47
$content
$content
Definition: router.php:78
User\whoIs
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:809
MediaWiki\Permissions\PermissionManager\getAllPermissions
getAllPermissions()
Get a list of all available permissions.
Definition: PermissionManager.php:1471
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:1396
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:247
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:1593
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:268
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:431
MediaWiki\Permissions\PermissionManager\__construct
__construct(ServiceOptions $options, SpecialPageFactory $specialPageFactory, RevisionLookup $revisionLookup, NamespaceInfo $nsInfo)
Definition: PermissionManager.php:196
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:268
MediaWiki\Permissions\PermissionManager\checkSiteConfigPermissions
checkSiteConfigPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check sitewide CSS/JSON/JS permissions.
Definition: PermissionManager.php:1060
MediaWiki\Permissions\PermissionManager\groupHasPermission
groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: PermissionManager.php:1353
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:48
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
User\isEmailConfirmed
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4634
User\isAllowUsertalk
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5340
NS_USER
const NS_USER
Definition: Defines.php:62
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\Permissions\PermissionManager\userHasAnyRight
userHasAnyRight(UserIdentity $user)
Check if user is allowed to make any action.
Definition: PermissionManager.php:1226
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
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:96
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:1368
MediaWiki\Permissions\PermissionManager\$options
ServiceOptions $options
Definition: PermissionManager.php:58
Hooks
Hooks class.
Definition: Hooks.php:34
MediaWiki\Permissions\PermissionManager\overrideUserRightsForTesting
overrideUserRightsForTesting( $user, $rights=[])
Overrides user permissions cache.
Definition: PermissionManager.php:1612
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:302