add event to check access to admin plugins

This adds a new method that capsulates the access check that has to be
done to decide if an admin plugin's page should be shown to the user.
The default implementation is the same as before, relying only on the
forAdminOnly() method and the users' isadmin or ismanager status.

Admin plugins themselves can override the method to do additional
checks. In this patch, I added that to the usermanager plugin which will
only return true if the current auth backend can list users.

However the real idea behind this change is that the new method emits a
new event called ADMINPLUGIN_ACCESS_CHECK which would allow plugins to
overwrite it. This way it could be possible to give certain user groups
access to certain admin plugins without giving them admin or manager
permissions.

Note: this does not change how the "Admin" link is shown, it still
depends on ismanager or isadmin. A plugin as mentioned above would need
to influence the display via the MENU_ITEMS_ASSEMBLY event.

Note: this only covers the basic access check. Admin plugins may need
further adjustments for access to other parts of the plugin (like AJAX
components). An additional commit will update this for the bundled
plugins.
This commit is contained in:
Andreas Gohr 2018-10-30 14:07:15 +01:00
parent 433bb3d9e3
commit 64cdf7793c
5 changed files with 95 additions and 60 deletions

View File

@ -41,7 +41,7 @@ class Admin extends AbstractUserAction {
if(($page = $INPUT->str('page', '', true)) != '') {
/** @var $plugin \DokuWiki_Admin_Plugin */
if($plugin = plugin_getRequestAdminPlugin()) { // FIXME this method does also permission checking
if($plugin->forAdminOnly() && !$INFO['isadmin']) {
if(!$plugin->isAccessibleByCurrentUser()) {
throw new ActionException('denied');
}
$plugin->handle();

View File

@ -12,6 +12,9 @@ namespace dokuwiki\Ui;
*/
class Admin extends Ui {
protected $forAdmins = array('usermanager', 'acl', 'extension', 'config', 'styling');
protected $forManagers = array('revert', 'popularity');
/** @var array[] */
protected $menu;
/**
@ -24,58 +27,30 @@ class Admin extends Ui {
echo '<div class="ui-admin">';
echo p_locale_xhtml('admin');
$this->showSecurityCheck();
$this->showAdminMenu();
$this->showManagerMenu();
$this->showMenu('admin');
$this->showMenu('manager');
$this->showVersion();
$this->showPluginMenu();
$this->showMenu('other');
echo '</div>';
}
/**
* Display the standard admin tasks
* Show the given menu of available plugins
*
* @param string $type admin|manager|other
*/
protected function showAdminMenu() {
/** @var \DokuWiki_Auth_Plugin $auth */
global $auth;
global $INFO;
protected function showMenu($type) {
if (!$this->menu[$type]) return;
if(!$INFO['isadmin']) return;
// user manager only if the auth backend supports it
if(!$auth || !$auth->canDo('getUsers') ) {
if(isset($this->menu['usermanager'])) unset($this->menu['usermanager']);
if ($type === 'other') {
echo p_locale_xhtml('adminplugins');
$class = 'admin_plugins';
} else {
$class = 'admin_tasks';
}
echo '<ul class="admin_tasks">';
foreach(array('usermanager','acl', 'extension', 'config', 'styling') as $plugin) {
if(!isset($this->menu[$plugin])) continue;
$this->showMenuItem($this->menu[$plugin]);
unset($this->menu[$plugin]);
}
echo '</ul>';
}
/**
* Display the standard manager tasks
*/
protected function showManagerMenu() {
echo '<ul class="admin_tasks">';
foreach(array('revert','popularity') as $plugin) {
if(!isset($this->menu[$plugin])) continue;
$this->showMenuItem($this->menu[$plugin]);
unset($this->menu[$plugin]);
}
echo '</ul>';
}
/**
* Display all the remaining plugins
*/
protected function showPluginMenu() {
if(!count($this->menu)) return;
echo p_locale_xhtml('adminplugins');
echo '<ul class="admin_plugins">';
foreach ($this->menu as $item) {
echo "<ul class=\"$class\">";
foreach ($this->menu[$type] as $item) {
$this->showMenuItem($item);
}
echo '</ul>';
@ -104,7 +79,9 @@ class Admin extends Ui {
if(substr($conf['savedir'], 0, 2) !== './') return;
echo '<a style="border:none; float:right;"
href="http://www.dokuwiki.org/security#web_access_security">
<img src="' . DOKU_URL . $conf['savedir'] . '/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png" alt="Your data directory seems to be protected properly."
<img src="' . DOKU_URL . $conf['savedir'] .
'/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png"
alt="Your data directory seems to be protected properly."
onerror="this.parentNode.style.display=\'none\'" /></a>';
}
@ -136,19 +113,27 @@ class Admin extends Ui {
* @return array list of plugins with their properties
*/
protected function getPluginList() {
global $INFO;
global $conf;
$pluginlist = plugin_list('admin');
$menu = array();
$menu = ['admin' => [], 'manager' => [], 'other' => []];
foreach($pluginlist as $p) {
/** @var \DokuWiki_Admin_Plugin $obj */
if(($obj = plugin_load('admin', $p)) === null) continue;
if (($obj = plugin_load('admin', $p)) === null) continue;
// check permissions
if($obj->forAdminOnly() && !$INFO['isadmin']) continue;
if (!$obj->isAccessibleByCurrentUser()) continue;
$menu[$p] = array(
if (in_array($p, $this->forAdmins, true)) {
$type = 'admin';
} elseif (in_array($p, $this->forManagers, true)){
$type = 'manager';
} else {
$type = 'other';
}
$menu[$type][$p] = array(
'plugin' => $p,
'prompt' => $obj->getMenuText($conf['lang']),
'icon' => $obj->getMenuIcon(),
@ -157,17 +142,26 @@ class Admin extends Ui {
}
// sort by name, then sort
uasort(
$menu,
function ($a, $b) {
$strcmp = strcasecmp($a['prompt'], $b['prompt']);
if($strcmp != 0) return $strcmp;
if($a['sort'] == $b['sort']) return 0;
return ($a['sort'] < $b['sort']) ? -1 : 1;
}
);
uasort($menu['admin'], [$this, 'menuSort']);
uasort($menu['manager'], [$this, 'menuSort']);
uasort($menu['other'], [$this, 'menuSort']);
return $menu;
}
/**
* Custom sorting for admin menu
*
* We sort alphabetically first, then by sort value
*
* @param array $a
* @param array $b
* @return int
*/
protected function menuSort ($a, $b) {
$strcmp = strcasecmp($a['prompt'], $b['prompt']);
if($strcmp != 0) return $strcmp;
if($a['sort'] === $b['sort']) return 0;
return ($a['sort'] < $b['sort']) ? -1 : 1;
}
}

View File

@ -123,7 +123,7 @@ function plugin_getRequestAdminPlugin(){
/** @var $admin_plugin DokuWiki_Admin_Plugin */
$admin_plugin = plugin_load('admin', $page);
// verify
if ($admin_plugin && $admin_plugin->forAdminOnly() && !$INFO['isadmin']) {
if ($admin_plugin && !$admin_plugin->isAccessibleByCurrentUser()) {
$admin_plugin = null;
$INPUT->remove('page');
msg('For admins only',-1);

View File

@ -72,6 +72,29 @@ class DokuWiki_Admin_Plugin extends DokuWiki_Plugin {
trigger_error('html() not implemented in '.get_class($this), E_USER_WARNING);
}
/**
* Checks if access should be granted to this admin plugin
*
* @return bool true if the current user may access this admin plugin
*/
public function isAccessibleByCurrentUser() {
global $INFO;
$data['hasAccess'] = false;
$event = new Doku_Event('ADMINPLUGIN_ACCESS_CHECK', $data);
if($event->advise_before()) {
if ($this->forAdminOnly()) {
$data['hasAccess'] = $INFO['isadmin'];
} else {
$data['hasAccess'] = $INFO['ismanager'];
}
}
$event->advise_after();
return $data['hasAccess'];
}
/**
* Return true for access only by admins (config:superuser) or false if managers are allowed as well
*

View File

@ -297,6 +297,24 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin {
return true;
}
/**
* User Manager is only available if the auth backend supports it
*
* @inheritdoc
* @return bool
*/
public function isAccessibleByCurrentUser()
{
/** @var DokuWiki_Auth_Plugin $auth */
global $auth;
if(!$auth || !$auth->canDo('getUsers') ) {
return false;
}
return parent::isAccessibleByCurrentUser();
}
/**
* Display form to add or modify a user
*