* Admin/Api: allow to hide groups from non-admins

This commit is contained in:
ralf 2025-03-03 11:03:24 +01:00
parent ce0ca47b6a
commit f52ece8366
9 changed files with 69 additions and 29 deletions

View File

@ -82,6 +82,10 @@ class admin_cmd_edit_group extends admin_cmd
{
$data['account_members'] = admin_cmd::parse_accounts($data['account_members'],true);
}
if (isset($data['hidden']))
{
$data['hidden'] = admin_cmd::parse_boolean($data['hidden'],null);
}
if ($check_only) return true;
if (($update = $this->account))
@ -121,6 +125,18 @@ class admin_cmd_edit_group extends admin_cmd
{
admin_cmd::$accounts->set_members($data['account_members'],$data['account_id']);
}
if (isset($data['hidden']))
{
admin_cmd::_instanciate_acl();
if ($data['hidden'])
{
admin_cmd::$acl->add_repository('phpgwapi','hidden',$data['account_id'],1);
}
else
{
admin_cmd::$acl->delete_repository('phpgwapi','hidden',$data['account_id']);
}
}
// make new account_id available to caller
$this->account = $data['account_id'];
@ -177,7 +193,7 @@ class admin_cmd_edit_group extends admin_cmd
{
$widgets = parent::get_change_widgets();
$widgets['account_id'] = 'integer'; // normaly not displayed
$widgets['account_id'] = 'integer'; // normally not displayed
$widgets['run'] = 'select-app';
return $widgets;
@ -186,7 +202,7 @@ class admin_cmd_edit_group extends admin_cmd
/**
* Return the whole object-data as array, it's a cast of the object to an array
*
* Reimplement to supress data not relevant for groups, but historically stored
* Reimplement to suppress data not relevant for groups, but historically stored
*
* @todo Fix command to store it's data in a more sane way, like we use it.
* @return array
@ -247,4 +263,4 @@ class admin_cmd_edit_group extends admin_cmd
return $data;
}
}
}

View File

@ -476,6 +476,7 @@ group excepted from above export limit (admins are always excepted) admin de Gru
group has been added common de Gruppe wurde hinzugefügt.
group has been deleted common de Gruppe wurde gelöscht.
group has been updated common de Gruppe wurde aktualisiert.
group hidden from non-admins admin de Gruppe versteckt für Nicht-Administratoren
group hierarchy admin de Gruppen Hierarchie
group list admin de Liste der Gruppen
group manager admin de Gruppenmanager

View File

@ -476,6 +476,7 @@ group excepted from above export limit (admins are always excepted) admin en Gro
group has been added common en Group has been added.
group has been deleted common en Group has been deleted.
group has been updated common en Group has been updated.
group hidden from non-admins admin en Group hidden from non-admins
group hierarchy admin en Group hierarchy
group list admin en Group list
group manager admin en Group manager

View File

@ -118,6 +118,9 @@ class Groups
unset($content['account_members'][$key]);
}
}
$acl = new Acl($content['account_id']);
$acl->read_repository();
$content['hidden'] = $acl->check('hidden', 1, 'phpgwapi');
$content['old'] = $content;
}
else
@ -238,7 +241,7 @@ class Groups
if ($run_rights[$app]) $content['old_run'][] = $app;
$readonlys['apps']['button['.$app.']'] = !$acl_action;
}
usort($content['apps'], function($a, $b)
usort($content['apps'], static function($a, $b)
{
if ($a['run'] !== $b['run']) return $b['run']-$a['run'];
return strcasecmp($a['title'], $b['title']);
@ -271,6 +274,7 @@ class Groups
'account_lid',
'account_description',
'account_members',
'hidden',
);
// Only send real changes
$account = array();
@ -301,11 +305,11 @@ class Groups
if(count($account) == 0) return $content['account_id'];
$cmd = new admin_cmd_edit_group(array(
'account' => (int)$content['account_id'],
'set' => $account,
'old' => $old,
// This is the documentation from policy app
)+(array)$content['admin_cmd']);
'account' => (int)$content['account_id'],
'set' => $account,
'old' => $old,
// This is the documentation from policy app
)+(array)$content['admin_cmd']);
$msg = $cmd->run();
return $cmd->account;
}

View File

@ -2,12 +2,12 @@
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2.0//EN" "https://www.egroupware.org/etemplate2.0.dtd">
<overlay>
<template id="admin.group.edit.members" template="" lang="" group="0" version="1.9.001">
<et2-select-account id="account_members" multiple="true" accountType="accounts" required="true"></et2-select-account>
<et2-select-account id="account_members" multiple="true" accountType="accounts" required="true" rows="5"></et2-select-account>
</template>
<template id="admin.group.edit.apps" template="" lang="" group="0" version="1.9.001">
<grid width="100%" id="apps" overflow="auto" height="200" resize_ratio="1">
<grid width="100%" id="apps" overflow="auto" height="250" resize_ratio="1">
<columns>
<column width="32px"/>
<column width="44px"/>
<column/>
<column/>
</columns>
@ -41,7 +41,10 @@
</row>
<row>
<et2-description value="Filesystem quota"></et2-description>
<et2-textbox id="quota" disabled="!@epl" placeholder="@default_quota"></et2-textbox>
<et2-hbox>
<et2-textbox id="quota" disabled="!@epl" placeholder="@default_quota"></et2-textbox>
<et2-checkbox id="hidden" label="Group hidden from non-admins"></et2-checkbox>
</et2-hbox>
</row>
<row>
<et2-description value="Container" for="account_dn"></et2-description>
@ -49,7 +52,7 @@
</row>
</rows>
</grid>
<et2-tabbox id="tabs" class="et2_nowrap" span="all" width="100%" tabHeight="250px">
<et2-tabbox id="tabs" class="et2_nowrap" span="all" width="100%" tabHeight="280px">
<tabs>
<tab id="members" label="Members" statustext="Users in this group"/>
<tab id="apps" label="Applications" statustext="Applications this group can use"/>

View File

@ -292,17 +292,21 @@ class Accounts
$serial = self::cacheKey($param, $serial_unlimited);
// implement $param['hidden'] via $param['account_id']
if (isset($param['hidden']) && !in_array($param['type'],['groups', 'owngroups']) &&
if (isset($param['hidden']) &&
($account_id_filter = self::hidden2account_id($param['hidden'], (array)($param['account_id']??null))))
{
$param['account_id'] = $account_id_filter;
$hidden_groups = (bool)array_filter($account_id_filter, static function ($account_id)
{
return $account_id !== '!' && $account_id < 0;
});
}
unset($param['hidden']);
// cache list of all groups on instance level (not session)
// cache list of all groups on instance level (not session), taking into account that some groups might be hidden from non-admins
if ($serial_unlimited === self::cacheKey(['type'=>'groups','active'=>true]))
{
$result = Cache::getCache($this->config['install_id'], __CLASS__, 'groups', function() use ($param)
$result = Cache::getCache($this->config['install_id'], __CLASS__, 'groups'.(!empty($hidden_groups)?'-hidden':''), function() use ($param)
{
return $this->backend->search($param);
}, [], self::READ_CACHE_TIMEOUT);

View File

@ -1010,7 +1010,7 @@ class Ads
* @param $param['objectclass'] boolean return objectclass(es) under key 'objectclass' in each account
* @param $param['active'] boolean true: only return active / not expired accounts
* @param $param['modified'] int if given minimum modification time
* @param $param['account_id'] int[] return only given account_id's
* @param $param['account_id'] int[]|string[] return only given account_id's, include "!" to return everything, but the given account_id's
* @return array with account_id => data pairs, data is an array with account_id, account_lid, account_firstname,
* account_lastname, person_id (id of the linked addressbook entry), account_status, account_expires, account_primary_group
*/

View File

@ -757,7 +757,7 @@ class Ldap
* @param $param['offset'] int - number of matches to return if start given, default use the value in the prefs
* @param $param['objectclass'] boolean return objectclass(es) under key 'objectclass' in each account
* @param $param['modified'] int if given minimum modification time
* @param $param['account_id'] int[] return only given account_id's
* @param $param['account_id'] int[]|string[] return only given account_id's, include "!" to return everything, but the given account_id's
* @return array with account_id => data pairs, data is an array with account_id, account_lid, account_firstname,
* account_lastname, person_id (id of the linked addressbook entry), account_status, account_expires, account_primary_group
*/
@ -848,7 +848,10 @@ class Ldap
$filter .= '(!';
unset($param['account_id'][$not_account_ids]);
}
$filter .= '(|(uidNumber=' . implode(')(uidNumber=', (array)$param['account_id']) . '))';
$filter .= '(|'.implode('', array_map(static function($account_id)
{
return $account_id < 0 ? '(gidNumber='.$account_id.')' : '(uidNumber='.$account_id.')';
})).')';
if ($not_account_ids !== false)
{
$filter .= ')';

View File

@ -424,7 +424,7 @@ class Sql
* 'lid','firstname','lastname','email' - query only the given field for containing $param[query]
* @param $param['offset'] int - number of matches to return if start given, default use the value in the prefs
* @param $param['objectclass'] boolean return objectclass(es) under key 'objectclass' in each account
* @param $param['account_id'] int[] return only given account_id's
* @param $param['account_id'] int[]|string[] return only given account_id's, include "!" to return everything, but the given account_id's
* @return array with account_id => data pairs, data is an array with account_id, account_lid, account_firstname,
* account_lastname, person_id (id of the linked addressbook entry), account_status, account_expires, account_primary_group
*/
@ -466,7 +466,14 @@ class Sql
$join .= ' LEFT JOIN '.Api\Mail\Smtp\Sql::TABLE.' ON '.$this->table.'.account_id=-'.Api\Mail\Smtp\Sql::TABLE.'.account_id AND mail_type='.Api\Mail\Smtp\Sql::TYPE_ALIAS;
}
$filter = empty($param['account_id']) ? [] : ['account_id' => (array)$param['account_id']];
$filter = [];
// implement negated account_id filter used for hidden accounts/groups
if (!empty($param['account_id']) && ($not_account_ids = array_search('!', $param['account_id'])) !== false)
{
unset($param['account_id'][$not_account_ids]);
$filter[] = $this->db->expression(self::TABLE, 'NOT ('.self::TABLE.'.', ['account_id' => array_map('abs', (array)$param['account_id'])], ')');
unset($param['account_id']);
}
switch($param['type'])
{
case 'accounts':
@ -502,20 +509,21 @@ class Sql
$filter[] = "(egw_addressbook.contact_owner=0 OR egw_addressbook.contact_owner IS NULL)";
break;
}
// if params also has an account_id filter, we need to intersect both (the negated account_id filter is already handled above)
if (!empty($params['account_id']))
{
$filter['account_id'] = isset($filter['account_id']) ? array_intersect($filter['account_id'], $params['account_id']) : $params['account_id'];
}
// fix ambiguous account_id (used in accounts and contacts table)
if (array_key_exists('account_id', $filter))
{
if (!$filter['account_id']) // eg. group without members (would give SQL error)
if (!$filter['account_id']) // e.g. group without members (would give SQL error)
{
$this->total = 0;
return array();
}
if (($not_account_id = array_search('!', $filter['account_id'])) !== false)
{
unset($filter['account_id'][$not_account_id]);
}
$filter[] = ($not_account_id !== false ? ' NOT ' : '').$this->db->expression($this->table, $this->table.'.', array(
'account_id' => $filter['account_id'],
$filter[] = $this->db->expression($this->table, $this->table.'.', array(
'account_id' => array_map('abs', (array)$filter['account_id']),
));
unset($filter['account_id']);
}