egroupware_official/admin/inc/class.admin_acl.inc.php

511 lines
15 KiB
PHP

<?php
/**
* EGroupware: Admin ACL
*
* @link http://www.egroupware.org
* @author Ralf Becker <rb@stylite.de>
* @package admin
* @copyright (c) 2013-16 by Ralf Becker <rb@stylite.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
use EGroupware\Api;
use EGroupware\Api\Framework;
use EGroupware\Api\Acl;
use EGroupware\Api\Etemplate;
/**
* UI for admin ACL
*
* Will also be extended by preferences_acl for user ACL
*/
class admin_acl
{
/**
* Methods callable via menuaction
* @var array
*/
public $public_functions = array(
'index' => true,
);
/**
* Reference to global Acl class (instanciated for current user)
*
* @var Acl
*/
protected $acl;
/**
* Constructor
*/
public function __construct()
{
$this->acl = $GLOBALS['egw']->acl;
}
/**
* Save run rights and refresh opener
*
* @param array $content
*/
protected function save_run_rights(array $content)
{
$old_apps = array_keys($this->acl->get_user_applications($content['acl_account'], false, false));
// add new apps
$added_apps = array_diff($content['apps'], $old_apps);
foreach($added_apps as $app)
{
$this->acl->add_repository($app, 'run', $content['acl_account'], 1);
}
// remove no longer checked apps
$removed_apps = array_diff($old_apps, $content['apps']);
$deleted_ids = array();
foreach($removed_apps as $app)
{
$this->acl->delete_repository($app, 'run', $content['acl_account']);
$deleted_ids[] = $app.':'.$content['acl_account'].':run';
}
//error_log(__METHOD__."() apps=".array2string($content['apps']).", old_apps=".array2string($old_apps).", added_apps=".array2string($added_apps).", removed_apps=".array2string($removed_apps));
if (!$added_apps && !$removed_apps)
{
// nothing changed --> nothing to do/notify
}
elseif (!$old_apps)
{
Framework::refresh_opener(lang('ACL added.'), 'admin', null, 'add');
}
elseif (!$added_apps)
{
Framework::refresh_opener(lang('ACL deleted.'), 'admin', $deleted_ids, 'delete');
}
else
{
Framework::refresh_opener(lang('ACL updated.'), 'admin', null, 'edit');
}
}
/**
* Save rights and refresh opener
*
* @param array $content
*/
protected function save_rights(array $content)
{
// assamble rights again
$rights = (int)$content['preserve_rights'];
foreach($content['acl'] as $right)
{
$rights |= $right;
}
$id = !empty($content['id']) ? $content['id'] :
$content['acl_appname'].':'.$content['acl_account'].':'.$content['acl_location'];
//error_log(__METHOD__."() id=$id, acl=".array2string($content['acl'])." --> rights=$rights");
if ($this->acl->get_specific_rights_for_account($content['acl_account'], $content['acl_location'], $content['acl_appname']) == $rights)
{
// nothing changed --> nothing to do
}
elseif (!$rights) // all rights removed --> delete it
{
$this->acl->delete_repository($content['acl_appname'], $content['acl_location'], $content['acl_account']);
Framework::refresh_opener(lang('ACL deleted.'), 'admin', $id, 'delete');
}
else
{
$this->acl->add_repository($content['acl_appname'], $content['acl_location'], $content['acl_account'], $rights);
if ($content['id'])
{
Framework::refresh_opener(lang('ACL updated.'), 'admin', $id, 'edit');
}
else
{
Framework::refresh_opener(lang('ACL added.'), 'admin', $id, 'add');
}
}
}
/**
* Callback for nextmatch to fetch Acl
*
* @param array $query
* @param array &$rows=null
* @return int total number of rows available
*/
public static function get_rows(array $query, array &$rows=null)
{
$so_sql = new Api\Storage\Base('phpgwapi', Acl::TABLE, null, '', true);
// client queries destinct rows by their row-id
if (isset($query['col_filter']['id']))
{
$query['col_filter'][] = $GLOBALS['egw']->db->concat('acl_appname',"':'",'acl_account',"':'",'acl_location').
' IN ('.implode(',', array_map(array($GLOBALS['egw']->db, 'quote'), (array)$query['col_filter']['id'])).')';
unset($query['col_filter']['id']);
}
else
{
$memberships = $GLOBALS['egw']->accounts->memberships($query['account_id'], true);
$memberships[] = $query['account_id'];
Api\Cache::setSession(__CLASS__, 'state', array(
'account_id' => $query['account_id'],
'filter' => $query['filter'],
'acl_appname' => $query['filter2'],
));
if ($GLOBALS['egw_info']['user']['preferences']['admin']['acl_filter'] != $query['filter'])
{
$GLOBALS['egw']->preferences->add('admin', 'acl_filter', $query['filter']);
$GLOBALS['egw']->preferences->save_repository(false,'user',false);
}
switch($query['filter'])
{
case 'run':
$query['col_filter']['acl_location'] = 'run';
$query['col_filter']['acl_account'] = $memberships;
break;
default:
case 'other':
//$query['col_filter'][] = "acl_location!='run'";
// remove everything not an account-id in location, like category based acl
if (substr($GLOBALS['egw']->db->Type, 0, 5) == 'mysql')
{
$query['col_filter'][] = "acl_location REGEXP '^-?[0-9]+$'";
}
else
{
$query['col_filter'][] = "acl_location SIMILAR TO '-?[0-9]+'";
}
// get apps not using group-acl (eg. Addressbook) or using it only partialy (eg. InfoLog)
$not_enum_group_acls = Api\Hooks::process('not_enum_group_acls', array(), true);
//error_log(__METHOD__."(filter=$query[filter]) not_enum_group_acl=".array2string($not_enum_group_acls));
if ($not_enum_group_acls)
{
$sql = '(CASE acl_appname';
foreach($not_enum_group_acls as $app => $groups)
{
if ($groups === true)
{
$check = $query['account_id'];
}
else
{
$check = array_diff($memberships, (array)$groups);
//error_log(__METHOD__."() app=$app, array_diff(memberships=".array2string($memberships).", groups=".array2string($groups).")=".array2string($check));
if (!$check) continue; // would give sql error otherwise!
}
$sql .= ' WHEN '.$GLOBALS['egw']->db->quote($app).' THEN '.$GLOBALS['egw']->db->expression(Acl::TABLE, array(
'acl_account' => $check,
));
}
$sql .= ' ELSE ';
}
$sql .= $GLOBALS['egw']->db->expression(Acl::TABLE, array(
'acl_account' => $memberships,
));
if ($not_enum_group_acls) $sql .= ' END)';
$query['col_filter'][] = $sql;
break;
case 'own':
$query['col_filter']['acl_location'] = $memberships;
break;
}
// do NOT list group-memberships and other non-ACL stuff here
$query['col_filter']['acl_appname'] = $query['filter2'];
if (empty($query['col_filter']['acl_appname']) && $query['filter'] != 'run')
{
//$query['col_filter'][] = "NOT acl_appname IN ('phpgw_group','sqlfs')";
$query['col_filter']['acl_appname'] = array_keys($query['acl_rights']);
}
}
$readonlys = array();
$total = $so_sql->get_rows($query, $rows, $readonlys);
foreach($rows as &$row)
{
// generate a row-id
$row['id'] = $row['acl_appname'].':'.$row['acl_account'].':'.$row['acl_location'];
if ($row['acl_location'] == 'run')
{
$row['acl1'] = lang('run');
}
else
{
if ($app !== $row['acl_appname']) Api\Translation::add_app($row['app_name']);
foreach($query['acl_rights'][$row['acl_appname']] as $val => $label)
{
if ($row['acl_rights'] & $val)
{
$row['acl'.$val] = lang($label);
}
}
}
if (!self::check_access($row['acl_account'], $row['acl_location'], false)) // false: do NOT throw an exception!
{
$row['class'] = 'rowNoEdit';
}
//error_log(__METHOD__."() $n: ".array2string($row));
}
//error_log(__METHOD__."(".array2string($query).") returning ".$total);
// Get supporting or all apps for filter2 depending on filter
if($query['filter'] == 'run')
{
$rows['sel_options']['acl_appname'] = $rows['sel_options']['filter2'] = array(
'' => lang('All applications'),
)+Etemplate\Widget\Select::app_options('enabled');
}
else
{
$rows['sel_options']['filter2'] = array(
array('value' => '', 'label' => lang('All applications'))
);
$apps = Api\Hooks::process(array(
'location' => 'acl_rights',
'owner' => $query['account_id'],
), array(), true);
foreach(array_keys($apps) as $appname)
{
$rows['sel_options']['filter2'][] = array(
'value' => $appname,
'label' => lang(Api\Link::get_registry($appname, 'entries') ?: $appname)
);
}
usort($rows['sel_options']['filter2'], function($a,$b) {
return strcasecmp($a['label'], $b['label']);
});
$rows['sel_options']['acl_appname'] = $rows['sel_options']['filter2'];
}
return $total;
}
/**
* Check if current user has access to ACL setting of a given location
*
* @param int $account_id numeric account-id
* @param int|string $location =null numeric account-id or "run"
* @param boolean $throw =true if true, throw an exception if no access, instead of just returning false
* @return boolean true if access is granted, false if notification_bo
* @throws Api\Exception\NoPermission
*/
public static function check_access($account_id, $location=null, $throw=true)
{
static $admin_access=null;
static $own_access=null;
if (is_null($admin_access))
{
$admin_access = isset($GLOBALS['egw_info']['user']['apps']['admin']) &&
!$GLOBALS['egw']->acl->check('account_access', 64, 'admin'); // ! because this denies access!
$own_access = $admin_access || isset($GLOBALS['egw_info']['user']['apps']['preferences']);
}
if (!(int)$account_id || !((int)$account_id == (int)$GLOBALS['egw_info']['user']['account_id'] && $location !== 'run' ?
$own_access : $admin_access))
{
if ($throw) throw new Api\Exception\NoPermission(lang('Permission denied!!!'));
return false;
}
return true;
}
/**
* Get the list of applications allowed for the given user
*
* The list of applications is added to the json response
*
* @param int $account_id
*/
public static function ajax_get_app_list($account_id)
{
$list = array();
if(self::check_access((int)$account_id,'run',false))
{
$list = array_keys($GLOBALS['egw']->acl->get_user_applications((int)$account_id,false,false));
}
Api\Json\Response::get()->data($list);
}
/**
* Change (add, modify, delete) an ACL entry
*
* Checks access and throws an exception, if a change is attempted without proper access
*
* @param string|array $ids "$app:$account:$location" string used as row-id in list
* @param int $rights null to delete, or new rights
* @param array $values Additional values from UI
* @param string $etemplate_exec_id to check against CSRF
* @throws Api\Exception\NoPermission
*/
public static function ajax_change_acl($ids, $rights, $values, $etemplate_exec_id)
{
Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args());
try {
foreach((array)$ids as $id)
{
list($app, $account_id, $location) = explode(':', $id, 3);
self::check_access($account_id, $location); // throws exception, if no rights
$acl = $GLOBALS['egw']->acl;
if($location == 'run')
{
$right_list = array(1 => 'run');
}
else
{
$right_list = Api\Hooks::single(array(
'location' => 'acl_rights',
'owner' => $location,
), $app);
}
$remove = false;
$current = (int)$acl->get_specific_rights_for_account($account_id,$location,$app);
if($rights < 0)
{
$rights = $current & ~abs($rights);
}
foreach(array_keys((array)$right_list) as $right)
{
$have_it = !!($current & $right);
$set_it = !!($rights & $right);
if($have_it == $set_it) continue;
$data = array(
'allow' => $set_it,
'account' => $account_id,
'app' => $app,
'location' => $location,
'rights' => (int)$right
// This is the documentation from policy app
)+(array)$values['admin_cmd'];
if($location == 'run')
{
$cmd = new admin_cmd_account_app($set_it,$account_id, $app, (array)$values['admin_cmd']);
}
else
{
$cmd = new admin_cmd_acl($data);
}
$cmd->run();
}
}
if (!(int)$rights)
{
if (count((array)$ids) > 1)
{
$msg = lang('%1 ACL entries deleted.', count($ids));
}
else
{
$msg = lang('ACL entry deleted.');
}
}
else
{
$msg = lang('ACL updated');
}
Api\Json\Response::get()->data(array(
'msg' => $msg,
'ids' => $ids,
'type' => !(int)$rights ? 'delete' : 'add',
));
}
catch (Exception $e) {
Api\Json\Response::get()->call('egw.message', $e->getMessage(), 'error');
}
}
public static function ajax_get_rights($account_id)
{
$acl_rights = Api\Hooks::process(array(
'location' => 'acl_rights',
'owner' => $account_id,
), array(), true);
Api\Json\Response::get()->data($acl_rights);
}
/**
* New index page
*
* @param array $_content =null
*/
public function index(array $_content=null)
{
unset($_content); // not used, required by function signature
$tpl = new Etemplate('admin.acl');
$content = array();
$account_id = isset($_GET['account_id']) && (int)$_GET['account_id'] ?
(int)$_GET['account_id'] : $GLOBALS['egw_info']['user']['account_id'];
$content['nm'] = array(
'get_rows' => 'admin_acl::get_rows',
'no_cat' => true,
'filter' => !empty($_GET['acl_filter']) ? $_GET['acl_filter'] :
($GLOBALS['egw_info']['flags']['currentapp'] != 'admin' ? 'other' :
$GLOBALS['egw_info']['user']['preferences']['admin']['acl_filter']),
'filter2' => !empty($_GET['acl_app']) ? $_GET['acl_app'] : '',
'filter2_onchange' => 'app.admin.acl_app_change',
'lettersearch' => false,
'order' => 'acl_appname',
'sort' => 'ASC',
'row_id' => 'id',
'account_id' => $account_id,
'actions' => self::get_actions(),
'acl_rights' => Api\Hooks::process(array(
'location' => 'acl_rights',
'owner' => $account_id,
), array(), true),
);
$user = Api\Accounts::username($content['nm']['account_id']);
$sel_options = array(
'filter' => array(
'other' => lang('Access to %1 data by others', $user),
'own' => lang('%1 access to other data', $user),
'run' => lang('%1 run rights for applications', $user),
)
);
// Set this so if loaded via preferences, js is still properly
// loaded into global app.admin
$GLOBALS['egw_info']['flags']['currentapp'] = 'admin';
$tpl->exec('admin.admin_acl.index', $content, $sel_options, array(), array(), 2);
}
/**
* Get actions for ACL
*
* @return array
*/
static function get_actions()
{
return array(
'edit' => array(
'caption' => 'Edit',
'default' => true,
'allowOnMultiple' => false,
'disableClass' => 'rowNoEdit',
'onExecute' => 'javaScript:app.admin.acl',
),
'add' => array(
'caption' => 'Add',
'disableClass' => 'rowNoEdit',
'onExecute' => 'javaScript:app.admin.acl',
),
'delete' => array(
'caption' => 'Delete',
'disableClass' => 'rowNoEdit',
'onExecute' => 'javaScript:app.admin.acl',
),
);
}
}