User:Fcarpani/PageSecurity Modified Version
I have this version working on MediaWiki 1.9.1
<?php // PageSecurity MediaWiki extension. // Restricts access to pages according to security definitions. // Copyright (C) 2007, Benner Sistemas. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA $pageSecurityVersion = '1.1.2'; #---------------------------------------------------------------------------- # Extension initialization #---------------------------------------------------------------------------- global $wgExtensionCredits; $wgExtensionCredits['parserhook'][] = array( 'name'=>'PageSecurity', 'version'=>$pageSecurityVersion, 'author'=>'Fernando Correia modified by Fernando Carpani', 'url'=>'http://www.mediawiki.org/wiki/Extension:PageSecurity', 'description' => 'Restricts access to pages according to security definitions. Now Allows a user if he belongs to allowed group using a configuration variable.' ); $wgExtensionFunctions[] = "fnPageSecurityExtension"; $wgHooks['ArticleSave'][] = 'fnPageSecuritySaveHook'; $wgHooks['userCan'][] = 'fnPageSecurityUserCanHook'; $wgHooks['PermissionRequired'][] = 'fnPageSecurityPermissionRequiredHook'; $wgHooks['SkinFooterLinks'][] = 'fnPageSecuritySkinFooterLinksHook'; $wgHooks['BeforePageDisplay'][] = 'fnPageSecurityBeforePageDisplayHook'; // Registers the extension with the WikiText parser. function fnPageSecurityExtension() { global $wgParser; $wgParser->setHook("security-definition", "fnSecurityDefinitionTag"); } #---------------------------------------------------------------------------- # Event handlers #---------------------------------------------------------------------------- // Processes the <security-definition> tag. function fnSecurityDefinitionTag($input, $argv, &$parser) { if (!isset($argv["name"])) return SecurityDefinition::htmlError("Name parameter not informed for security-definition tag."); $name = $argv["name"]; $security_definition = new SecurityDefinition(); if (empty($parser->mRevisionId)) { // preview mode: show from memory $result = $security_definition->loadFromPreview($input, $argv); if (!empty($result)) return $result; } else { // normal mode: show effective settings from database $security_definition->mName = $name; $security_definition->loadFromDatabase(); } return $security_definition->getHtmlDescription(); } // Handles the save article event. // Has two functions: double-check write permission and save security definitions. function fnPageSecuritySaveHook(&$article, &$user, &$text, &$summary, &$minoredit, &$watchthis, &$sectionanchor, &$flags) { // Make sure the user can save the article. // This is not strictly required but it adds another layer of security. if (!SecurityManager::userCan($article->getTitle(), $user, "write")) { return "Access denied for saving this article."; } // Parse the security definition and save it to the database. $pattern = '@<security-definition(.*?)>(.*?)</security-definition>@is'; $offset = 0; while (preg_match($pattern, $text, $matches, PREG_OFFSET_CAPTURE, $offset)) { if (!$user->isAllowed('protect')) { return "Only sysops can save security definitions."; } $definition = $matches[0][0]; $offset = $matches[0][1] + strlen($definition); $security_definition = new SecurityDefinition(); $result = $security_definition->parseXml($definition); if (!empty($result)) return $result; $security_definition->saveToDatabase(); } return true; } // Handles the userCan event. // $result = false if the $user is not allowed to execute $action on $title. function fnPageSecurityUserCanHook($title, $user, $action, $result) { if (!SecurityManager::userCan($title, $user, $action)) $result = false; } // Handles the PermissionRequired event. // Redirects to the error page. function fnPageSecurityPermissionRequiredHook () { return SecurityManager::permissionRequired(); } // Handles the SkinFooterLinks event. // Adds a security definition notice to the page footer. function fnPageSecuritySkinFooterLinksHook ($template, $footerlinks) { SecurityManager::addTemplateNotice($template, $footerlinks); } // Handles the BeforePageDisplay event. // Changes the page logo according to the page security definition. function fnPageSecurityBeforePageDisplayHook(&$out) { SecurityManager::setLogo(); } #---------------------------------------------------------------------------- # SecurityManager class #---------------------------------------------------------------------------- // Executes page security operations. class SecurityManager { static $userCan = array(); // Used for cache and loop control static $securityDefinitions = array(); // Security definition cache static $readPermissionError = false; // true if a read permission error ocurred // Returns true if the action is allowed on the page; false if it is not allowed. private static function actionAllowed($title, $user, $action, $page_security_definition) { global $wgPageSecurityAllowGroupAccept; // check parameters if (empty($page_security_definition)) return true; // no security definition if (empty($action)) return true; // no action if (!in_array($action, array('read', 'write'))) return true; // unknown actions won't be checked // add meta tags to output $meta_tag_name = "Page security definition (" . $title->getPrefixedDBkey() . ")"; SecurityManager::addMetaTag($meta_tag_name, $page_security_definition->mName); // get groups the user belongs to $user_groups = $user->getEffectiveGroups(); // check for sysop access global $wgPageSecurityAllowSysop; if (!empty($wgPageSecurityAllowSysop)) { // Are sysops always allowed? if (in_array("sysop", $user_groups)) { return true; // sysop access override granted } } // *** Can check allow first and deny last.... dirty but efective way. if (empty($wgPageSecurityAllowGroupAccept)||(!$wgPageSecurityAllowGroupAccept)){ wfDebug("====>Chequeando primero los grupos PROHIBIDOS\n"); // check denied user groups if (!empty($page_security_definition->mPermissions[$action]["deny"])) { $denied_groups = $page_security_definition->mPermissions[$action]["deny"]; } if (!empty($denied_groups)) { foreach ($denied_groups as $group) { if (in_array($group, $user_groups)) { return false; // user is in denied group } } } // check allowed user groups if (!empty($page_security_definition->mPermissions[$action]["allow"])) { $allowed_groups = $page_security_definition->mPermissions[$action]["allow"]; } if (!empty($allowed_groups)) { foreach ($allowed_groups as $group) { if (in_array($group, $user_groups)) { return true; // user is in allowed group } } } } else { # wfDebug("====>Chequeando primero los grupos permitidos PERMITIDOS\n"); // check allowed user groups if (!empty($page_security_definition->mPermissions[$action]["allow"])) { $allowed_groups = $page_security_definition->mPermissions[$action]["allow"]; } if (!empty($allowed_groups)) { foreach ($allowed_groups as $group) { # wfDebug(sprintf("=======>Grupo %s Permitido\n",$group)); if (in_array($group, $user_groups)) { return true; // user is in allowed group } } } // check denied user groups if (!empty($page_security_definition->mPermissions[$action]["deny"])) { $denied_groups = $page_security_definition->mPermissions[$action]["deny"]; } if (!empty($denied_groups)) { # wfDebug(sprintf("=======>Grupo %s Prohibido\n",$group)); foreach ($denied_groups as $group) { if (in_array($group, $user_groups)) { return false; // user is in denied group } } } } // access not granted return false; } // Adds a new meta tag to the output HTML page. // Will not add duplicate tags. private static function addMetaTag($name, $value) { global $wgOut; if (!empty($wgOut->mMetatags)) { foreach ($wgOut->mMetatags as $tag) { if ($tag[0] == $name) { return; // already exists } } } $wgOut->addMeta($name, $value); } // Adds a security definition notice to the template. function addTemplateNotice(&$template, &$footerlinks) { if (empty($template->data["titleprefixeddbkey"])) return; $titleprefixeddbkey = $template->data["titleprefixeddbkey"]; if (empty(SecurityManager::$securityDefinitions[$titleprefixeddbkey])) return; $securityDefinition = SecurityManager::$securityDefinitions[$titleprefixeddbkey]; if (empty($securityDefinition->mNotice)) return; $notice = $securityDefinition->mNotice; $footerlinks[] = "SecurityDefinitionNotice"; $template->data["SecurityDefinitionNotice"] = $notice; } // Executes the work for the UserCan function. private static function doUserCan($title, $user, $action) { // get page data $content = SecurityManager::getRevisionText($title); if (empty($content)) return true; // nothing to check // check permissions if (empty(SecurityManager::$securityDefinitions[$title->getPrefixedDBkey()])) { SecurityManager::$securityDefinitions[$title->getPrefixedDBkey()] = SecurityManager::getPageSecurityDefinition($title, $content); } $page_security_definition = SecurityManager::$securityDefinitions[$title->getPrefixedDBkey()]; if (!empty($page_security_definition)) { // there is a security definition for this page if (!SecurityManager::actionAllowed($title, $user, $action, $page_security_definition)) { if ($action == "read") { SecurityManager::$readPermissionError = true; } return false; } } // protect transclusion global $wgPageSecurityOptionalTransclusions; if ($action == "read" and !$wgPageSecurityOptionalTransclusions) { if (SecurityManager::transclusionNotAllowed($user, $content)) { return false; } } // access granted return true; } // Gets the security definition that applies to the page. // Only the first matching configuration will be used. private static function getPageSecurityDefinition($title, $content) { // search for a matching security definition global $wgPageSecurity; if (empty($title)) return; // no title if (empty($wgPageSecurity)) return; // no security definition foreach ($wgPageSecurity as $expression) { if (is_array($expression)) { // pattern definition // get parameters if (count($expression) != 3) continue; // ignore invalid expression $security_definition_name = $expression[0]; $protection_subject = $expression[1]; $protection_pattern = utf8_encode($expression[2]); // get pattern subject text if (strcasecmp($protection_subject, "title") == 0) $subject = $title->getPrefixedText(); else if (strcasecmp($protection_subject, "content") == 0) { if (empty($content)) continue; // no subject $subject = $content; } else continue; // ignore invalid expression // test pattern in page if (preg_match("&$protection_pattern&is", $subject, $matches, PREG_OFFSET_CAPTURE)) { $page_security_definition_name = $security_definition_name; break; } } else { // a default security definition was specified $default_definition = $expression; } } // use default security definition if needed if (empty($page_security_definition_name)) { // no expression matches this page if (!empty($default_definition)) { $page_security_definition_name = $default_definition; // use default security definition } } // get security definition object if (empty($page_security_definition_name)) return; $page_security_definition = new SecurityDefinition(); $page_security_definition->mName = $page_security_definition_name; if (!$page_security_definition->loadFromDatabase()) return; return $page_security_definition; } // Returns the text of the current revision of the title. private static function getRevisionText($title) { if (empty($title)) return; $revision = Revision::newFromTitle($title); if (empty($revision)) return; $content = $revision->getText(); if (empty($content)) return; return $content; } // Redirects to the error page specified by the user. // The custom error message will be displayed if both these conditions are true: // - The access restriction was generated by the PageSecurity extension. // - A custom error page was specified. // Otherwise, the default error message will be displayed. static function permissionRequired() { global $wgOut; global $wgPageSecurityErrorPage; if (!SecurityManager::$readPermissionError) return; if (empty($wgPageSecurityErrorPage)) return; $title = Title::newFromText($wgPageSecurityErrorPage); $redirectURL = $title->getFullURL(); $wgOut->redirect($redirectURL); return false; // instruct caller to skip processing } // Sets the logo to the one specified in the page security definition, if there is one. // If no logo was specified, nothing is changed. static function setLogo() { global $wgTitle; if (empty($wgTitle)) return; $db_key = $wgTitle->getPrefixedDBkey(); if (empty($db_key)) return; if (empty(SecurityManager::$securityDefinitions)) return; $page_security_definition = SecurityManager::$securityDefinitions[$db_key]; if (empty($page_security_definition)) return; $logo = $page_security_definition->mLogo; if (empty($logo)) return; global $wgLogo; $wgLogo = $logo; } // Returns true if the page includes a protected page that the user is not allowed to access. private static function transclusionNotAllowed($user, $content) { $pattern = '@{{(.+?)(\|.*?)?}}@is'; $offset = 0; while (preg_match($pattern, $content, $matches, PREG_OFFSET_CAPTURE, $offset)) { $offset = $matches[0][1] + 1; // restart search at the second character to prevent attacks $transclusion_text = trim($matches[1][0]); $transclusion_link = $transclusion_text; $transclusion_title = Title::newFromText($transclusion_link, NS_TEMPLATE); if (!empty($transclusion_title)) { if (!SecurityManager::UserCan($transclusion_title, $user, "read")) return true; // transclusion not allowed } } return false; // no forbidden transclusion } // Checkes whether $user can execute $action on $title. // Returns true if the user can do the action; false if the action is not allowed. // Safe to use even in case of recursion or circular reference. static function userCan($title, $user, $action) { $title_key = $title->getPrefixedDBkey(); if (in_array($action, array('create', 'edit', 'move'))) $action = 'write'; // write permission allows several actions if (isset(SecurityManager::$userCan[$title_key][$action])) { // either already processed or processing $stored_result = SecurityManager::$userCan[$title_key][$action]; if ($stored_result === "processing") return true; // prevent infinite loop return $stored_result; } SecurityManager::$userCan[$title_key][$action] = "processing"; $result = SecurityManager::doUserCan($title, $user, $action); SecurityManager::$userCan[$title_key][$action] = $result; // store result to avoid checking the same permission again return $result; } } #---------------------------------------------------------------------------- # SecurityDefinition class #---------------------------------------------------------------------------- // A security definition. class SecurityDefinition { var $mId; var $mName; var $mBaseId; var $mBaseName; var $mNotice; var $mLogo; var $mSecurityDefinitionItems; // array of SecurityDefinitionItem var $mPermissions; // array of action, permission, group function SecurityDefinition() { // initialize members to avoid uninitialized variable errors $this->mId = 0; $this->mName = ""; $this->mBaseId = 0; $this->mBaseName = ""; $this->mNotice = ""; $this->mLogo = ""; } // Adds an item to the security definition. // Prevents duplicated items. function addItem($item) { if (!$this->duplicatedItem($item)) $this->mSecurityDefinitionItems[] = $item; } // Creates the permissions collection from the items array. function createPermissionsArray() { unset($this->mPermissions); if (empty($this->mSecurityDefinitionItems)) return; foreach ($this->mSecurityDefinitionItems as $item) { $this->mPermissions[$item->mAction][$item->mPermission][] = $item->mGroup; } } // Returns true if an item with the same permission is already in the security definition; false if it is not. function duplicatedItem($item) { if (empty($this->mSecurityDefinitionItems)) return false; foreach ($this->mSecurityDefinitionItems as $existingItem) { if ($existingItem->mAction == $item->mAction and $existingItem->mPermission == $item->mPermission and $existingItem->mGroup == $item->mGroup) return true; } return false; } // Finds an Id that corresponds to a name. static function findId($name) { $securityDefinition = new SecurityDefinition(); $securityDefinition->mName = $name; if ($securityDefinition->loadFromDatabase()) return $securityDefinition->mId; else return 0; } // Returns an HTML description of the security definition. function getHtmlDescription() { if (empty($this->mName)) return SecurityDefinition::htmlError("The security definition was not initialized."); $name = $this->mName; if (empty($this->mBaseName)) $inheritance = ""; else $inheritance = " (inherits from " . $this->mBaseName. ")"; $description = "<p><b>Security definition $name$inheritance:</b></p>\n"; if (!empty($this->mNotice)) { $description .= "<p><i>{$this->mNotice}</i></p>\n"; } if (!empty($this->mLogo)) { $description .= "<p>Logo: {$this->mLogo}</p>\n"; } if (empty($this->mPermissions)) $description .= "<p>(empty)</p>\n"; else { $description .= "<ul>\n"; foreach ($this->mPermissions as $action => $actions) { $allow = ""; if (!empty($actions["allow"])) { foreach ($actions["allow"] as $group) { if ($group == "*") $group = "all"; if (!empty($allow)) $allow .= ", "; $allow .= $group; } } $except = ""; if (!empty($actions["deny"])) { foreach ($actions["deny"] as $group) { if ($group == "*") $group = "all"; if (!empty($except)) $except .= ", "; $except .= $group; } } $description .= " <li>"; $description .= "<b>$action</b>: "; if (empty($allow)) $description .= "deny all"; else { $description .= "allow $allow"; if (!empty($except)) $description .= " except $except"; } $description .= ".</li>\n"; } $description .= "</ul>\n"; } return $description; } // Returns an error message formatted as HTML. static function htmlError($input) { return "<p><b>Security definition error: $input</b></p>\n"; } // Loads items from a base security definition. // Only the base id or the base name must be informed. function loadFromBase($baseId = 0, $baseName = "") { if (empty($baseId) and empty($baseName)) return SecurityDefinition::htmlError("The base security definition was not specified."); $baseSecurityDefinition = new SecurityDefinition(); $baseSecurityDefinition->mId = $baseId; $baseSecurityDefinition->mName = $baseName; if (!$baseSecurityDefinition->loadFromDatabase()) return SecurityDefinition::htmlError("Base security definition is empty."); $this->mBaseId = $baseSecurityDefinition->mId; $this->mBaseName = $baseSecurityDefinition->mName; foreach ($baseSecurityDefinition->mSecurityDefinitionItems as $item) { $this->addItem($item); } } // Loads the SecurityDefinitionItems from the database. // Uses either $mId or $mName as keys. // Returns true if the record was loaded; false if not found. function loadFromDatabase() { // load security definition if (!empty($this->mId)) $key = array('security_definition_id' => $this->mId); else if (!empty($this->mName)) $key = array('security_definition_name' => $this->mName); else return false; // no key unset($this->mSecurityDefinitionItems); // must unset before possibly loading base security definition $dbr =& wfGetDB(DB_SLAVE); $res = $dbr->select( 'security_definitions', array( 'security_definition_id', 'security_definition_name', 'base_security_definition_id', 'security_definition_notice', 'security_definition_logo', ), $key, __METHOD__ ); if ($record = $dbr->fetchObject($res)) { $this->mId = intval($record->security_definition_id); $this->mName = trim($record->security_definition_name); $this->mBaseId = intval($record->base_security_definition_id); $this->mNotice = trim($record->security_definition_notice); $this->mLogo = trim($record->security_definition_logo); if (!empty($this->mBaseId)) { $this->loadFromBase($this->mBaseId, ""); } } else { return false; } // load items $dbr =& wfGetDB(DB_SLAVE); $res = $dbr->select( 'security_definition_items', array( 'security_definition_item_id', 'security_definition_id', 'security_definition_item_action', 'security_definition_item_permission', 'security_definition_item_group', ), array('security_definition_id' => $this->mId), __METHOD__, array('ORDER BY' => 'security_definition_item_id') ); while ($record = $dbr->fetchObject($res)) { $item = new SecurityDefinitionItem(); $item->mId = $record->security_definition_item_id; $item->mSecurityDefinitionId = $record->security_definition_id; $item->mAction = trim($record->security_definition_item_action); $item->mPermission = trim($record->security_definition_item_permission); $item->mGroup = trim($record->security_definition_item_group); $this->addItem($item); } $this->createPermissionsArray(); return true; } // Loads from information available during an edit preview. function loadFromPreview($input, $argv) { if (isset($argv["name"])) $name_parameter = 'name="' . $argv["name"] . '"'; else $name_parameter = ""; if (isset($argv["notice"])) $notice_parameter = 'notice="' . $argv["notice"] . '"'; else $notice_parameter = ""; if (isset($argv["logo"])) $logo_parameter = 'logo="' . $argv["logo"] . '"'; else $logo_parameter = ""; if (isset($argv["base"])) $base_parameter = 'base="' . $argv["base"] . '"'; else $base_parameter = ""; $tag = "<security_definition $name_parameter $notice_parameter $logo_parameter $base_parameter>$input</security_definition>"; return $this->parseXml($tag); } // Creates item definitions from a XML structure. function parseXml($tag) { $xmlParser = xml_parser_create(); xml_parse_into_struct($xmlParser, $tag, $vals, $index); xml_parser_free($xmlParser); if (empty($vals[0])) return SecurityDefinition::htmlError("The security definition could not be parsed."); $open_tag = $vals[0]; if (empty($open_tag["attributes"])) return SecurityDefinition::htmlError("No attribute was specified in security definition."); $attributes = $open_tag["attributes"]; if (empty($attributes["NAME"])) return SecurityDefinition::htmlError("Name was not specified in security definition."); $name = $attributes["NAME"]; $this->mId = SecurityDefinition::findId($name); $this->mName = $name; if (!empty($attributes["NOTICE"])) { $this->mNotice = $attributes["NOTICE"]; } if (!empty($attributes["LOGO"])) { $this->mLogo = $attributes["LOGO"]; } unset($this->mSecurityDefinitionItems); // must unset before possibly loading from base if (!empty($attributes["BASE"])) $baseName = $attributes["BASE"]; if (!empty($baseName)) { $result = $this->loadFromBase(0, $baseName); if (!empty($result)) return $result; } foreach ($vals as $value) { $permission = trim(strtolower($value["tag"])); if ($permission == "allow" || $permission == "deny") { if ($value["type"] != "complete") continue; if (empty($value["attributes"])) return SecurityDefinition::htmlError("No attribute in $permission tag of then security definition."); $attributes = $value["attributes"]; if (empty($attributes["ACTION"])) return SecurityDefinition::htmlError("No action in $permission tag of then security definition."); $action = trim(strtolower($attributes["ACTION"])); if (!in_array($action, array('read', 'write'))) return SecurityDefinition::htmlError("Action $action is not recognized. Use 'read' or 'write'."); if (empty($value["value"])) return SecurityDefinition::htmlError("No group in $permission tag of then security definition."); $groups = split(",", $value["value"]); if (empty($groups)) return SecurityDefinition::htmlError("No group in $permission tag of then security definition."); foreach ($groups as $group) { $group = trim($group); if (strcasecmp($group, "all") == 0) $group = "*"; // substitute "*" for "all" $item = new SecurityDefinitionItem(); $item->mSecurityDefinitionId = $this->mId; $item->mAction = $action; $item->mPermission = $permission; $item->mGroup = $group; $this->addItem($item); } } } $this->createPermissionsArray(); } // Saves the SecurityDefinitionItems to the database. function saveToDatabase() { // delete previous security definition items if (!empty($this->mId)) { $dbw =& wfGetDB( DB_MASTER ); $dbw->delete('security_definition_items', array('security_definition_id' => $this->mId), __METHOD__ ); } // save security definition $dbw =& wfGetDB( DB_MASTER ); if (empty($this->mId)) { // insert $seqVal = $dbw->nextSequenceValue('security_definition_id_seq'); $dbw->insert('security_definitions', array( 'security_definition_id' => $seqVal, 'security_definition_name' => trim($this->mName), 'base_security_definition_id' => $this->mBaseId, 'security_definition_notice' => trim($this->mNotice), 'security_definition_logo' => trim($this->mLogo), ), __METHOD__ ); $this->mId = $dbw->insertId(); } else { // update $dbw->update('security_definitions', array( 'security_definition_name' => trim($this->mName), 'base_security_definition_id' => $this->mBaseId, 'security_definition_notice' => trim($this->mNotice), 'security_definition_logo' => trim($this->mLogo), ), array('security_definition_id' => $this->mId), __METHOD__ ); } // save security definition items if (empty($this->mSecurityDefinitionItems)) return; foreach ($this->mSecurityDefinitionItems as $item) { if (empty($item->mSecurityDefinitionId)) $item->mSecurityDefinitionId = $this->mId; // associate with security definition record ID if ($item->mSecurityDefinitionId == $this->mId) { // do not save items inherited from base security $item->addToDatabase(); } } } } #---------------------------------------------------------------------------- # SecurityDefinitionItem class #---------------------------------------------------------------------------- // An item of a security definition. class SecurityDefinitionItem { var $mId; var $mSecurityDefinitionId; var $mAction; var $mPermission; var $mGroup; function SecurityDefinitionItem() { } // Inserts the record into the database. function addToDatabase() { $dbw =& wfGetDB( DB_MASTER ); $seqVal = $dbw->nextSequenceValue('security_definition_item_id_seq'); $dbw->insert('security_definition_items', array( 'security_definition_item_id' => $seqVal, 'security_definition_id' => $this->mSecurityDefinitionId, 'security_definition_item_action' => $this->mAction, 'security_definition_item_permission' => $this->mPermission, 'security_definition_item_group' => $this->mGroup, ), __METHOD__ ); $this->mId = $dbw->insertId(); } } ?>