diff --git a/extension.json b/extension.json index 421ca57..bccf6db 100644 --- a/extension.json +++ b/extension.json @@ -7,7 +7,7 @@ "license-name": "MIT", "@note": "Only list specific tested MediaWiki versions given how unstable the extension is", "requires": { - "MediaWiki": "1.39.15 || 1.43.5" + "MediaWiki": "1.39.17 || 1.43.5" }, "AutoloadNamespaces": { "MediaWiki\\Extension\\MixedVisibilityFiles\\": "src/" @@ -27,7 +27,9 @@ "visibility": { "class": "\\MediaWiki\\Extension\\MixedVisibilityFiles\\VisibilityHooks", "services": [ - "PageProps" + "PageProps", + "UserGroupManager", + "MainConfig" ] } }, @@ -36,5 +38,11 @@ "getUserPermissionsErrorsExpensive": "visibility", "MediaWikiServices": "services" }, + "config": { + "MixedVisibilityFilesPrefixRestrictions": { + "value": {}, + "description": "Map of file name prefixes to user group(s). Files whose name starts with a given prefix are restricted to users in the specified group(s). Each key is a prefix string and each value is either a group name (string) or an array of group names. Prefix matching is case-sensitive; note that MediaWiki normalizes the first character of file names to uppercase. Example: {\"Confidential_\": \"sysop\", \"Internal_\": [\"sysop\", \"bureaucrat\"]}" + } + }, "manifest_version": 2 } diff --git a/src/VisibilityHooks.php b/src/VisibilityHooks.php index 1da3d7d..9ae05d1 100644 --- a/src/VisibilityHooks.php +++ b/src/VisibilityHooks.php @@ -2,8 +2,10 @@ namespace MediaWiki\Extension\MixedVisibilityFiles; +use Config; use MediaWiki\Hook\GetDoubleUnderscoreIDsHook; use MediaWiki\Permissions\Hook\GetUserPermissionsErrorsExpensiveHook; +use MediaWiki\User\UserGroupManager; use PageProps; class VisibilityHooks implements @@ -13,8 +15,19 @@ class VisibilityHooks implements private PageProps $pageProps; - public function __construct( PageProps $pageProps ) { + private UserGroupManager $userGroupManager; + + /** @var array */ + private array $prefixRestrictions; + + public function __construct( + PageProps $pageProps, + UserGroupManager $userGroupManager, + Config $config + ) { $this->pageProps = $pageProps; + $this->userGroupManager = $userGroupManager; + $this->prefixRestrictions = $config->get( 'MixedVisibilityFilesPrefixRestrictions' ); } /** @@ -33,14 +46,36 @@ public function onGetUserPermissionsErrorsExpensive( $title, $user, $action, if ( MW_ENTRY_POINT !== 'img_auth' ) { return; } - // Only trying to affect file reads by anonymous users + // Only trying to affect file reads if ( $action !== 'read' ) { return; } - if ( $user->isRegistered() ) { + if ( $title->getNamespace() !== NS_FILE ) { return; } - if ( $title->getNamespace() !== NS_FILE ) { + + // Check prefix-based group restrictions (applies to all users) + $fileName = $title->getDBkey(); + foreach ( $this->prefixRestrictions as $prefix => $requiredGroups ) { + if ( str_starts_with( $fileName, $prefix ) ) { + // File matches a prefix restriction + if ( !$user->isRegistered() ) { + $result = false; + return false; + } + $userGroups = $this->userGroupManager->getUserEffectiveGroups( $user ); + $allowedGroups = (array)$requiredGroups; + if ( !array_intersect( $allowedGroups, $userGroups ) ) { + $result = false; + return false; + } + // User is in a required group, allow access + return; + } + } + + // Default behavior: block anonymous users unless file is marked public + if ( $user->isRegistered() ) { return; }