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