* Admin: allow to mark users as hidden, to show them only to admins e.g. to hide functional users from regular users

This commit is contained in:
ralf 2024-11-05 14:01:18 +01:00
parent d31915df05
commit e72c2e492a
13 changed files with 116 additions and 11 deletions

View File

@ -69,6 +69,7 @@ class admin_account
$acl = new Acl($content['account_id']);
$acl->read_repository();
$account['anonymous'] = $acl->check('anonymous', 1, 'phpgwapi');
$account['hidden'] = $acl->check('hidden', 1, 'phpgwapi');
$account['changepassword'] = !$acl->check('nopasswordchange', 1, 'preferences');
$auth = new Api\Auth();
if (($account['account_lastpwd_change'] = $auth->getLastPwdChange($account['account_lid'])) === false)
@ -116,7 +117,7 @@ class admin_account
// save old values to only trigger save, if one of the following values change (contact data get saved anyway)
$preserve = empty($content['id']) ? array() :
array('old_account' => array_intersect_key($account, array_flip(array(
'account_lid', 'account_status', 'account_groups', 'anonymous', 'changepassword',
'account_lid', 'account_status', 'account_groups', 'anonymous', 'hidden', 'changepassword',
'mustchangepassword', 'account_primary_group', 'homedirectory', 'loginshell',
'account_expires', 'account_firstname', 'account_lastname', 'account_email'))),
'deny_edit' => $deny_edit);
@ -185,7 +186,7 @@ class admin_account
'account_groups',
// copy following fields to account
'account_lid',
'changepassword', 'anonymous', 'mustchangepassword',
'changepassword', 'anonymous', 'hidden', 'mustchangepassword',
'account_passwd', 'account_passwd_2',
'account_primary_group',
'account_expires', 'account_status',
@ -215,6 +216,7 @@ class admin_account
case 'changepassword': // boolean values: admin_cmd_edit_user understands '' as NOT set
case 'anonymous':
case 'hidden':
case 'mustchangepassword':
$account[$a_name] = (boolean)$content[$c_name];
break;

View File

@ -107,6 +107,7 @@ class admin_cmd_edit_user extends admin_cmd_change_pw
// automatic set anonymous flag for username "anonymous", to not allow to create anonymous user without it
$data['anonymous'] = ($data['account_lid'] ?: admin_cmd::$accounts->id2name($this->account)) === 'anonymous' ?
true : admin_cmd::parse_boolean($data['anonymous'],$this->account ? null : false);
$data['hidden'] = admin_cmd::parse_boolean($data['hidden'],null);
if ($data['mustchangepassword'] && $data['changepassword'])
{
@ -187,6 +188,18 @@ class admin_cmd_edit_user extends admin_cmd_change_pw
admin_cmd::$acl->delete_repository('phpgwapi','anonymous',$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']);
}
}
if (!is_null($data['changepassword']))
{
if (!$data['changepassword'])
@ -274,4 +287,4 @@ class admin_cmd_edit_user extends admin_cmd_change_pw
}
return admin_cmd::parse_date($str);
}
}
}

View File

@ -122,6 +122,7 @@ anonymous user admin de Anonymer Benutzer
anonymous user (not shown in list sessions) admin de Anonymer Benutzer (wird bei 'Sitzungen anzeigen' nicht angezeigt).
anonymous user does not exist! admin de Anonymer Benutzer existiert NICHT!
anonymous user has no run-rights for the application! admin de Anonymer Benutzer hat KEINE Ausführungsrechte für die Anwendung!
anonymous user. not shown in list sessions. admin de Anonymer Benutzer. Wird nicht in der Liste der Sitzungen angezeigt.
any application admin de Jede Anwendung
any group admin de Jede Gruppe
any user admin de Jeder Benutzer
@ -958,6 +959,7 @@ user csv import admin de CSV-Import von Benutzern
user data common de Benutzerdaten
user for smtp-authentication (leave it empty if no auth required) admin de Benutzer für SMTP-Authentifizierung (leer lassen falls keine Authentifizierung nötig).
user groups admin de Benutzergruppen
user hidden vom non-admins. admin de Benutzer verborgen von nicht Administratoren.
user-agent admin de Browser
userdata admin de Benutzerkonto
userid@domain eg. u1234@domain admin de UserId@domain z.B. u1234@domain

View File

@ -122,6 +122,7 @@ anonymous user admin en Anonymous user
anonymous user (not shown in list sessions) admin en Anonymous user. Not shown in sessions list.
anonymous user does not exist! admin en Anonymous user does NOT exist!
anonymous user has no run-rights for the application! admin en Anonymous user has NO run-rights for the application!
anonymous user. not shown in list sessions. admin en Anonymous user. Not shown in list sessions.
any application admin en Any application
any group admin en Any group
any user admin en Any user
@ -958,6 +959,7 @@ user csv import admin en User CSV import
user data common en User data
user for smtp-authentication (leave it empty if no auth required) admin en User for SMTP authentication. Leave it empty if no authentication is required.
user groups admin en User groups
user hidden vom non-admins. admin en User hidden vom non-admins.
user-agent admin en User-Agent
userdata admin en User data
userid@domain eg. u1234@domain admin en UserId@domain eg. u1234@domain

View File

@ -39,7 +39,10 @@
</et2-vbox>
<et2-description></et2-description>
<et2-description></et2-description>
<et2-checkbox id="anonymous" label="Anonymous user. Not shown in list sessions."></et2-checkbox>
<et2-vbox>
<et2-checkbox id="anonymous" label="Anonymous user. Not shown in list sessions."></et2-checkbox>
<et2-checkbox id="hidden" label="User hidden vom non-admins."></et2-checkbox>
</et2-vbox>
<et2-description></et2-description>
</row>
<row disabled="!@ldap_extra_attributes">

View File

@ -199,6 +199,32 @@ class Accounts
return $key;
}
/**
* Implement hidden account-filter via explicit account_id's and "hidden" ACL location
*
* @param bool|null $hidden false: do NOT return hidden users, true: return only hidden user, null: return all users
* @param array|null $account_ids account_id filter, if set
* @return array|int[] array of account_id's optionally with extra "!" for $hidden === false
*/
public static function hidden2account_id(bool $hidden=null, array $account_ids=null)
{
$hidden_account_ids = array_keys($GLOBALS['egw']->acl->get_all_rights('hidden', 'phpgwapi') ?: []);
if (empty($account_ids))
{
$account_ids = $hidden_account_ids;
if ($hidden === false) $account_ids[] = '!';
}
elseif ($hidden === true)
{
$account_ids = array_intersect($account_ids, $hidden_account_ids);
}
else
{
throw new \InvalidArgumentException(__METHOD__."(): Can NOT have hidden=false AND an account_id filter!");
}
return $account_ids;
}
/**
* Searches / lists accounts: users and/or groups
*
@ -225,6 +251,7 @@ class Accounts
* 'lid','firstname','lastname','email' - query only the given field for containing $param[query]
* @param $param['app'] string with an app-name, to limit result on accounts with run-right for that app
* @param $param['active']=true boolean - true: return only acctive accounts, false: return expired or deactivated too
* @param $param['hidden']=false boolean false: do NOT return hidden users, true: return only hidden user, null: return all users
* @param $param['account_id'] int[] return only 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
@ -234,6 +261,8 @@ class Accounts
//error_log(__METHOD__.'('.array2string($param).') '.function_backtrace());
if (!isset($param['active'])) $param['active'] = true; // default is true = only return active accounts
if (!empty($param['offset']) && !isset($param['start'])) $param['start'] = 0;
// show hidden users by default only to admin
if (!array_key_exists('hidden', $param)) $param['hidden'] = !empty($GLOBALS['egw_info']['user']['apps']['admin']) ? null : false;
// Check for lang(Group) in search - if there, we search all groups
$group_index = array_search(strtolower(lang('Group')), array_map('strtolower', $query = explode(' ',$param['query'] ?? '')));
@ -259,6 +288,13 @@ class Accounts
$account_search = &self::$cache['account_search'];
$serial = self::cacheKey($param, $serial_unlimited);
// implement $param['hidden'] via $param['account_id']
if (isset($param['hidden']) && !in_array($param['type'],['groups', 'owngroups']))
{
$param['account_id'] = self::hidden2account_id($param['hidden'], $param['account_id']);
}
unset($param['hidden']);
// cache list of all groups on instance level (not session)
if ($serial_unlimited === self::cacheKey(['type'=>'groups','active'=>true]))
{
@ -312,7 +348,7 @@ class Accounts
}
$param['type'] = $param['type'] == 'groupmembers+memberships' ? 'both' : 'accounts';
}
// call ourself recursive to get (evtl. cached) full search
// call ourselves recursive to get (evtl. cached) full search
$full_search = $this->search($param);
// filter search now on accounts with run-rights for app or a group

View File

@ -1062,7 +1062,15 @@ class Ads
}
if (!empty($param['account_id']))
{
if (($not_account_ids = array_search('!', $param['account_id'])) !== false)
{
unset($param['account_id'][$not_account_ids]);
}
$account_ids_filter = '(|(objectsid='.implode(')(objectsid=', array_map([$this, 'get_sid'], (array)$param['account_id'])).'))';
if ($not_account_ids !== false)
{
$account_ids_filter = '(!'.$account_ids_filter.')';
}
$filter = $filter ? "(&$filter$account_ids_filter)" : $account_ids_filter;
}
if (!empty($param['modified']))

View File

@ -842,7 +842,17 @@ class Ldap
// only return given account_id's
if (!empty($param['account_id']))
{
// do we need a negated account_id filter
if (($not_account_ids = array_search('!', $param['account_id'])) !== false)
{
$filter .= '(!';
unset($param['account_id'][$not_account_ids]);
}
$filter .= '(|(uidNumber=' . implode(')(uidNumber=', (array)$param['account_id']) . '))';
if ($not_account_ids !== false)
{
$filter .= ')';
}
}
if (!empty($param['modified']))
{

View File

@ -510,7 +510,11 @@ class Sql
$this->total = 0;
return array();
}
$filter[] = $this->db->expression($this->table, $this->table.'.', 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'],
));
unset($filter['account_id']);

View File

@ -185,7 +185,7 @@ class Ads extends Ldap
/**
* Return LDAP filter for (multiple) account ids
*
* @param int|int[]|null $ids
* @param int|int[]|null $ids use "!" to negate the whole filter
* @return string
*/
protected function account_ids_filter($ids)
@ -197,12 +197,20 @@ class Ads extends Ldap
}
elseif ($ids)
{
if (($not_account_ids = array_search('!', $ids)) !== false)
{
unset($ids[$not_account_ids]);
}
if (is_array($ids)) $filter = '(|';
foreach((array)$ids as $account_id)
{
$filter .= '(objectsid='.$this->accounts_ads->get_sid($account_id).')';
}
if (is_array($ids)) $filter .= ')';
if ($not_account_ids !== false)
{
$filter = '(!'.$filter.')';
}
}
return $filter;
}

View File

@ -470,10 +470,11 @@ class Ldap
* Return LDAP filter for (multiple) contact ids
*
* @param array|string $ids
* @param bool $not=false true: negate the filter
* @throws Api\Exception\AssertionFailed if $contact_id is no valid GUID (for ADS!)
* @return string
*/
protected function ids_filter($ids)
protected function ids_filter($ids, bool $not=false)
{
if (!is_array($ids) || count($ids) == 1)
{
@ -484,13 +485,13 @@ class Ldap
{
$filter[] = $this->id_filter($id);
}
return '(|'.implode('', $filter).')';
return ($not ? '(!' : '').'(|'.implode('', $filter).')'.($not ? ')' : '');
}
/**
* Return LDAP filter for (multiple) account ids
*
* @param int|int[]|null $ids
* @param int|int[]|null $ids use "!" to negate the whole filter
* @return string
*/
protected function account_ids_filter($ids)
@ -502,10 +503,14 @@ class Ldap
}
elseif ($ids)
{
if (($not_account_ids = array_search('!', $ids)) !== false)
{
unset($ids[$not_account_ids]);
}
$filter = $this->ids_filter(array_map(static function($account_id)
{
return $GLOBALS['egw']->accounts->id2name($account_id, 'person_id');
}, (array)$ids));
}, (array)$ids), $not_account_ids !== false);
}
return $filter;
}

View File

@ -716,6 +716,13 @@ class Sql extends Api\Storage
$this->sanitize_order_by = false;
}
// implement negated account_id filter
if (!empty($filter['account_id']) && ($not_account_ids = array_search('!', $filter['account_id'])) !== false)
{
$filter[] = $this->db->expression($this->table_name, ' NOT ', $this->table_name.'.', ['account_id' => $filter['account_id']]);
unset($filter['account_id']);
}
$rows =& parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join,$need_full_no_count);
if ($start === false) $this->total = is_array($rows) ? count($rows) : 0; // so_sql sets total only for $start !== false!

View File

@ -659,6 +659,11 @@ class Storage
{
//error_log(__METHOD__.'('.array2string($criteria,true).','.array2string($only_keys).",'$order_by','$extra_cols','$wildcard','$empty','$op',".array2string($start).','.array2string($filter,true).",'$join')");
// add hidden user filter for non-admins
if (empty($GLOBALS['egw_info']['user']['apps']['admin']) && empty($filter['owner']))
{
$filter['account_id'] = Api\Accounts::hidden2account_id(false, $filter['account_id']);
}
// Handle 'None' country option
if(is_array($filter) && isset($filter['adr_one_countrycode']) && $filter['adr_one_countrycode'] === '-custom-')
{