MarkAHershberger has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/398520 )
Change subject: Clean up formatting, bring code up to modernish standards ...................................................................... Clean up formatting, bring code up to modernish standards * Take functions out of the global context. * Newish array notation. * Documentation for every function. * Line length. * Consolidate checking of $wgNamespacePermissionLockdown. Change-Id: I319a09a2dad1178ea3470949e4928e9a665cc8c6 --- M Lockdown.php 1 file changed, 230 insertions(+), 170 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Lockdown refs/changes/20/398520/1 diff --git a/Lockdown.php b/Lockdown.php index 9ba1af0..1db1a2a 100644 --- a/Lockdown.php +++ b/Lockdown.php @@ -1,220 +1,280 @@ <?php /** - * Lockdown extension - implements restrictions on individual namespaces and special pages. + * Lockdown extension - implements restrictions on individual + * namespaces and special pages. * * @file * @ingroup Extensions * @author Daniel Kinzler, brightbyte.de - * @copyright © 2007 Daniel Kinzler + * @author Mark A. Hershberger, NicheWork, LLC + * @copyright © 2007, 2016 Daniel Kinzler + * @copyright © 2017 Mark A. Hershberger * @license GNU General Public Licence 2.0 or later */ -/* -* WARNING: you can use this extension to deny read access to some namespaces. Keep in mind that this -* may be circumvented in several ways. This extension doesn't try to -* plug such holes. Also note that pages that are not readable will still be shown in listings, -* such as the search page, categories, etc. -* -* Known ways to access "hidden" pages: -* - transcluding as template. can be avoided using $wgNonincludableNamespaces. -* Some search messages may reveal the page existance by producing links to it (MediaWiki:searchsubtitle, -* MediaWiki:noexactmatch, MediaWiki:searchmenu-exists, MediaWiki:searchmenu-new...). -* - supplying oldid=<revisionfromhiddenpage> may work in some versions of mediawiki. Same with diff, etc. -* -* NOTE: you cannot GRANT access to things forbidden by $wgGroupPermissions. You can only DENY access -* granted there. -*/ +/** + * WARNING: you can use this extension to deny read access to some + * namespaces. Keep in mind that this may be circumvented in several + * ways. This extension doesn't try to plug such holes. Also note that + * pages that are not readable will still be shown in listings, such as + * the search page, categories, etc. + * + * Known ways to access "hidden" pages: + * - transcluding as template. can be avoided using $wgNonincludableNamespaces. + * + * - Some search messages may reveal the page existance by producing + * links to it (MediaWiki:searchsubtitle, MediaWiki:noexactmatch, + * MediaWiki:searchmenu-exists, MediaWiki:searchmenu-new...). + * + * - supplying oldid=<revisionfromhiddenpage> may work in some + * versions of mediawiki. Same with diff, etc. + * + * NOTE: you cannot GRANT access to things forbidden by + * $wgGroupPermissions. You can only DENY access granted there. + */ if ( !defined( 'MEDIAWIKI' ) ) { - echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" ); + echo( "This file is an extension to the MediaWiki software and cannot " + . "be used standalone.\n" ); die( 1 ); } -$wgExtensionCredits['other'][] = array( +$wgExtensionCredits['other'][] = [ 'path' => __FILE__, 'name' => 'Lockdown', - 'author' => array( + 'author' => [ 'Daniel Kinzler', + 'Mark A. Hershberger', 'Platonides', '...' - ), + ], 'url' => 'https://mediawiki.org/wiki/Extension:Lockdown', 'descriptionmsg' => 'lockdown-desc', 'license-name' => 'GPL-2.0+' -); +]; $wgMessagesDirs['Lockdown'] = __DIR__ . '/i18n'; -$wgNamespacePermissionLockdown = array(); -$wgSpecialPageLockdown = array(); -$wgActionLockdown = array(); +$wgNamespacePermissionLockdown = []; +$wgSpecialPageLockdown = []; +$wgActionLockdown = []; -$wgHooks['getUserPermissionsErrors'][] = 'lockdownUserPermissionsErrors'; -$wgHooks['MediaWikiPerformAction'][] = 'lockdownMediawikiPerformAction'; -$wgHooks['SearchableNamespaces'][] = 'lockdownSearchableNamespaces'; -$wgHooks['SearchGetNearMatchComplete'][] = 'lockdownSearchGetNearMatchComplete'; +$wgHooks['getUserPermissionsErrors'][] = 'Lockdown::onGetUserPermissionsErrors'; +$wgHooks['MediaWikiPerformAction'][] = 'Lockdown::onMediawikiPerformAction'; +$wgHooks['SearchableNamespaces'][] = 'Lockdown::onSearchableNamespaces'; +$wgHooks['SearchGetNearMatchComplete'][] + = 'Lockdown::onSearchGetNearMatchComplete'; -/** - * @param Title $title - * @param User $user - * @param string $action - * @param MessageSpecifier|array|string|bool|null $result - * @return bool - */ -function lockdownUserPermissionsErrors( - Title $title, - User $user, - $action, - &$result = null -) { - global $wgNamespacePermissionLockdown, $wgSpecialPageLockdown, $wgWhitelistRead, $wgLang; +class Lockdown { - $result = null; + /** + * Fetch an appropriate permission error (or none!) + * + * @param Title $title being checked + * @param User $user whose access is being checked + * @param string $action being checked + * @param MessageSpecifier|array|string|bool|null &$result User + * permissions error to add. If none, return true. $result can be + * returned as a single error message key (string), or an array of + * error message keys when multiple messages are needed + * @return bool + * @see https://www.mediawiki.org/wiki/Manual:Hooks/getUserPermissionsErrors + */ + public static function onGetUserPermissionsErrors( + Title $title, User $user, $action, &$result = null + ) { + global $wgSpecialPageLockdown, $wgWhitelistRead, $wgLang; - // don't impose extra restrictions on UI pages - if ( $title->isCssJsSubpage() ) { - return true; - } + $result = null; - if ( $action == 'read' && is_array( $wgWhitelistRead ) ) { - // don't impose read restrictions on whitelisted pages - if ( in_array( $title->getPrefixedText(), $wgWhitelistRead ) ) { + // don't impose extra restrictions on UI pages + if ( $title->isCssJsSubpage() ) { return true; } - } - $groups = null; - $ns = $title->getNamespace(); - if ( NS_SPECIAL == $ns ) { - foreach ( $wgSpecialPageLockdown as $page => $g ) { - if ( !$title->isSpecial( $page ) ) continue; - $groups = $g; - break; + if ( $action == 'read' && is_array( $wgWhitelistRead ) ) { + // don't impose read restrictions on whitelisted pages + if ( in_array( $title->getPrefixedText(), $wgWhitelistRead ) ) { + return true; + } + } + + $groups = null; + $ns = $title->getNamespace(); + if ( NS_SPECIAL == $ns ) { + foreach ( $wgSpecialPageLockdown as $page => $g ) { + if ( !$title->isSpecial( $page ) ) { + continue; + } + $groups = $g; + break; + } + } else { + $groups = self::namespaceGroups( $ns, $action ); + } + + if ( $groups === null ) { + # no restrictions + return true; + } + + if ( !$groups ) { + # no groups allowed + $result = [ + 'badaccess-group0' + ]; + + return false; + } + + $ugroups = $user->getEffectiveGroups(); + + $match = array_intersect( $ugroups, $groups ); + + if ( $match ) { + # group is allowed - keep processing + $result = null; + return true; + } else { + # group is denied - abort + $groupLinks = array_map( [ 'User', 'makeGroupLinkWiki' ], $groups ); + + $result = [ + 'badaccess-groups', + $wgLang->commaList( $groupLinks ), + count( $groups ) + ]; + + return false; } } - else { - $groups = @$wgNamespacePermissionLockdown[$ns][$action]; + + /** + * See if the user is in an allowed group for this action + * + * @param OutputPage $output to use for output + * @param Article $article the article being acted on + * @param Title $title the title being acted on + * @param User $user who is trying this + * @param WebRequest $request to get any input + * @param MediaWiki $wiki for other info + * @return bool false if permission is denied + * @see https://www.mediawiki.org/wiki/Manual:Hooks/MediaWikiPerformAction + */ + function onMediawikiPerformAction( + OutputPage $output, Article $article, Title $title, User $user, + WebRequest $request, MediaWiki $wiki + ) { + global $wgActionLockdown, $wgLang; + + $action = $wiki->getAction(); + + if ( !isset( $wgActionLockdown[$action] ) ) { + return true; + } + + $groups = $wgActionLockdown[$action]; if ( $groups === null ) { - $groups = @$wgNamespacePermissionLockdown['*'][$action]; + return true; + } + if ( !$groups ) { + return false; + } + + $ugroups = $user->getEffectiveGroups(); + $match = array_intersect( $ugroups, $groups ); + + if ( $match ) { + return true; + } else { + $groupLinks = array_map( [ 'User', 'makeGroupLinkWiki' ], $groups ); + + $err = [ + 'badaccess-groups', $wgLang->commaList( $groupLinks ), + count( $groups ) + ]; + throw new PermissionsError( + $request->getVal( 'action' ), [ $err ] + ); + } + } + + /** + * Hide the namespaces that this user doesn't have permission to search + * + * @param array &$searchableNs from which namespaces will be removed + * @see https://www.mediawiki.org/wiki/Manual:Hooks/SearchableNamespaces + */ + public static function onSearchableNamespaces( array &$searchableNs ) { + $user = RequestContext::getMain()->getUser(); + $ugroups = $user->getEffectiveGroups(); + + foreach ( $searchableNs as $ns => $name ) { + if ( !self::namespaceCheck( $ns, $ugroups ) ) { + unset( $searchableNs[$ns] ); + } + } + } + + /** + * Get groups that this action is restricted to in this namespace. + * + * @param int $ns to check + * @param string $action to check (default: read) + * @return null|array of groups + */ + protected static function namespaceGroups( $ns, $action = 'read' ) { + global $wgNamespacePermissionLockdown; + + $groups = isset( $wgNamespacePermissionLockdown[$ns][$action] ) + ? $wgNamespacePermissionLockdown[$ns][$action] + : null; + if ( $groups === null ) { + $groups = isset( $wgNamespacePermissionLockdown['*'][$action] ) + ? $wgNamespacePermissionLockdown['*'][$action] + : null; } if ( $groups === null ) { - $groups = @$wgNamespacePermissionLockdown[$ns]['*']; + $groups = isset( $wgNamespacePermissionLockdown[$ns]['*'] ) + ? $wgNamespacePermissionLockdown[$ns]['*'] + : null; } + return $groups; } - if ( $groups === null ) { - #no restrictions - return true; - } - - if ( !$groups ) { - #no groups allowed - - $result = array( - 'badaccess-group0' - ); - - return false; - } - - $ugroups = $user->getEffectiveGroups(); - - $match = array_intersect( $ugroups, $groups ); - - if ( $match ) { - # group is allowed - keep processing - $result = null; - return true; - } else { - # group is denied - abort - $groupLinks = array_map( array( 'User', 'makeGroupLinkWiki' ), $groups ); - - $result = array( - 'badaccess-groups', - $wgLang->commaList( $groupLinks ), - count( $groups ) - ); - - return false; - } -} - -function lockdownMediawikiPerformAction ( - OutputPage $output, - Article $article, - Title $title, - User $user, - WebRequest $request, - MediaWiki $wiki -) { - global $wgActionLockdown, $wgLang; - - $action = $wiki->getAction(); - - if ( !isset( $wgActionLockdown[$action] ) ) { - return true; - } - - $groups = $wgActionLockdown[$action]; - if ( $groups === null ) { - return true; - } - if ( !$groups ) { - return false; - } - - $ugroups = $user->getEffectiveGroups(); - $match = array_intersect( $ugroups, $groups ); - - if ( $match ) { - return true; - } else { - $groupLinks = array_map( array( 'User', 'makeGroupLinkWiki' ), $groups ); - - $err = array( 'badaccess-groups', $wgLang->commaList( $groupLinks ), count( $groups ) ); - throw new PermissionsError( $request->getVal('action'), array( $err ) ); - } -} - -function lockdownSearchableNamespaces( array &$searchableNs ) { - $user = RequestContext::getMain()->getUser(); - $ugroups = $user->getEffectiveGroups(); - - foreach ( $searchableNs as $ns => $name ) { - if ( !lockdownNamespace( $ns, $ugroups ) ) { - unset( $searchableNs[$ns] ); + /** + * Determine if this the user has the group to read this namespace + * + * @param int $ns to check + * @param array $ugroups that the user is in + * @return bool false if the user does not have permission + */ + protected static function namespaceCheck( $ns, array $ugroups ) { + $groups = namespaceGroups( $ns ); + if ( $groups && !array_intersect( $ugroups, $groups ) ) { + return false; } - } - return true; -} -function lockdownNamespace( $ns, array $ugroups ) { - global $wgNamespacePermissionLockdown; - - $groups = @$wgNamespacePermissionLockdown[$ns]['read']; - if ( $groups === null ) { - $groups = @$wgNamespacePermissionLockdown['*']['read']; - } - if ( $groups === null ) { - $groups = @$wgNamespacePermissionLockdown[$ns]['*']; + return true; } - if ( $groups && !array_intersect($ugroups, $groups) ) { - return false; - } + /** + * Stop a "go" search for a hidden title to send you to the login + * required page. Will show a no such page message instead. + * + * @param string $searchterm that the user is searching + * @param Title $title that the user would end up on + * @see https://www.mediawiki.org/wiki/Manual:Hooks/SearchGetNearMatchComplete + */ + function onSearchGetNearMatchComplete( $searchterm, Title $title = null ) { + global $wgUser; - return true; -} - -#Stop a Go search for a hidden title to send you to the login required page. Will show a no such page message instead. -function lockdownSearchGetNearMatchComplete( $searchterm, Title $title = null ) { - global $wgUser; - - if ( $title ) { - $ugroups = $wgUser->getEffectiveGroups(); - if ( !lockdownNamespace( $title->getNamespace(), $ugroups ) ) { - $title = null; + if ( $title ) { + $ugroups = $wgUser->getEffectiveGroups(); + if ( !self::namespaceCheck( $title->getNamespace(), $ugroups ) ) { + $title = null; + } } } } -- To view, visit https://gerrit.wikimedia.org/r/398520 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I319a09a2dad1178ea3470949e4928e9a665cc8c6 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Lockdown Gerrit-Branch: master Gerrit-Owner: MarkAHershberger <m...@nichework.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits