auto-format code using perltidy with Proxmox style guide

using the new top-level `make tidy` target, which calls perltidy via
our wrapper to enforce the desired style as closely as possible.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht 2025-06-01 14:02:38 +02:00
parent 124e7a199b
commit 9590c6bdfe
34 changed files with 6005 additions and 5472 deletions

View File

@ -14,16 +14,22 @@ use PVE::RESTHandler;
use base qw(PVE::RESTHandler);
register_standard_option('acl-propagate', {
register_standard_option(
'acl-propagate',
{
description => "Allow to propagate (inherit) permissions.",
type => 'boolean',
optional => 1,
default => 1,
});
register_standard_option('acl-path', {
},
);
register_standard_option(
'acl-path',
{
description => "Access control path",
type => 'string',
});
},
);
__PACKAGE__->register_method({
name => 'read_acl',
@ -31,7 +37,8 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Get Access Control List (ACLs).",
permissions => {
description => "The returned list is restricted to objects where you have rights to modify permissions.",
description =>
"The returned list is restricted to objects where you have rights to modify permissions.",
user => 'all',
},
parameters => {
@ -67,7 +74,10 @@ __PACKAGE__->register_method ({
my $audit = $rpcenv->check($authuser, '/access', ['Sys.Audit'], 1);
my $root = $usercfg->{acl_root};
PVE::AccessControl::iterate_acl_tree("/", $root, sub {
PVE::AccessControl::iterate_acl_tree(
"/",
$root,
sub {
my ($path, $node) = @_;
foreach my $type (qw(user group token)) {
my $d = $node->{"${type}s"};
@ -76,7 +86,8 @@ __PACKAGE__->register_method ({
foreach my $id (keys %$d) {
foreach my $role (keys %{ $d->{$id} }) {
my $propagate = $d->{$id}->{$role};
push @$res, {
push @$res,
{
path => $path,
type => $type,
ugid => $id,
@ -86,10 +97,12 @@ __PACKAGE__->register_method ({
}
}
}
});
},
);
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'update_acl',
@ -107,22 +120,26 @@ __PACKAGE__->register_method ({
path => get_standard_option('acl-path'),
users => {
description => "List of users.",
type => 'string', format => 'pve-userid-list',
type => 'string',
format => 'pve-userid-list',
optional => 1,
},
groups => {
description => "List of groups.",
type => 'string', format => 'pve-groupid-list',
type => 'string',
format => 'pve-groupid-list',
optional => 1,
},
tokens => {
description => "List of API tokens.",
type => 'string', format => 'pve-tokenid-list',
type => 'string',
format => 'pve-tokenid-list',
optional => 1,
},
roles => {
description => "List of roles.",
type => 'string', format => 'pve-roleid-list',
type => 'string',
format => 'pve-roleid-list',
},
delete => {
description => "Remove permissions (instead of adding it).",
@ -136,7 +153,10 @@ __PACKAGE__->register_method ({
my ($param) = @_;
if (!($param->{users} || $param->{groups} || $param->{tokens})) {
raise_param_exc({ map { $_ => "either 'users', 'groups' or 'tokens' is required." } qw(users groups tokens) });
raise_param_exc({
map { $_ => "either 'users', 'groups' or 'tokens' is required." }
qw(users groups tokens)
});
}
my $path = PVE::AccessControl::normalize_path($param->{path});
@ -173,17 +193,32 @@ __PACKAGE__->register_method ({
my $role_privs = $cfg->{roles}->{$role};
my $verb = $param->{delete} ? 'remove' : 'add';
foreach my $priv (keys $role_privs->%*) {
raise_param_exc({ role => "Cannot $verb role '$role' - requires 'Permissions.Modify' or superset of privileges." })
if !defined($auth_user_privs->{$priv});
raise_param_exc(
{
role =>
"Cannot $verb role '$role' - requires 'Permissions.Modify' or superset of privileges.",
},
) if !defined($auth_user_privs->{$priv});
# propagation is only potentially problematic for adding ACLs, not removing..
raise_param_exc({ role => "Cannot $verb role '$role' with propagation - requires 'Permissions.Modify' or propagated superset of privileges." })
if $propagate && $auth_user_privs->{$priv} != $propagate && !$param->{delete};
raise_param_exc(
{
role =>
"Cannot $verb role '$role' with propagation - requires 'Permissions.Modify' or propagated superset of privileges.",
},
)
if $propagate
&& $auth_user_privs->{$priv} != $propagate
&& !$param->{delete};
}
# NoAccess has no privs, needs an explicit check
raise_param_exc({ role => "Cannot $verb role '$role' - requires 'Permissions.Modify'"})
if $role eq 'NoAccess';
raise_param_exc(
{
role =>
"Cannot $verb role '$role' - requires 'Permissions.Modify'",
},
) if $role eq 'NoAccess';
}
foreach my $group (split_list($param->{groups})) {
@ -224,9 +259,12 @@ __PACKAGE__->register_method ({
}
cfs_write_file("user.cfg", $cfg);
}, "ACL update failed");
},
"ACL update failed",
);
return undef;
}});
},
});
1;

View File

@ -108,8 +108,8 @@ __PACKAGE__->register_method ({
push @$res, { subdir => 'password' };
return $res;
}});
},
});
my sub verify_auth : prototype($$$$$$) {
my ($rpcenv, $username, $pw_or_ticket, $otp, $path, $privs) = @_;
@ -118,16 +118,16 @@ my sub verify_auth : prototype($$$$$$) {
die "invalid path - $path\n" if defined($path) && !defined($normpath);
my $ticketuser;
if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) &&
($ticketuser eq $username)) {
if (
($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1))
&& ($ticketuser eq $username)
) {
# valid ticket
} elsif (PVE::AccessControl::verify_vnc_ticket($pw_or_ticket, $username, $normpath, 1)) {
# valid vnc ticket
} else {
$username = PVE::AccessControl::authenticate_user(
$username,
$pw_or_ticket,
$otp,
$username, $pw_or_ticket, $otp,
);
}
@ -137,7 +137,7 @@ my sub verify_auth : prototype($$$$$$) {
}
return { username => $username };
};
}
my sub create_ticket_do : prototype($$$$$) {
my ($rpcenv, $username, $pw_or_ticket, $otp, $tfa_challenge) = @_;
@ -160,10 +160,7 @@ my sub create_ticket_do : prototype($$$$$) {
# valid ticket. Note: root@pam can create tickets for other users
} else {
($username, $tfa_info) = PVE::AccessControl::authenticate_user(
$username,
$pw_or_ticket,
$otp,
$tfa_challenge,
$username, $pw_or_ticket, $otp, $tfa_challenge,
);
}
@ -185,7 +182,7 @@ my sub create_ticket_do : prototype($$$$$) {
CSRFPreventionToken => $csrftoken,
%extra,
};
};
}
__PACKAGE__->register_method({
name => 'get_ticket',
@ -197,7 +194,8 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
},
returns => { type => "null" },
code => sub { return undef; }});
code => sub { return undef; },
});
__PACKAGE__->register_method({
name => 'create_ticket',
@ -205,7 +203,7 @@ __PACKAGE__->register_method ({
method => 'POST',
permissions => {
description => "You need to pass valid credientials.",
user => 'world'
user => 'world',
},
protected => 1, # else we can't access shadow files
allowtoken => 0, # we don't want tokens to create tickets
@ -219,12 +217,16 @@ __PACKAGE__->register_method ({
maxLength => 64,
completion => \&PVE::AccessControl::complete_username,
},
realm => get_standard_option('realm', {
description => "You can optionally pass the realm using this parameter. Normally"
realm => get_standard_option(
'realm',
{
description =>
"You can optionally pass the realm using this parameter. Normally"
. " the realm is simply added to the username <username>\@<realm>.",
optional => 1,
completion => \&PVE::AccessControl::complete_realm,
}),
},
),
password => {
description => "The secret password. This can also be a valid ticket.",
type => 'string',
@ -243,7 +245,8 @@ __PACKAGE__->register_method ({
},
privs => {
description => "Verify ticket, and check if user have access 'privs' on 'path'",
type => 'string' , format => 'pve-priv-list',
type => 'string',
format => 'pve-priv-list',
requires => 'path',
optional => 1,
maxLength => 64,
@ -259,7 +262,7 @@ __PACKAGE__->register_method ({
description => "The signed TFA challenge string the user wants to respond to.",
optional => 1,
},
}
},
},
returns => {
type => "object",
@ -269,7 +272,7 @@ __PACKAGE__->register_method ({
CSRFPreventionToken => { type => 'string', optional => 1 },
clustername => { type => 'string', optional => 1 },
# cap => computed api permissions, unless there's a u2f challenge
}
},
},
code => sub {
my ($param) = @_;
@ -286,8 +289,14 @@ __PACKAGE__->register_method ({
$rpcenv->check_user_enabled($username);
if ($param->{path} && $param->{privs}) {
$res = verify_auth($rpcenv, $username, $param->{password}, $param->{otp},
$param->{path}, $param->{privs});
$res = verify_auth(
$rpcenv,
$username,
$param->{password},
$param->{otp},
$param->{path},
$param->{privs},
);
} else {
$res = create_ticket_do(
$rpcenv,
@ -316,24 +325,27 @@ __PACKAGE__->register_method ({
PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'change_password',
path => 'password',
method => 'PUT',
permissions => {
description => "Each user is allowed to change their own password. A user can change the"
description =>
"Each user is allowed to change their own password. A user can change the"
. " password of another user if they have 'Realm.AllocateUser' (on the realm of user"
. " <userid>) and 'User.Modify' permission on /access/groups/<group> on a group where"
. " user <userid> is member of. For the PAM realm, a password change does not take "
. " effect cluster-wide, but only applies to the local node.",
check => [ 'or',
check => [
'or',
['userid-param', 'self'],
[ 'and',
[ 'userid-param', 'Realm.AllocateUser'],
[ 'userid-group', ['User.Modify']]
]
[
'and', ['userid-param', 'Realm.AllocateUser'],
['userid-group', ['User.Modify']],
],
],
},
protected => 1, # else we can't access shadow files
@ -350,7 +362,7 @@ __PACKAGE__->register_method ({
maxLength => 64,
},
'confirmation-password' => $PVE::API2::TFA::OPTIONAL_PASSWORD_SCHEMA,
}
},
},
returns => { type => "null" },
code => sub {
@ -385,7 +397,8 @@ __PACKAGE__->register_method ({
PVE::Cluster::log_msg('info', 'root@pam', "changed password for user '$userid'");
return undef;
}});
},
});
sub get_u2f_config() {
die "u2f support not available\n" if !$u2f_available;
@ -458,14 +471,14 @@ sub verify_user_tfa_config {
PVE::OTP::oath_verify_otp($value, $secret, $step, $digits);
}
__PACKAGE__->register_method({
name => 'permissions',
path => 'permissions',
method => 'GET',
description => 'Retrieve effective permissions of given user/token.',
permissions => {
description => "Each user/token is allowed to dump their own permissions (or that of owned"
description =>
"Each user/token is allowed to dump their own permissions (or that of owned"
. " tokens). A user can dump the permissions of another user or their tokens if they"
. " have 'Sys.Audit' permission on /access.",
user => 'all',
@ -479,10 +492,13 @@ __PACKAGE__->register_method({
pattern => $PVE::AccessControl::userid_or_token_regex,
optional => 1,
},
path => get_standard_option('acl-path', {
path => get_standard_option(
'acl-path',
{
description => "Only dump this specific path, not the whole tree.",
optional => 1,
}),
},
),
},
},
returns => {
@ -519,6 +535,7 @@ __PACKAGE__->register_method({
}
return $res;
}});
},
});
1;

View File

@ -48,7 +48,8 @@ my $map_sync_default_options = sub {
my $new_opt = $map_remove_vanished->($old_opt, $delete_deprecated);
$cfg->{'sync-defaults-options'} = PVE::JSONSchema::print_property_string($new_opt, $sync_opts_fmt);
$cfg->{'sync-defaults-options'} =
PVE::JSONSchema::print_property_string($new_opt, $sync_opts_fmt);
};
__PACKAGE__->register_method({
@ -57,7 +58,8 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Authentication domain index.",
permissions => {
description => "Anyone can access that, because we need that list for the login box (before the user is authenticated).",
description =>
"Anyone can access that, because we need that list for the login box (before the user is authenticated).",
user => 'world',
},
parameters => {
@ -78,7 +80,8 @@ __PACKAGE__->register_method ({
optional => 1,
},
comment => {
description => "A comment. The GUI use this text when you select a domain (Realm) on the login window.",
description =>
"A comment. The GUI use this text when you select a domain (Realm) on the login window.",
type => 'string',
optional => 1,
},
@ -106,7 +109,8 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'create',
@ -117,14 +121,17 @@ __PACKAGE__->register_method ({
check => ['perm', '/access/realm', ['Realm.Allocate']],
},
description => "Add an authentication server.",
parameters => PVE::Auth::Plugin->createSchema(0, {
parameters => PVE::Auth::Plugin->createSchema(
0,
{
'check-connection' => {
description => 'Check bind connection to the server.',
type => 'boolean',
optional => 1,
default => 0,
},
}),
},
),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@ -151,7 +158,8 @@ __PACKAGE__->register_method ({
die "unable to create builtin type '$type'\n"
if ($type eq 'pam' || $type eq 'pve');
die "'check-connection' parameter can only be set for realms of type 'ldap' or 'ad'\n"
die
"'check-connection' parameter can only be set for realms of type 'ldap' or 'ad'\n"
if defined($check_connection) && !($type eq 'ldap' || $type eq 'ad');
if ($type eq 'ad' || $type eq 'ldap') {
@ -181,10 +189,13 @@ __PACKAGE__->register_method ({
if $check_connection;
cfs_write_file($domainconfigfile, $cfg);
}, "add auth server failed");
},
"add auth server failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'update',
@ -195,14 +206,17 @@ __PACKAGE__->register_method ({
},
description => "Update authentication server settings.",
protected => 1,
parameters => PVE::Auth::Plugin->updateSchema(0, {
parameters => PVE::Auth::Plugin->updateSchema(
0,
{
'check-connection' => {
description => 'Check bind connection to the server.',
type => 'boolean',
optional => 1,
default => 0,
},
}),
},
),
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@ -226,7 +240,8 @@ __PACKAGE__->register_method ({
die "domain '$realm' does not exist\n"
if !$ids->{$realm};
die "'check-connection' parameter can only be set for realms of type 'ldap' or 'ad'\n"
die
"'check-connection' parameter can only be set for realms of type 'ldap' or 'ad'\n"
if defined($check_connection) && !($type eq 'ldap' || $type eq 'ad');
my $delete_str = extract_param($param, 'delete');
@ -268,10 +283,13 @@ __PACKAGE__->register_method ({
if $check_connection;
cfs_write_file($domainconfigfile, $cfg);
}, "update auth server failed");
},
"update auth server failed",
);
return undef;
}});
},
});
# fixme: return format!
__PACKAGE__->register_method({
@ -307,8 +325,8 @@ __PACKAGE__->register_method ({
$data->{digest} = $cfg->{digest};
return $data;
}});
},
});
__PACKAGE__->register_method({
name => 'delete',
@ -323,7 +341,7 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
realm => get_standard_option('realm'),
}
},
},
returns => { type => 'null' },
code => sub {
@ -345,10 +363,13 @@ __PACKAGE__->register_method ({
delete $ids->{$realm};
cfs_write_file($domainconfigfile, $cfg);
}, "delete auth server failed");
},
"delete auth server failed",
);
return undef;
}});
},
});
my $update_users = sub {
my ($usercfg, $realm, $synced_users, $opts) = @_;
@ -474,8 +495,9 @@ my $parse_sync_opts = sub {
# only scope has no implicit value
raise_param_exc({
"scope" => 'Not passed as parameter and not defined in realm default sync options.'
}) if !defined($res->{scope});
"scope" => 'Not passed as parameter and not defined in realm default sync options.',
})
if !defined($res->{scope});
return $res;
};
@ -487,7 +509,8 @@ __PACKAGE__->register_method ({
permissions => {
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
. " 'User.Modify' permissions to '/access/groups/'.",
check => [ 'and',
check => [
'and',
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
['perm', '/access/groups', ['User.Modify']],
],
@ -498,7 +521,9 @@ __PACKAGE__->register_method ({
protected => 1,
parameters => {
additionalProperties => 0,
properties => get_standard_option('realm-sync-options', {
properties => get_standard_option(
'realm-sync-options',
{
realm => get_standard_option('realm'),
'dry-run' => {
description => "If set, does not write anything.",
@ -506,11 +531,12 @@ __PACKAGE__->register_method ({
optional => 1,
default => 0,
},
}),
},
),
},
returns => {
description => 'Worker Task-UPID',
type => 'string'
type => 'string',
},
code => sub {
my ($param) = @_;
@ -547,7 +573,8 @@ __PACKAGE__->register_method ({
$synced_groups = $plugin->get_groups($realmconfig, $realm, $dnmap);
}
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
print "got data from server, updating $whatstring\n";
@ -560,16 +587,20 @@ __PACKAGE__->register_method ({
}
if ($dry_run) {
print "\nNOTE: Dry test run, changes were NOT written to the configuration.\n";
print
"\nNOTE: Dry test run, changes were NOT written to the configuration.\n";
return;
}
cfs_write_file("user.cfg", $usercfg);
print "successfully updated $whatstring configuration\n";
}, "syncing $whatstring failed");
},
"syncing $whatstring failed",
);
};
my $workerid = !$dry_run ? 'auth-realm-sync' : 'auth-realm-sync-test';
return $rpcenv->fork_worker($workerid, $realm, $authuser, $worker);
}});
},
});
1;

View File

@ -10,11 +10,14 @@ use PVE::JSONSchema qw(get_standard_option register_standard_option);
use base qw(PVE::RESTHandler);
register_standard_option('group-id', {
register_standard_option(
'group-id',
{
type => 'string',
format => 'pve-groupid',
completion => \&PVE::AccessControl::complete_group,
});
},
);
register_standard_option('group-comment', { type => 'string', optional => 1 });
@ -24,7 +27,8 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Group index.",
permissions => {
description => "The returned list is restricted to groups where you have 'User.Modify', 'Sys.Audit' or 'Group.Allocate' permissions on /access/groups/<group>.",
description =>
"The returned list is restricted to groups where you have 'User.Modify', 'Sys.Audit' or 'Group.Allocate' permissions on /access/groups/<group>.",
user => 'all',
},
parameters => {
@ -64,12 +68,14 @@ __PACKAGE__->register_method ({
my $data = $usercfg->{groups}->{$group};
my $entry = { groupid => $group };
$entry->{comment} = $data->{comment} if defined($data->{comment});
$entry->{users} = join (',', sort keys %{$data->{users}}) if defined($data->{users});
$entry->{users} = join(',', sort keys %{ $data->{users} })
if defined($data->{users});
push @$res, $entry;
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'create_group',
@ -103,14 +109,17 @@ __PACKAGE__->register_method ({
$usercfg->{groups}->{$group} = { users => {} };
$usercfg->{groups}->{$group}->{comment} = $param->{comment} if $param->{comment};
$usercfg->{groups}->{$group}->{comment} = $param->{comment}
if $param->{comment};
cfs_write_file("user.cfg", $usercfg);
}, "create group failed");
},
"create group failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'update_group',
@ -147,10 +156,13 @@ __PACKAGE__->register_method ({
$data->{comment} = $param->{comment} if defined($param->{comment});
cfs_write_file("user.cfg", $usercfg);
}, "update group failed");
},
"update group failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'read_group',
@ -173,7 +185,7 @@ __PACKAGE__->register_method ({
comment => get_standard_option('group-comment'),
members => {
type => 'array',
items => get_standard_option('userid-completed')
items => get_standard_option('userid-completed'),
},
},
},
@ -195,8 +207,8 @@ __PACKAGE__->register_method ({
$res->{comment} = $data->{comment} if defined($data->{comment});
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'delete_group',
@ -211,7 +223,7 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
groupid => get_standard_option('group-id'),
}
},
},
returns => { type => 'null' },
code => sub {
@ -232,9 +244,12 @@ __PACKAGE__->register_method ({
PVE::AccessControl::delete_group_acl($group, $usercfg);
cfs_write_file("user.cfg", $usercfg);
}, "delete group failed");
},
"delete group failed",
);
return undef;
}});
},
});
1;

View File

@ -50,7 +50,7 @@ __PACKAGE__->register_method ({
properties => {
id => {
description => "The ID of the entry.",
type => 'string'
type => 'string',
},
enabled => {
description => "If the job is enabled or not.",
@ -69,12 +69,14 @@ __PACKAGE__->register_method ({
scope => get_standard_option('sync-scope'),
'remove-vanished' => get_standard_option('sync-remove-vanished'),
'last-run' => {
description => "Last execution time of the job in seconds since the beginning of the UNIX epoch",
description =>
"Last execution time of the job in seconds since the beginning of the UNIX epoch",
type => 'integer',
optional => 1,
},
'next-run' => {
description => "Next planned execution time of the job in seconds since the beginning of the UNIX epoch.",
description =>
"Next planned execution time of the job in seconds since the beginning of the UNIX epoch.",
type => 'integer',
optional => 1,
},
@ -111,7 +113,8 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'read_job',
@ -143,7 +146,8 @@ __PACKAGE__->register_method({
raise_param_exc({ id => "No such job '$id'" });
}});
},
});
__PACKAGE__->register_method({
name => 'create_job',
@ -154,7 +158,8 @@ __PACKAGE__->register_method({
permissions => {
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
. "'User.Modify' permissions to '/access/groups/'.",
check => [ 'and',
check => [
'and',
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
['perm', '/access/groups', ['User.Modify']],
],
@ -166,7 +171,10 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'id');
cfs_lock_file('jobs.cfg', undef, sub {
cfs_lock_file(
'jobs.cfg',
undef,
sub {
my $data = cfs_read_file('jobs.cfg');
die "Job '$id' already exists\n"
@ -188,11 +196,13 @@ __PACKAGE__->register_method({
$data->{ids}->{$id} = $opts;
cfs_write_file('jobs.cfg', $data);
});
},
);
die "$@" if ($@);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'update_job',
@ -203,7 +213,8 @@ __PACKAGE__->register_method({
permissions => {
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and 'User.Modify'"
. " permissions to '/access/groups/'.",
check => [ 'and',
check => [
'and',
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
['perm', '/access/groups', ['User.Modify']],
],
@ -219,7 +230,10 @@ __PACKAGE__->register_method({
die "no job options specified\n" if !scalar(keys %$param);
cfs_lock_file('jobs.cfg', undef, sub {
cfs_lock_file(
'jobs.cfg',
undef,
sub {
my $jobs = cfs_read_file('jobs.cfg');
my $plugin = PVE::Job::Registry->lookup('realm-sync');
@ -236,10 +250,11 @@ __PACKAGE__->register_method({
cfs_write_file('jobs.cfg', $jobs);
return;
});
},
);
die "$@" if ($@);
}});
},
});
__PACKAGE__->register_method({
name => 'delete_job',
@ -265,20 +280,28 @@ __PACKAGE__->register_method({
my $id = $param->{id};
cfs_lock_file('jobs.cfg', undef, sub {
cfs_lock_file(
'jobs.cfg',
undef,
sub {
my $jobs = cfs_read_file('jobs.cfg');
if (!defined($jobs->{ids}->{$id}) || $jobs->{ids}->{$id}->{type} ne 'realm-sync') {
if (
!defined($jobs->{ids}->{$id})
|| $jobs->{ids}->{$id}->{type} ne 'realm-sync'
) {
raise_param_exc({ id => "No such job '$id'" });
}
delete $jobs->{ids}->{$id};
cfs_write_file('jobs.cfg', $jobs);
PVE::Jobs::RealmSync::save_state($id, undef);
});
},
);
die "$@" if $@;
return undef;
}});
},
});
1;

View File

@ -76,10 +76,10 @@ __PACKAGE__->register_method ({
my ($param) = @_;
return [
{ subdir => 'auth-url' },
{ subdir => 'login' },
{ subdir => 'auth-url' }, { subdir => 'login' },
];
}});
},
});
__PACKAGE__->register_method({
name => 'auth_url',
@ -92,7 +92,8 @@ __PACKAGE__->register_method ({
properties => {
realm => get_standard_option('realm'),
'redirect-url' => {
description => "Redirection Url. The client should set this to the used server url (location.origin).",
description =>
"Redirection Url. The client should set this to the used server url (location.origin).",
type => 'string',
maxLength => 255,
},
@ -116,7 +117,8 @@ __PACKAGE__->register_method ({
my $url = $openid->authorize_url($openid_state_path, $realm);
return $url;
}});
},
});
__PACKAGE__->register_method({
name => 'login',
@ -138,7 +140,8 @@ __PACKAGE__->register_method ({
maxLength => 4096,
},
'redirect-url' => {
description => "Redirection Url. The client should set this to the used server url (location.origin).",
description =>
"Redirection Url. The client should set this to the used server url (location.origin).",
type => 'string',
maxLength => 255,
},
@ -164,8 +167,8 @@ __PACKAGE__->register_method ({
my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
local $ENV{all_proxy} = $dcconf->{http_proxy} if exists $dcconf->{http_proxy};
my ($realm, $private_auth_state) = PVE::RS::OpenId::verify_public_auth_state(
$openid_state_path, $param->{'state'});
my ($realm, $private_auth_state) =
PVE::RS::OpenId::verify_public_auth_state($openid_state_path, $param->{'state'});
my $redirect_url = extract_param($param, 'redirect-url');
@ -200,10 +203,12 @@ __PACKAGE__->register_method ({
PVE::Auth::Plugin::verify_username($username);
if ($config->{'autocreate'} && !$rpcenv->check_user_exist($username, 1)) {
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
die "user '$username' already exists\n" if $usercfg->{users}->{$username};
die "user '$username' already exists\n"
if $usercfg->{users}->{$username};
my $entry = { enable => 1 };
if (defined(my $email = $info->{'email'})) {
@ -219,7 +224,9 @@ __PACKAGE__->register_method ({
$usercfg->{users}->{$username} = $entry;
cfs_write_file("user.cfg", $usercfg);
}, "autocreate openid user failed");
},
"autocreate openid user failed",
);
} else {
# test if user exists and is enabled
$rpcenv->check_user_enabled($username);
@ -228,7 +235,8 @@ __PACKAGE__->register_method ({
if (defined(my $groups_claim = $config->{'groups-claim'})) {
if (defined(my $groups_list = $info->{$groups_claim})) {
if (ref($groups_list) eq 'ARRAY') {
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
my $oidc_groups;
@ -240,7 +248,7 @@ __PACKAGE__->register_method ({
# ignore any groups in the list that have invalid characters
syslog(
'warn',
"openid group '$group' contains invalid characters"
"openid group '$group' contains invalid characters",
);
}
}
@ -248,7 +256,8 @@ __PACKAGE__->register_method ({
# get groups that exist in OIDC and PVE
my $groups_intersect;
for my $group (keys %$oidc_groups) {
$groups_intersect->{$group} = 1 if $usercfg->{groups}->{$group};
$groups_intersect->{$group} = 1
if $usercfg->{groups}->{$group};
}
if ($config->{'groups-autocreate'}) {
@ -256,14 +265,16 @@ __PACKAGE__->register_method ({
$groups_intersect = $oidc_groups;
my $groups_to_create;
for my $group (keys %$oidc_groups) {
$groups_to_create->{$group} = 1 if !$usercfg->{groups}->{$group};
$groups_to_create->{$group} = 1
if !$usercfg->{groups}->{$group};
}
if ($groups_to_create) {
# log a messages about created groups here
my $groups_to_create_string = join(', ', sort keys %$groups_to_create);
my $groups_to_create_string =
join(', ', sort keys %$groups_to_create);
syslog(
'info',
"groups created automatically from openid claim: $groups_to_create_string"
"groups created automatically from openid claim: $groups_to_create_string",
);
}
}
@ -271,12 +282,11 @@ __PACKAGE__->register_method ({
# if groups should be overwritten, delete all the users groups first
if ($config->{'groups-overwrite'}) {
PVE::AccessControl::delete_user_group(
$username,
$usercfg,
$username, $usercfg,
);
syslog(
'info',
"openid overwrite groups enabled; user '$username' removed from all groups"
"openid overwrite groups enabled; user '$username' removed from all groups",
);
}
@ -286,21 +296,27 @@ __PACKAGE__->register_method ({
PVE::AccessControl::add_user_group(
$username,
$usercfg,
$group
$group,
);
}
my $groups_intersect_string = join(', ', sort keys %$groups_intersect);
my $groups_intersect_string =
join(', ', sort keys %$groups_intersect);
syslog(
'info',
"openid user '$username' added to groups: $groups_intersect_string"
"openid user '$username' added to groups: $groups_intersect_string",
);
}
cfs_write_file("user.cfg", $usercfg);
}, "openid group mapping failed");
},
"openid group mapping failed",
);
} else {
syslog('err', "openid groups list is not an array; groups will not be updated");
syslog(
'err',
"openid groups list is not an array; groups will not be updated",
);
}
} else {
syslog('err', "openid groups claim '$groups_claim' is not found in claims");
@ -319,7 +335,9 @@ __PACKAGE__->register_method ({
};
my $clinfo = PVE::Cluster::get_clinfo();
if ($clinfo->{cluster}->{name} && $rpcenv->check($username, '/', ['Sys.Audit'], 1)) {
if (
$clinfo->{cluster}->{name} && $rpcenv->check($username, '/', ['Sys.Audit'], 1)
) {
$res->{clustername} = $clinfo->{cluster}->{name};
}
};
@ -330,7 +348,12 @@ __PACKAGE__->register_method ({
die PVE::Exception->new("authentication failure\n", code => 401);
}
PVE::Cluster::log_msg('info', 'root@pam', "successful openid auth for user '$res->{username}'");
PVE::Cluster::log_msg(
'info',
'root@pam',
"successful openid auth for user '$res->{username}'",
);
return $res;
}});
},
});

View File

@ -10,15 +10,21 @@ use PVE::JSONSchema qw(get_standard_option register_standard_option);
use base qw(PVE::RESTHandler);
register_standard_option('role-id', {
register_standard_option(
'role-id',
{
type => 'string',
format => 'pve-roleid',
});
register_standard_option('role-privs', {
},
);
register_standard_option(
'role-privs',
{
type => 'string',
format => 'pve-priv-list',
optional => 1,
});
},
);
__PACKAGE__->register_method({
name => 'index',
@ -53,7 +59,8 @@ __PACKAGE__->register_method ({
foreach my $role (keys %{ $usercfg->{roles} }) {
my $privs = join(',', sort keys %{ $usercfg->{roles}->{$role} });
push @$res, {
push @$res,
{
roleid => $role,
privs => $privs,
special => PVE::AccessControl::role_is_special($role),
@ -61,7 +68,8 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'create_role',
@ -87,11 +95,13 @@ __PACKAGE__->register_method ({
if ($role =~ /^PVE/i) {
raise_param_exc({
roleid => "cannot use role ID starting with the (case-insensitive) 'PVE' namespace",
roleid =>
"cannot use role ID starting with the (case-insensitive) 'PVE' namespace",
});
}
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
die "role '$role' already exists\n" if $usercfg->{roles}->{$role};
@ -101,10 +111,13 @@ __PACKAGE__->register_method ({
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
cfs_write_file("user.cfg", $usercfg);
}, "create role failed");
},
"create role failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'update_role',
@ -132,7 +145,8 @@ __PACKAGE__->register_method ({
die "auto-generated role '$role' cannot be modified\n"
if PVE::AccessControl::role_is_special($role);
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
@ -142,10 +156,13 @@ __PACKAGE__->register_method ({
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
cfs_write_file("user.cfg", $usercfg);
}, "update role failed");
},
"update role failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'read_role',
@ -178,7 +195,7 @@ __PACKAGE__->register_method ({
die "role '$role' does not exist\n" if !$data;
return $data;
}
},
});
__PACKAGE__->register_method({
@ -205,7 +222,8 @@ __PACKAGE__->register_method ({
die "auto-generated role '$role' cannot be deleted\n"
if PVE::AccessControl::role_is_special($role);
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
@ -215,10 +233,12 @@ __PACKAGE__->register_method ({
# fixme: delete role from acl?
cfs_write_file("user.cfg", $usercfg);
}, "delete role failed");
},
"delete role failed",
);
return undef;
}
},
});
1;

View File

@ -23,7 +23,7 @@ our $OPTIONAL_PASSWORD_SCHEMA = {
type => 'string',
optional => 1, # Only required if not root@pam
minLength => 5,
maxLength => 64
maxLength => 64,
};
my $TFA_TYPE_SCHEMA = {
@ -79,7 +79,7 @@ my $TFA_UPDATE_INFO_SCHEMA = {
optional => 1,
description =>
'When adding u2f entries, this contains a challenge the user must respond to in order'
.' to finish the registration.'
. ' to finish the registration.',
},
recovery => {
type => 'array',
@ -89,7 +89,7 @@ my $TFA_UPDATE_INFO_SCHEMA = {
. ' the user',
items => {
type => 'string',
description => 'A recovery entry.'
description => 'A recovery entry.',
},
},
},
@ -100,7 +100,8 @@ my $TFA_UPDATE_INFO_SCHEMA = {
my sub set_user_tfa_enabled : prototype($$$) {
my ($userid, $realm, $tfa_cfg) = @_;
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $user_cfg = cfs_read_file('user.cfg');
my $user = $user_cfg->{users}->{$userid};
my $keys = $user->{keys};
@ -115,11 +116,15 @@ my sub set_user_tfa_enabled : prototype($$$) {
my $realm_tfa = $realm_cfg->{tfa};
$realm_tfa = PVE::Auth::Plugin::parse_tfa_config($realm_tfa) if $realm_tfa;
PVE::AccessControl::add_old_keys_to_realm_tfa($userid, $tfa_cfg, $realm_tfa, $keys);
PVE::AccessControl::add_old_keys_to_realm_tfa(
$userid, $tfa_cfg, $realm_tfa, $keys,
);
}
$user->{keys} = $tfa_cfg ? 'x' : undef;
cfs_write_file("user.cfg", $user_cfg);
}, "enabling TFA for the user failed");
},
"enabling TFA for the user failed",
);
}
__PACKAGE__->register_method({
@ -127,9 +132,8 @@ __PACKAGE__->register_method ({
path => '{userid}',
method => 'GET',
permissions => {
check => [ 'or',
['userid-param', 'self'],
['userid-group', ['User.Modify', 'Sys.Audit']],
check => [
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify', 'Sys.Audit']],
],
},
protected => 1, # else we can't access shadow files
@ -137,10 +141,13 @@ __PACKAGE__->register_method ({
parameters => {
additionalProperties => 0,
properties => {
userid => get_standard_option('userid', {
userid => get_standard_option(
'userid',
{
completion => \&PVE::AccessControl::complete_username,
}),
}
},
),
},
},
returns => {
description => "A list of the user's TFA entries.",
@ -152,16 +159,16 @@ __PACKAGE__->register_method ({
my ($param) = @_;
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
return $tfa_cfg->api_list_user_tfa($param->{userid});
}});
},
});
__PACKAGE__->register_method({
name => 'get_tfa_entry',
path => '{userid}/{id}',
method => 'GET',
permissions => {
check => [ 'or',
['userid-param', 'self'],
['userid-group', ['User.Modify', 'Sys.Audit']],
check => [
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify', 'Sys.Audit']],
],
},
protected => 1, # else we can't access shadow files
@ -169,11 +176,14 @@ __PACKAGE__->register_method ({
parameters => {
additionalProperties => 0,
properties => {
userid => get_standard_option('userid', {
userid => get_standard_option(
'userid',
{
completion => \&PVE::AccessControl::complete_username,
}),
},
),
id => $TFA_ID_SCHEMA,
}
},
},
returns => $TYPED_TFA_ENTRY_SCHEMA,
code => sub {
@ -183,16 +193,16 @@ __PACKAGE__->register_method ({
my $entry = $tfa_cfg->api_get_tfa_entry($param->{userid}, $id);
raise("No such tfa entry '$id'", code => HTTP::Status::HTTP_NOT_FOUND) if !$entry;
return $entry;
}});
},
});
__PACKAGE__->register_method({
name => 'delete_tfa',
path => '{userid}/{id}',
method => 'DELETE',
permissions => {
check => [ 'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
check => [
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
protected => 1, # else we can't access shadow files
@ -201,12 +211,15 @@ __PACKAGE__->register_method ({
parameters => {
additionalProperties => 0,
properties => {
userid => get_standard_option('userid', {
userid => get_standard_option(
'userid',
{
completion => \&PVE::AccessControl::complete_username,
}),
},
),
id => $TFA_ID_SCHEMA,
password => $OPTIONAL_PASSWORD_SCHEMA,
}
},
},
returns => { type => 'null' },
code => sub {
@ -215,9 +228,7 @@ __PACKAGE__->register_method ({
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $userid = $rpcenv->reauth_user_for_user_modification(
$authuser,
$param->{userid},
$param->{password},
$authuser, $param->{userid}, $param->{password},
);
my $has_entries_left = PVE::AccessControl::lock_tfa_config(sub {
@ -229,7 +240,8 @@ __PACKAGE__->register_method ({
if (!$has_entries_left) {
set_user_tfa_enabled($userid, undef, undef);
}
}});
},
});
__PACKAGE__->register_method({
name => 'list_tfa',
@ -243,7 +255,7 @@ __PACKAGE__->register_method ({
description => 'List TFA configurations of users.',
parameters => {
additionalProperties => 0,
properties => {}
properties => {},
},
returns => {
description => "The list tuples of user and TFA entries.",
@ -297,16 +309,16 @@ __PACKAGE__->register_method ({
$userid eq $authuser || $allowed_users->{$userid}
} $entries->@*
];
}});
},
});
__PACKAGE__->register_method({
name => 'add_tfa_entry',
path => '{userid}',
method => 'POST',
permissions => {
check => [ 'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
check => [
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
protected => 1, # else we can't access shadow files
@ -315,9 +327,12 @@ __PACKAGE__->register_method ({
parameters => {
additionalProperties => 0,
properties => {
userid => get_standard_option('userid', {
userid => get_standard_option(
'userid',
{
completion => \&PVE::AccessControl::complete_username,
}),
},
),
type => $TFA_TYPE_SCHEMA,
description => {
type => 'string',
@ -332,14 +347,14 @@ __PACKAGE__->register_method ({
},
value => {
type => 'string',
description =>
'The current value for the provided totp URI, or a Webauthn/U2F'
description => 'The current value for the provided totp URI, or a Webauthn/U2F'
. ' challenge response',
optional => 1,
},
challenge => {
type => 'string',
description => 'When responding to a u2f challenge: the original challenge string',
description =>
'When responding to a u2f challenge: the original challenge string',
optional => 1,
},
password => $OPTIONAL_PASSWORD_SCHEMA,
@ -352,9 +367,7 @@ __PACKAGE__->register_method ({
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my ($userid, undef, $realm) = $rpcenv->reauth_user_for_user_modification(
$authuser,
$param->{userid},
$param->{password},
$authuser, $param->{userid}, $param->{password},
);
my $type = delete $param->{type};
@ -383,7 +396,8 @@ __PACKAGE__->register_method ({
return $response;
});
}});
},
});
sub validate_yubico_otp : prototype($$$) {
my ($userid, $realm, $value) = @_;
@ -412,9 +426,8 @@ __PACKAGE__->register_method ({
path => '{userid}/{id}',
method => 'PUT',
permissions => {
check => [ 'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
check => [
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
protected => 1, # else we can't access shadow files
@ -423,9 +436,12 @@ __PACKAGE__->register_method ({
parameters => {
additionalProperties => 0,
properties => {
userid => get_standard_option('userid', {
userid => get_standard_option(
'userid',
{
completion => \&PVE::AccessControl::complete_username,
}),
},
),
id => $TFA_ID_SCHEMA,
description => {
type => 'string',
@ -448,23 +464,19 @@ __PACKAGE__->register_method ({
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $userid = $rpcenv->reauth_user_for_user_modification(
$authuser,
$param->{userid},
$param->{password},
$authuser, $param->{userid}, $param->{password},
);
PVE::AccessControl::lock_tfa_config(sub {
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
$tfa_cfg->api_update_tfa_entry(
$userid,
$param->{id},
$param->{description},
$param->{enable},
$userid, $param->{id}, $param->{description}, $param->{enable},
);
cfs_write_file('priv/tfa.cfg', $tfa_cfg);
});
}});
},
});
1;

View File

@ -17,69 +17,104 @@ use PVE::RESTHandler;
use base qw(PVE::RESTHandler);
register_standard_option('user-enable', {
description => "Enable the account (default). You can set this to '0' to disable the account",
register_standard_option(
'user-enable',
{
description =>
"Enable the account (default). You can set this to '0' to disable the account",
type => 'boolean',
optional => 1,
default => 1,
});
register_standard_option('user-expire', {
description => "Account expiration date (seconds since epoch). '0' means no expiration date.",
},
);
register_standard_option(
'user-expire',
{
description =>
"Account expiration date (seconds since epoch). '0' means no expiration date.",
type => 'integer',
minimum => 0,
optional => 1,
});
register_standard_option('user-firstname', { type => 'string', optional => 1, maxLength => 1024, });
register_standard_option('user-lastname', { type => 'string', optional => 1, maxLength => 1024, });
register_standard_option('user-email', {
},
);
register_standard_option('user-firstname', { type => 'string', optional => 1, maxLength => 1024 });
register_standard_option('user-lastname', { type => 'string', optional => 1, maxLength => 1024 });
register_standard_option(
'user-email',
{
type => 'string',
optional => 1,
format => 'email-opt',
maxLength => 254, # 256 including punctuation and separator is the max path as per RFC 5321
});
register_standard_option('user-comment', {
},
);
register_standard_option(
'user-comment',
{
type => 'string',
optional => 1,
maxLength => 2048,
});
register_standard_option('user-keys', {
},
);
register_standard_option(
'user-keys',
{
description => "Keys for two factor auth (yubico).",
type => 'string',
pattern => '[0-9a-zA-Z!=]{0,4096}',
optional => 1,
});
register_standard_option('group-list', {
type => 'string', format => 'pve-groupid-list',
},
);
register_standard_option(
'group-list',
{
type => 'string',
format => 'pve-groupid-list',
optional => 1,
completion => \&PVE::AccessControl::complete_group,
});
register_standard_option('token-subid', {
},
);
register_standard_option(
'token-subid',
{
type => 'string',
pattern => $PVE::AccessControl::token_subid_regex,
description => 'User-specific token identifier.',
});
register_standard_option('token-expire', {
description => "API token expiration date (seconds since epoch). '0' means no expiration date.",
},
);
register_standard_option(
'token-expire',
{
description =>
"API token expiration date (seconds since epoch). '0' means no expiration date.",
type => 'integer',
minimum => 0,
optional => 1,
default => 'same as user',
});
register_standard_option('token-privsep', {
description => "Restrict API token privileges with separate ACLs (default), or give full privileges of corresponding user.",
},
);
register_standard_option(
'token-privsep',
{
description =>
"Restrict API token privileges with separate ACLs (default), or give full privileges of corresponding user.",
type => 'boolean',
optional => 1,
default => 1,
});
},
);
register_standard_option('token-comment', { type => 'string', optional => 1 });
register_standard_option('token-info', {
register_standard_option(
'token-info',
{
type => 'object',
properties => {
expire => get_standard_option('token-expire'),
privsep => get_standard_option('token-privsep'),
comment => get_standard_option('token-comment'),
}
});
},
},
);
my $token_info_extend = sub {
my ($props) = @_;
@ -122,7 +157,8 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "User index.",
permissions => {
description => "The returned list is restricted to users where you have 'User.Modify' or 'Sys.Audit' permissions on '/access/groups' or on a group the user belongs too. But it always includes the current (authenticated) user.",
description =>
"The returned list is restricted to users where you have 'User.Modify' or 'Sys.Audit' permissions on '/access/groups' or on a group the user belongs too. But it always includes the current (authenticated) user.",
user => 'all',
},
protected => 1, # to access priv/tfa.cfg
@ -139,7 +175,7 @@ __PACKAGE__->register_method ({
description => "Include group and token information.",
optional => 1,
default => 0,
}
},
},
},
returns => {
@ -164,7 +200,8 @@ __PACKAGE__->register_method ({
}),
},
'realm-type' => {
type => 'string', format => 'pve-realm',
type => 'string',
format => 'pve-realm',
description => 'The type of the users realm',
optional => 1, # it should always be there, but we use conditional code below, so..
},
@ -217,14 +254,14 @@ __PACKAGE__->register_method ({
$entry->{groups} = join(',', @{ $entry->{groups} }) if $entry->{groups};
if (defined(my $tokens = $entry->{tokens})) {
$entry->{tokens} = [
map { { tokenid => $_, %{$tokens->{$_}} } } sort keys %$tokens
];
$entry->{tokens} =
[map { { tokenid => $_, %{ $tokens->{$_} } } } sort keys %$tokens];
}
if ($user =~ /($PVE::Auth::Plugin::realm_regex)$/) {
my $realm = $1;
$entry->{'realm-type'} = $domainids->{$realm}->{type} if exists $domainids->{$realm};
$entry->{'realm-type'} = $domainids->{$realm}->{type}
if exists $domainids->{$realm};
}
$entry->{userid} = $user;
@ -241,7 +278,8 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'create_user',
@ -249,7 +287,8 @@ __PACKAGE__->register_method ({
path => '',
method => 'POST',
permissions => {
description => "You need 'Realm.AllocateUser' on '/access/realm/<realm>' on the realm of user <userid>, and 'User.Modify' permissions to '/access/groups/<group>' for any group specified (or 'User.Modify' on '/access/groups' if you pass no groups.",
description =>
"You need 'Realm.AllocateUser' on '/access/realm/<realm>' on the realm of user <userid>, and 'User.Modify' permissions to '/access/groups/<group>' for any group specified (or 'User.Modify' on '/access/groups' if you pass no groups.",
check => [
'and',
['userid-param', 'Realm.AllocateUser'],
@ -273,7 +312,7 @@ __PACKAGE__->register_method ({
type => 'string',
optional => 1,
minLength => 8,
maxLength => 64
maxLength => 64,
},
groups => get_standard_option('group-list'),
},
@ -282,8 +321,10 @@ __PACKAGE__->register_method ({
code => sub {
my ($param) = @_;
PVE::AccessControl::lock_user_config(sub {
my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
PVE::AccessControl::lock_user_config(
sub {
my ($username, $ruid, $realm) =
PVE::AccessControl::verify_username($param->{userid});
my $usercfg = cfs_read_file("user.cfg");
@ -308,17 +349,23 @@ __PACKAGE__->register_method ({
}
}
$usercfg->{users}->{$username}->{firstname} = $param->{firstname} if $param->{firstname};
$usercfg->{users}->{$username}->{lastname} = $param->{lastname} if $param->{lastname};
$usercfg->{users}->{$username}->{firstname} = $param->{firstname}
if $param->{firstname};
$usercfg->{users}->{$username}->{lastname} = $param->{lastname}
if $param->{lastname};
$usercfg->{users}->{$username}->{email} = $param->{email} if $param->{email};
$usercfg->{users}->{$username}->{comment} = $param->{comment} if $param->{comment};
$usercfg->{users}->{$username}->{comment} = $param->{comment}
if $param->{comment};
$usercfg->{users}->{$username}->{keys} = $param->{keys} if $param->{keys};
cfs_write_file("user.cfg", $usercfg);
}, "create user failed");
},
"create user failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'read_user',
@ -358,7 +405,7 @@ __PACKAGE__->register_method ({
additionalProperties => get_standard_option('token-info'),
},
},
type => "object"
type => "object",
},
code => sub {
my ($param) = @_;
@ -370,7 +417,8 @@ __PACKAGE__->register_method ({
my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
return &$extract_user_data($data, 1);
}});
},
});
__PACKAGE__->register_method({
name => 'update_user',
@ -406,13 +454,16 @@ __PACKAGE__->register_method ({
my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
PVE::AccessControl::check_user_exist($usercfg, $username);
$usercfg->{users}->{$username}->{enable} = $param->{enable} if defined($param->{enable});
$usercfg->{users}->{$username}->{expire} = $param->{expire} if defined($param->{expire});
$usercfg->{users}->{$username}->{enable} = $param->{enable}
if defined($param->{enable});
$usercfg->{users}->{$username}->{expire} = $param->{expire}
if defined($param->{expire});
PVE::AccessControl::delete_user_group($username, $usercfg)
if (!$param->{append} && defined($param->{groups}));
@ -427,17 +478,25 @@ __PACKAGE__->register_method ({
}
}
$usercfg->{users}->{$username}->{firstname} = $param->{firstname} if defined($param->{firstname});
$usercfg->{users}->{$username}->{lastname} = $param->{lastname} if defined($param->{lastname});
$usercfg->{users}->{$username}->{email} = $param->{email} if defined($param->{email});
$usercfg->{users}->{$username}->{comment} = $param->{comment} if defined($param->{comment});
$usercfg->{users}->{$username}->{keys} = $param->{keys} if defined($param->{keys});
$usercfg->{users}->{$username}->{firstname} = $param->{firstname}
if defined($param->{firstname});
$usercfg->{users}->{$username}->{lastname} = $param->{lastname}
if defined($param->{lastname});
$usercfg->{users}->{$username}->{email} = $param->{email}
if defined($param->{email});
$usercfg->{users}->{$username}->{comment} = $param->{comment}
if defined($param->{comment});
$usercfg->{users}->{$username}->{keys} = $param->{keys}
if defined($param->{keys});
cfs_write_file("user.cfg", $usercfg);
}, "update user failed");
},
"update user failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'delete_user',
@ -446,16 +505,15 @@ __PACKAGE__->register_method ({
method => 'DELETE',
description => "Delete user.",
permissions => {
check => [ 'and',
[ 'userid-param', 'Realm.AllocateUser'],
[ 'userid-group', ['User.Modify']],
check => [
'and', ['userid-param', 'Realm.AllocateUser'], ['userid-group', ['User.Modify']],
],
},
parameters => {
additionalProperties => 0,
properties => {
userid => get_standard_option('userid-completed'),
}
},
},
returns => { type => 'null' },
code => sub {
@ -466,7 +524,8 @@ __PACKAGE__->register_method ({
my ($userid, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
# NOTE: disable the user first (transaction like), so if (e.g.) we fail in the middle of
@ -497,10 +556,13 @@ __PACKAGE__->register_method ({
cfs_write_file("user.cfg", $usercfg);
};
die "$@$partial_deletion\n" if $@;
}, "delete user failed");
},
"delete user failed",
);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'read_user_tfa_type',
@ -509,9 +571,8 @@ __PACKAGE__->register_method ({
protected => 1,
description => "Get user TFA types (Personal and Realm).",
permissions => {
check => [ 'or',
['userid-param', 'self'],
['userid-group', ['User.Modify', 'Sys.Audit']],
check => [
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify', 'Sys.Audit']],
],
},
parameters => {
@ -538,15 +599,13 @@ __PACKAGE__->register_method ({
user => {
type => 'string',
enum => [qw(oath u2f)],
description =>
"The type of TFA the user has set, if any."
description => "The type of TFA the user has set, if any."
. " Only set if 'multiple' was not passed.",
optional => 1,
},
types => {
type => 'array',
description =>
"Array of the user configured TFA types, if any."
description => "Array of the user configured TFA types, if any."
. " Only available if 'multiple' was not passed.",
optional => 1,
items => {
@ -556,7 +615,7 @@ __PACKAGE__->register_method ({
},
},
},
type => "object"
type => "object",
},
code => sub {
my ($param) = @_;
@ -569,7 +628,8 @@ __PACKAGE__->register_method ({
my $res = {};
my $realm_tfa = {};
$realm_tfa = PVE::Auth::Plugin::parse_tfa_config($realm_cfg->{tfa}) if $realm_cfg->{tfa};
$realm_tfa = PVE::Auth::Plugin::parse_tfa_config($realm_cfg->{tfa})
if $realm_cfg->{tfa};
$res->{realm} = $realm_tfa->{type} if $realm_tfa->{type};
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
@ -586,7 +646,8 @@ __PACKAGE__->register_method ({
$res->{user} = $tfa->{type} if $tfa->{type};
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'unlock_tfa',
@ -618,7 +679,8 @@ __PACKAGE__->register_method ({
});
return $user_was_locked;
}});
},
});
__PACKAGE__->register_method({
name => 'token_index',
@ -627,9 +689,7 @@ __PACKAGE__->register_method ({
description => "Get user API tokens.",
permissions => {
check => [
'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
parameters => {
@ -655,7 +715,8 @@ __PACKAGE__->register_method ({
my $tokens = $user->{tokens} // {};
return [map { $tokens->{$_}->{tokenid} = $_; $tokens->{$_} } keys %$tokens];
}});
},
});
__PACKAGE__->register_method({
name => 'read_token',
@ -664,9 +725,7 @@ __PACKAGE__->register_method ({
description => "Get specific API token information.",
permissions => {
check => [
'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
parameters => {
@ -686,19 +745,19 @@ __PACKAGE__->register_method ({
my $usercfg = cfs_read_file("user.cfg");
return PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
}});
},
});
__PACKAGE__->register_method({
name => 'generate_token',
path => '{userid}/token/{tokenid}',
method => 'POST',
description => "Generate a new API token for a specific user. NOTE: returns API token value, which needs to be stored as it cannot be retrieved afterwards!",
description =>
"Generate a new API token for a specific user. NOTE: returns API token value, which needs to be stored as it cannot be retrieved afterwards!",
protected => 1,
permissions => {
check => [
'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
parameters => {
@ -744,7 +803,8 @@ __PACKAGE__->register_method ({
my $generate_and_add_token = sub {
$usercfg = cfs_read_file("user.cfg");
PVE::AccessControl::check_user_exist($usercfg, $userid);
die "Token already exists.\n" if defined(PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid, 1));
die "Token already exists.\n"
if defined(PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid, 1));
$full_tokenid = PVE::AccessControl::join_tokenid($userid, $tokenid);
$value = PVE::TokenConfig::generate_token($full_tokenid);
@ -758,15 +818,16 @@ __PACKAGE__->register_method ({
cfs_write_file("user.cfg", $usercfg);
};
PVE::AccessControl::lock_user_config($generate_and_add_token, 'generating token failed');
PVE::AccessControl::lock_user_config($generate_and_add_token,
'generating token failed');
return {
info => $token,
value => $value,
'full-tokenid' => $full_tokenid,
};
}});
},
});
__PACKAGE__->register_method({
name => 'update_token_info',
@ -776,9 +837,7 @@ __PACKAGE__->register_method ({
protected => 1,
permissions => {
check => [
'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
parameters => {
@ -791,7 +850,8 @@ __PACKAGE__->register_method ({
comment => get_standard_option('token-comment'),
},
},
returns => get_standard_option('token-info', { description => "Updated token information." }),
returns =>
get_standard_option('token-info', { description => "Updated token information." }),
code => sub {
my ($param) = @_;
@ -801,7 +861,8 @@ __PACKAGE__->register_method ({
my $usercfg = cfs_read_file("user.cfg");
my $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
$usercfg = cfs_read_file("user.cfg");
$token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
@ -813,11 +874,13 @@ __PACKAGE__->register_method ({
$usercfg->{users}->{$userid}->{tokens}->{$tokenid} = $token;
cfs_write_file("user.cfg", $usercfg);
}, 'updating token info failed');
},
'updating token info failed',
);
return $token;
}});
},
});
__PACKAGE__->register_method({
name => 'remove_token',
@ -827,9 +890,7 @@ __PACKAGE__->register_method ({
protected => 1,
permissions => {
check => [
'or',
['userid-param', 'self'],
['userid-group', ['User.Modify']],
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
],
},
parameters => {
@ -849,7 +910,8 @@ __PACKAGE__->register_method ({
my $usercfg = cfs_read_file("user.cfg");
my $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
$usercfg = cfs_read_file("user.cfg");
PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
@ -859,8 +921,11 @@ __PACKAGE__->register_method ({
delete $usercfg->{users}->{$userid}->{tokens}->{$tokenid};
cfs_write_file("user.cfg", $usercfg);
}, 'deleting token failed');
},
'deleting token failed',
);
return;
}});
},
});
1;

View File

@ -77,6 +77,7 @@ sub pve_verify_realm {
# 2) user config
# If we permit the other way round, too, we might end up deadlocking!
my $user_config_locked;
sub lock_user_config {
my ($code, $errmsg) = @_;
@ -179,7 +180,7 @@ sub check_authkey {
} else {
my $now = time();
if ($now - $mtime >= $authkey_lifetime) {
warn "auth key pair too old, rotating..\n" if !$quiet;;
warn "auth key pair too old, rotating..\n" if !$quiet;
return 0;
} elsif ($mtime > $now + $auth_graceperiod) {
# a nodes RTC had a time set in the future during key generation -> ticket
@ -188,13 +189,15 @@ sub check_authkey {
if ($old_mtime && $mtime >= $old_mtime && $mtime - $old_mtime < $ticket_lifetime) {
warn "auth key pair generated in the future (key $mtime > host $now),"
. " but old key still exists and in valid grace period so avoid automatic"
." fixup. Cluster time not in sync?\n" if !$quiet;
. " fixup. Cluster time not in sync?\n"
if !$quiet;
return 1;
}
warn "auth key pair generated in the future (key $mtime > host $now), rotating..\n" if !$quiet;
warn "auth key pair generated in the future (key $mtime > host $now), rotating..\n"
if !$quiet;
return 0;
} else {
warn "auth key new enough, skipping rotation\n" if !$quiet;;
warn "auth key new enough, skipping rotation\n" if !$quiet;
return 1;
}
}
@ -203,7 +206,9 @@ sub check_authkey {
sub rotate_authkey {
return if $authkey_lifetime == 0;
PVE::Cluster::cfs_lock_authkey(undef, sub {
PVE::Cluster::cfs_lock_authkey(
undef,
sub {
# stat() calls might be answered from the kernel page cache for up to
# 1s, so this special dance is needed to avoid a double rotation in
# clusters *despite* the cfs_lock context..
@ -258,22 +263,28 @@ sub rotate_authkey {
unlink $pve_auth_key_files->{pub};
unlink $pve_auth_key_files->{priv};
}
});
},
);
die $@ if $@;
}
PVE::JSONSchema::register_standard_option('tokenid', {
PVE::JSONSchema::register_standard_option(
'tokenid',
{
description => "API token identifier.",
type => "string",
format => "pve-tokenid",
});
},
);
our $token_subid_regex = $PVE::Auth::Plugin::realm_regex;
# username@realm username realm tokenid
our $token_full_regex = qr/((${PVE::Auth::Plugin::user_regex})\@(${PVE::Auth::Plugin::realm_regex}))!(${token_subid_regex})/;
our $token_full_regex =
qr/((${PVE::Auth::Plugin::user_regex})\@(${PVE::Auth::Plugin::realm_regex}))!(${token_subid_regex})/;
our $userid_or_token_regex = qr/^$PVE::Auth::Plugin::user_regex\@$PVE::Auth::Plugin::realm_regex(?:!$token_subid_regex)?$/;
our $userid_or_token_regex =
qr/^$PVE::Auth::Plugin::user_regex\@$PVE::Auth::Plugin::realm_regex(?:!$token_subid_regex)?$/;
sub split_tokenid {
my ($tokenid, $noerr) = @_;
@ -282,7 +293,8 @@ sub split_tokenid {
return ($1, $4);
}
die "'$tokenid' is not a valid token ID - not able to split into user and token parts\n" if !$noerr;
die "'$tokenid' is not a valid token ID - not able to split into user and token parts\n"
if !$noerr;
return undef;
}
@ -296,6 +308,7 @@ sub join_tokenid {
}
PVE::JSONSchema::register_format('pve-tokenid', \&pve_verify_tokenid);
sub pve_verify_tokenid {
my ($tokenid, $noerr) = @_;
@ -308,7 +321,6 @@ sub pve_verify_tokenid {
return undef;
}
my $csrf_prevention_secret;
my $csrf_prevention_secret_legacy;
my $get_csrfr_secret = sub {
@ -343,7 +355,8 @@ sub verify_csrf_prevention_token {
}
return PVE::Ticket::verify_csrf_prevention_token(
$secret, $username, $token, -$auth_graceperiod, $ticket_lifetime, $noerr);
$secret, $username, $token, -$auth_graceperiod, $ticket_lifetime, $noerr,
);
}
my $get_ticket_age_range = sub {
@ -404,8 +417,8 @@ sub verify_ticket : prototype($;$$) {
my ($min, $max) = $get_ticket_age_range->($now, $rsa_mtime, $old);
return undef if !defined($min);
return PVE::Ticket::verify_rsa_ticket(
$rsa_pub, 'PVE', $ticket, $tfa_ticket_aad, $min, $max, 1);
return PVE::Ticket::verify_rsa_ticket($rsa_pub, 'PVE', $ticket, $tfa_ticket_aad, $min,
$max, 1);
};
my ($data, $age) = $check->();
@ -494,7 +507,8 @@ sub verify_token {
my $token_info = $user->{tokens}->{$token};
my $ctime = time();
die "token '$token' access expired\n" if $token_info->{expire} && ($token_info->{expire} < $ctime);
die "token '$token' access expired\n"
if $token_info->{expire} && ($token_info->{expire} < $ctime);
die "invalid token value!\n" if !PVE::Cluster::verify_token($tokenid, $value);
@ -512,8 +526,7 @@ my $assemble_short_lived_ticket = sub {
my $secret_data = "$username:$path";
return PVE::Ticket::assemble_rsa_ticket(
$rsa_priv, $prefix, undef, $secret_data);
return PVE::Ticket::assemble_rsa_ticket($rsa_priv, $prefix, undef, $secret_data);
};
my $verify_short_lived_ticket = sub {
@ -535,8 +548,8 @@ my $verify_short_lived_ticket = sub {
}
}
return PVE::Ticket::verify_rsa_ticket(
$rsa_pub, $prefix, $ticket, $secret_data, -20, 40, $noerr);
return PVE::Ticket::verify_rsa_ticket($rsa_pub, $prefix, $ticket, $secret_data, -20, 40,
$noerr);
};
# VNC tickets
@ -574,8 +587,7 @@ sub assemble_spice_ticket {
my $secret = &$get_csrfr_secret();
return PVE::Ticket::assemble_spice_ticket(
$secret, $username, $vmid, $node);
return PVE::Ticket::assemble_spice_ticket($secret, $username, $vmid, $node);
}
sub verify_spice_connect_url {
@ -707,8 +719,9 @@ sub verify_one_time_pw {
my $proxy;
if ($type eq 'yubico') {
PVE::OTP::yubico_verify_otp($otp, $keys, $tfa_cfg->{url},
$tfa_cfg->{id}, $tfa_cfg->{key}, $proxy);
PVE::OTP::yubico_verify_otp(
$otp, $keys, $tfa_cfg->{url}, $tfa_cfg->{id}, $tfa_cfg->{key}, $proxy,
);
} elsif ($type eq 'oath') {
PVE::OTP::oath_verify_otp($otp, $keys, $tfa_cfg->{step}, $tfa_cfg->{digits});
} else {
@ -816,7 +829,8 @@ sub authenticate_2nd_new_do : prototype($$$$) {
if (defined($tfa_response)) {
if (defined($tfa_challenge)) {
$tfa_done = 1;
$result = $tfa_cfg->authentication_verify2($username, $tfa_challenge, $tfa_response);
$result =
$tfa_cfg->authentication_verify2($username, $tfa_challenge, $tfa_response);
} else {
die "no such challenge\n";
}
@ -1069,8 +1083,7 @@ my $privgroups = {
'Sys.AccessNetwork', # for, e.g., downloading ISOs from any URL
],
admin => [
'Sys.Console',
'Sys.Syslog',
'Sys.Console', 'Sys.Syslog',
],
user => [],
audit => [
@ -1080,8 +1093,7 @@ my $privgroups = {
Datastore => {
root => [],
admin => [
'Datastore.Allocate',
'Datastore.AllocateTemplate',
'Datastore.Allocate', 'Datastore.AllocateTemplate',
],
user => [
'Datastore.AllocateSpace',
@ -1093,8 +1105,7 @@ my $privgroups = {
SDN => {
root => [],
admin => [
'SDN.Allocate',
'SDN.Audit',
'SDN.Allocate', 'SDN.Audit',
],
user => [
'SDN.Use',
@ -1179,7 +1190,7 @@ sub create_roles {
delete $special_roles->{"PVEAdmin"}->{"Mapping.Modify"};
$special_roles->{"PVETemplateUser"} = { 'VM.Clone' => 1, 'VM.Audit' => 1 };
};
}
create_roles();
@ -1228,7 +1239,8 @@ sub lookup_username {
my $usercfg = cfs_read_file('user.cfg');
my @matches = grep { lc $username eq lc $_ } (keys %{ $usercfg->{users} });
die "ambiguous case insensitive match of username '$username', cannot safely grant access!\n"
die
"ambiguous case insensitive match of username '$username', cannot safely grant access!\n"
if scalar @matches > 1 && !$noerr;
return $matches[0] if defined($matches[0]);
@ -1290,6 +1302,7 @@ sub check_path {
}
PVE::JSONSchema::register_format('pve-groupid', \&verify_groupname);
sub verify_groupname {
my ($groupname, $noerr) = @_;
@ -1304,6 +1317,7 @@ sub verify_groupname {
}
PVE::JSONSchema::register_format('pve-roleid', \&verify_rolename);
sub verify_rolename {
my ($rolename, $noerr) = @_;
@ -1318,6 +1332,7 @@ sub verify_rolename {
}
PVE::JSONSchema::register_format('pve-poolid', \&verify_poolname);
sub verify_poolname {
my ($poolname, $noerr) = @_;
@ -1338,6 +1353,7 @@ sub verify_poolname {
}
PVE::JSONSchema::register_format('pve-priv', \&verify_privname);
sub verify_privname {
my ($priv, $noerr) = @_;
@ -1383,7 +1399,7 @@ sub parse_user_config {
foreach my $d (split(/:/, $line)) {
$d =~ s/^\s+//;
$d =~ s/\s+$//;
push @data, $d
push @data, $d;
}
my $et = shift @data;
@ -1402,7 +1418,8 @@ sub parse_user_config {
$expire = 0 if !$expire;
if ($expire !~ m/^\d+$/) {
warn "user config - ignore user '$user' - (illegal characters in expire '$expire')\n";
warn
"user config - ignore user '$user' - (illegal characters in expire '$expire')\n";
next;
}
$expire = int($expire);
@ -1510,7 +1527,8 @@ sub parse_user_config {
$acl_node->{users}->{$ug}->{$role} = $propagate;
} elsif (my ($user, $token) = split_tokenid($ug, 1)) {
if (check_token_exist($cfg, $user, $token, 1)) {
$acl_node = find_acl_tree_node($cfg->{acl_root}, $path) if !$acl_node;
$acl_node = find_acl_tree_node($cfg->{acl_root}, $path)
if !$acl_node;
$acl_node->{tokens}->{$ug}->{$role} = $propagate;
} else {
warn "user config - ignore invalid acl token '$ug'\n";
@ -1589,7 +1607,8 @@ sub parse_user_config {
$expire = 0 if !$expire;
if ($expire !~ m/^\d+$/) {
warn "user config - ignore token '$tokenid' - (illegal characters in expire '$expire')\n";
warn
"user config - ignore token '$tokenid' - (illegal characters in expire '$expire')\n";
next;
}
$expire = int($expire);
@ -1694,7 +1713,10 @@ sub write_user_config {
}
};
iterate_acl_tree("/", $cfg->{acl_root}, sub {
iterate_acl_tree(
"/",
$cfg->{acl_root},
sub {
my ($path, $d) = @_;
my $rolelist_members = {};
@ -1714,7 +1736,8 @@ sub write_user_config {
}
}
});
},
);
return $data;
}
@ -1896,8 +1919,10 @@ sub remove_storage_access {
cfs_write_file("user.cfg", $usercfg) if $modified;
};
lock_user_config($deleteStorageAccessFn,
"access permissions cleanup for storage $storeid failed");
lock_user_config(
$deleteStorageAccessFn,
"access permissions cleanup for storage $storeid failed",
);
}
sub add_vm_to_pool {
@ -2044,7 +2069,8 @@ sub user_get_tfa : prototype($$$) {
# bash completion helpers
register_standard_option('userid-completed',
register_standard_option(
'userid-completed',
get_standard_option('userid', { completion => \&complete_username }),
);

View File

@ -32,7 +32,8 @@ sub properties {
optional => 1,
},
sslversion => {
description => "LDAPS TLS/SSL version. It's not recommended to use version older than 1.2!",
description =>
"LDAPS TLS/SSL version. It's not recommended to use version older than 1.2!",
type => 'string',
enum => [qw(tlsv1 tlsv1_1 tlsv1_2 tlsv1_3)],
optional => 1,
@ -74,7 +75,7 @@ sub options {
port => { optional => 1 },
secure => { optional => 1 },
sslversion => { optional => 1 },
default => { optional => 1 },,
default => { optional => 1 },
comment => { optional => 1 },
tfa => { optional => 1 },
verify => { optional => 1 },

View File

@ -36,7 +36,8 @@ sub properties {
maxLength => 256,
},
password => {
description => "LDAP bind password. Will be stored in '/etc/pve/priv/realm/<REALM>.pw'.",
description =>
"LDAP bind password. Will be stored in '/etc/pve/priv/realm/<REALM>.pw'.",
type => 'string',
optional => 1,
},
@ -306,7 +307,8 @@ sub get_users {
$config->{user_classes} //= 'inetorgperson, posixaccount, person, user';
my $classes = [PVE::Tools::split_list($config->{user_classes})];
my $users = PVE::LDAP::query_users($ldap, $filter, [keys %$ldap_attribute_map], $basedn, $classes);
my $users =
PVE::LDAP::query_users($ldap, $filter, [keys %$ldap_attribute_map], $basedn, $classes);
my $ret = {};
my $dnmap = {};
@ -395,7 +397,8 @@ sub authenticate_user {
my $ldap = $class->connect_and_bind($config, $realm);
my $user_dn = PVE::LDAP::get_user_dn($ldap, $username, $config->{user_attr}, $config->{base_dn});
my $user_dn =
PVE::LDAP::get_user_dn($ldap, $username, $config->{user_attr}, $config->{base_dn});
PVE::LDAP::auth_user_dn($ldap, $user_dn, $password);
$ldap->unbind();

View File

@ -79,7 +79,8 @@ sub properties {
optional => 1,
},
'acr-values' => {
description => "Specifies the Authentication Context Class Reference values that the"
description =>
"Specifies the Authentication Context Class Reference values that the"
. "Authorization Server is being requested to use for the Auth Request.",
type => 'string',
pattern => '^[^\x00-\x1F\x7F <>#"]*$', # Prohibit characters not allowed in URI RFC 2396.
@ -119,5 +120,4 @@ sub authenticate_user {
die "OpenID realm does not allow password verification.\n";
}
1;

View File

@ -27,7 +27,10 @@ sub authenticate_user {
# user (www-data) need to be able to read /etc/passwd /etc/shadow
die "no password\n" if !$password;
my $pamh = Authen::PAM->new('proxmox-ve-auth', $username, sub {
my $pamh = Authen::PAM->new(
'proxmox-ve-auth',
$username,
sub {
my @res;
while (@_) {
my $msg_type = shift;
@ -36,7 +39,8 @@ sub authenticate_user {
}
push @res, 0;
return @res;
});
},
);
if (!ref($pamh)) {
my $err = $pamh->pam_strerror($pamh);
@ -66,7 +70,6 @@ sub authenticate_user {
return 1;
}
sub store_password {
my ($class, $config, $realm, $username, $password) = @_;

View File

@ -12,9 +12,7 @@ use base qw(PVE::Auth::Plugin);
my $shadowconfigfile = "priv/shadow.cfg";
cfs_register_file($shadowconfigfile,
\&parse_shadow_passwd,
\&write_shadow_config);
cfs_register_file($shadowconfigfile, \&parse_shadow_passwd, \&write_shadow_config);
sub parse_shadow_passwd {
my ($filename, $raw) = @_;
@ -47,7 +45,7 @@ sub write_shadow_config {
$data .= "$userid:$crypt_pass:\n";
}
return $data
return $data;
}
sub lock_shadow_config {
@ -80,8 +78,8 @@ sub authenticate_user {
my $shadow_cfg = cfs_read_file($shadowconfigfile);
if ($shadow_cfg->{users}->{$username}) {
my $encpw = crypt(Encode::encode('utf8', $password),
$shadow_cfg->{users}->{$username}->{shadow});
my $encpw =
crypt(Encode::encode('utf8', $password), $shadow_cfg->{users}->{$username}->{shadow});
die "invalid credentials\n" if ($encpw ne $shadow_cfg->{users}->{$username}->{shadow});
} else {
die "no password set\n";

View File

@ -15,9 +15,11 @@ use base qw(PVE::SectionConfig);
my $domainconfigfile = "domains.cfg";
cfs_register_file($domainconfigfile,
cfs_register_file(
$domainconfigfile,
sub { __PACKAGE__->parse_config(@_); },
sub { __PACKAGE__->write_config(@_); });
sub { __PACKAGE__->write_config(@_); },
);
sub lock_domain_config {
my ($code, $errmsg) = @_;
@ -34,6 +36,7 @@ our $user_regex = qr![^\s:/]+!;
our $groupname_regex_chars = qr/A-Za-z0-9\.\-_/;
PVE::JSONSchema::register_format('pve-realm', \&pve_verify_realm);
sub pve_verify_realm {
my ($realm, $noerr) = @_;
@ -44,22 +47,31 @@ sub pve_verify_realm {
return $realm;
}
PVE::JSONSchema::register_standard_option('realm', {
PVE::JSONSchema::register_standard_option(
'realm',
{
description => "Authentication domain ID",
type => 'string', format => 'pve-realm',
type => 'string',
format => 'pve-realm',
maxLength => 32,
});
},
);
my $remove_options = "(?:acl|properties|entry)";
PVE::JSONSchema::register_standard_option('sync-scope', {
PVE::JSONSchema::register_standard_option(
'sync-scope',
{
description => "Select what to sync.",
type => 'string',
enum => [qw(users groups both)],
optional => '1',
});
},
);
PVE::JSONSchema::register_standard_option('sync-remove-vanished', {
PVE::JSONSchema::register_standard_option(
'sync-remove-vanished',
{
description => "A semicolon-separated list of things to remove when they or the user"
. " vanishes during a sync. The following values are possible: 'entry' removes the"
. " user/group when not returned from the sync. 'properties' removes the set"
@ -71,14 +83,16 @@ PVE::JSONSchema::register_standard_option('sync-remove-vanished', {
typetext => "([acl];[properties];[entry])|none",
pattern => "(?:(?:$remove_options\;)*$remove_options)|none",
optional => '1',
});
},
);
my $realm_sync_options_desc = {
scope => get_standard_option('sync-scope'),
'remove-vanished' => get_standard_option('sync-remove-vanished'),
# TODO check/rewrite in pve7to8, and remove with 8.0
full => {
description => "DEPRECATED: use 'remove-vanished' instead. If set, uses the LDAP Directory as source of truth,"
description =>
"DEPRECATED: use 'remove-vanished' instead. If set, uses the LDAP Directory as source of truth,"
. " deleting users or groups not returned from the sync and removing"
. " all locally modified properties of synced users. If not set,"
. " only syncs information which is present in the synced data, and does not"
@ -103,6 +117,7 @@ PVE::JSONSchema::register_standard_option('realm-sync-options', $realm_sync_opti
PVE::JSONSchema::register_format('realm-sync-options', $realm_sync_options_desc);
PVE::JSONSchema::register_format('pve-userid', \&verify_username);
sub verify_username {
my ($username, $noerr) = @_;
@ -131,11 +146,15 @@ sub verify_username {
return undef;
}
PVE::JSONSchema::register_standard_option('userid', {
PVE::JSONSchema::register_standard_option(
'userid',
{
description => "Full User ID, in the `name\@realm` format.",
type => 'string', format => 'pve-userid',
type => 'string',
format => 'pve-userid',
maxLength => 64,
});
},
);
my $tfa_format = {
type => {
@ -166,7 +185,8 @@ my $tfa_format = {
description => "TOTP digits.",
format_description => 'COUNT',
type => 'integer',
minimum => 6, maximum => 8,
minimum => 6,
maximum => 8,
default => 6,
optional => 1,
},
@ -182,12 +202,16 @@ my $tfa_format = {
PVE::JSONSchema::register_format('pve-tfa-config', $tfa_format);
PVE::JSONSchema::register_standard_option('tfa', {
PVE::JSONSchema::register_standard_option(
'tfa',
{
description => "Use Two-factor authentication.",
type => 'string', format => 'pve-tfa-config',
type => 'string',
format => 'pve-tfa-config',
optional => 1,
maxLength => 128,
});
},
);
sub parse_tfa_config {
my ($data) = @_;
@ -255,7 +279,7 @@ sub parse_config {
if !$cfg->{ids}->{pam}->{comment};
return $cfg;
};
}
sub write_config {
my ($class, $filename, $cfg) = @_;

View File

@ -34,13 +34,16 @@ sub param_mapping {
PVE::CLIHandler::get_standard_mapping('pve-password'),
],
'create_ticket' => [
PVE::CLIHandler::get_standard_mapping('pve-password', {
PVE::CLIHandler::get_standard_mapping(
'pve-password',
{
func => sub {
# do not accept values given on cmdline
return PVE::PTY::read_password('Enter password: ');
},
}),
]
},
),
],
};
return $mapping->{$name};
@ -93,10 +96,13 @@ __PACKAGE__->register_method({
properties => {
userid => get_standard_option('userid'),
tokenid => get_standard_option('token-subid'),
path => get_standard_option('acl-path', {
path => get_standard_option(
'acl-path',
{
description => "Only dump this specific path, not the whole tree.",
optional => 1,
}),
},
),
},
},
returns => {
@ -110,7 +116,8 @@ __PACKAGE__->register_method({
$param->{userid} = PVE::AccessControl::join_tokenid($param->{userid}, $token_subid);
return PVE::API2::AccessControl->permissions($param);
}});
},
});
__PACKAGE__->register_method({
name => 'delete_tfa',
@ -145,7 +152,8 @@ __PACKAGE__->register_method({
cfs_write_file('priv/tfa.cfg', $tfa_cfg);
});
return;
}});
},
});
__PACKAGE__->register_method({
name => 'list_tfa',
@ -175,7 +183,7 @@ __PACKAGE__->register_method({
printf("${nl}${indent}%-9s %s\n${indent} %s\n", "$ty:", $id, $desc // '');
$nl = "\n";
}
};
}
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
if (defined($userid)) {
@ -190,58 +198,142 @@ __PACKAGE__->register_method({
}
}
return;
}});
},
});
our $cmddef = {
user => {
add => ['PVE::API2::User', 'create_user', ['userid']],
modify => ['PVE::API2::User', 'update_user', ['userid']],
delete => ['PVE::API2::User', 'delete_user', ['userid']],
list => [ 'PVE::API2::User', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
permissions => [ 'PVE::API2::AccessControl', 'permissions', ['userid'], {}, $print_perm_result, $PVE::RESTHandler::standard_output_options],
list => [
'PVE::API2::User',
'index',
[],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
permissions => [
'PVE::API2::AccessControl',
'permissions',
['userid'],
{},
$print_perm_result,
$PVE::RESTHandler::standard_output_options,
],
tfa => {
delete => [__PACKAGE__, 'delete_tfa', ['userid']],
list => [__PACKAGE__, 'list_tfa', ['userid']],
unlock => ['PVE::API2::User', 'unlock_tfa', ['userid']],
},
token => {
add => [ 'PVE::API2::User', 'generate_token', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ],
modify => [ 'PVE::API2::User', 'update_token_info', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ],
delete => [ 'PVE::API2::User', 'remove_token', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ],
add => [
'PVE::API2::User',
'generate_token',
['userid', 'tokenid'],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
modify => [
'PVE::API2::User',
'update_token_info',
['userid', 'tokenid'],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
delete => [
'PVE::API2::User',
'remove_token',
['userid', 'tokenid'],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
remove => { alias => 'delete' },
list => [ 'PVE::API2::User', 'token_index', ['userid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
permissions => [ __PACKAGE__, 'token_permissions', ['userid', 'tokenid'], {}, $print_perm_result, $PVE::RESTHandler::standard_output_options],
}
list => [
'PVE::API2::User',
'token_index',
['userid'],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
permissions => [
__PACKAGE__,
'token_permissions',
['userid', 'tokenid'],
{},
$print_perm_result,
$PVE::RESTHandler::standard_output_options,
],
},
},
group => {
add => ['PVE::API2::Group', 'create_group', ['groupid']],
modify => ['PVE::API2::Group', 'update_group', ['groupid']],
delete => ['PVE::API2::Group', 'delete_group', ['groupid']],
list => [ 'PVE::API2::Group', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
list => [
'PVE::API2::Group',
'index',
[],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
},
role => {
add => ['PVE::API2::Role', 'create_role', ['roleid']],
modify => ['PVE::API2::Role', 'update_role', ['roleid']],
delete => ['PVE::API2::Role', 'delete_role', ['roleid']],
list => [ 'PVE::API2::Role', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
list => [
'PVE::API2::Role',
'index',
[],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
},
acl => {
modify => ['PVE::API2::ACL', 'update_acl', ['path'], { delete => 0 }],
delete => ['PVE::API2::ACL', 'update_acl', ['path'], { delete => 1 }],
list => [ 'PVE::API2::ACL', 'read_acl', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
list => [
'PVE::API2::ACL',
'read_acl',
[],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
},
realm => {
add => ['PVE::API2::Domains', 'create', ['realm']],
modify => ['PVE::API2::Domains', 'update', ['realm']],
delete => ['PVE::API2::Domains', 'delete', ['realm']],
list => [ 'PVE::API2::Domains', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
sync => [ 'PVE::API2::Domains', 'sync', ['realm'], ],
list => [
'PVE::API2::Domains',
'index',
[],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
sync => ['PVE::API2::Domains', 'sync', ['realm']],
},
ticket => [ 'PVE::API2::AccessControl', 'create_ticket', ['username'], undef, sub {
ticket => [
'PVE::API2::AccessControl',
'create_ticket',
['username'],
undef,
sub {
my ($res) = @_;
print "$res->{ticket}\n";
}],
},
],
passwd => ['PVE::API2::AccessControl', 'change_password', ['userid']],
@ -275,7 +367,14 @@ if ($have_pool_api) {
add => ['PVE::API2::Pool', 'create_pool', ['poolid']],
modify => ['PVE::API2::Pool', 'update_pool', ['poolid']],
delete => ['PVE::API2::Pool', 'delete_pool', ['poolid']],
list => [ 'PVE::API2::Pool', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
list => [
'PVE::API2::Pool',
'index',
[],
{},
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
};
}

View File

@ -133,7 +133,10 @@ sub run {
my $nodename = PVE::INotify::nodename();
# check statefile in pmxcfs if we should start
my $shouldrun = PVE::Cluster::cfs_lock_domain('realm-sync', undef, sub {
my $shouldrun = PVE::Cluster::cfs_lock_domain(
'realm-sync',
undef,
sub {
my $members = PVE::Cluster::get_members();
my $state = get_state($id);
@ -141,13 +144,15 @@ sub run {
my $last_upid = $state->{upid};
my $last_time = $state->{time};
my $last_node_online = $last_node eq $nodename || ($members->{$last_node} // {})->{online};
my $last_node_online =
$last_node eq $nodename || ($members->{$last_node} // {})->{online};
if (defined($last_upid)) {
# first check if the next run is scheduled
if (my $parsed = PVE::Tools::upid_decode($last_upid, 1)) {
my $cal_spec = PVE::CalendarEvent::parse_calendar_event($schedule);
my $next_sync = PVE::CalendarEvent::compute_next_event($cal_spec, $parsed->{starttime});
my $next_sync =
PVE::CalendarEvent::compute_next_event($cal_spec, $parsed->{starttime});
return 0 if !defined($next_sync) || $now < $next_sync; # not yet its (next) turn
}
# check if still running and node is online
@ -168,32 +173,46 @@ sub run {
# * it was started but either too long ago, or with an error
# * the started task finished
save_state($id, {
save_state(
$id,
{
node => $nodename,
time => $now,
});
},
);
return 1;
});
},
);
die $@ if $@;
if ($shouldrun) {
my $upid = eval { PVE::API2::Domains->sync($conf) };
my $err = $@;
PVE::Cluster::cfs_lock_domain('realm-sync', undef, sub {
PVE::Cluster::cfs_lock_domain(
'realm-sync',
undef,
sub {
if ($err && !$upid) {
save_state($id, {
save_state(
$id,
{
node => $nodename,
time => $now,
error => $err,
});
},
);
die "$err\n";
}
save_state($id, {
save_state(
$id,
{
node => $nodename,
upid => $upid,
});
});
},
);
},
);
die $@ if $@;
return $upid;
}

View File

@ -32,7 +32,8 @@ my $compile_acl_path = sub {
# permissions() will always prime the cache for the owning user
my ($username, undef) = PVE::AccessControl::split_tokenid($user, 1);
die "internal error" if $username && $username ne 'root@pam' && !defined($cache->{$username});
die "internal error"
if $username && $username ne 'root@pam' && !defined($cache->{$username});
# resolve and cache roles of the current user/token for all pool ACL paths
if (!$data->{poolroles}) {
@ -193,10 +194,14 @@ sub compute_api_permission {
my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn', '/mapping'];
my $defined_paths = [];
PVE::AccessControl::iterate_acl_tree("/", $usercfg->{acl_root}, sub {
PVE::AccessControl::iterate_acl_tree(
"/",
$usercfg->{acl_root},
sub {
my ($path, $node) = @_;
push @$defined_paths, $path;
});
},
);
my $checked_paths = {};
foreach my $path (@$required_paths, @$defined_paths) {
@ -251,10 +256,14 @@ sub get_effective_permissions {
my $cfg = $self->{user_cfg};
# paths explicitly listed in ACLs
PVE::AccessControl::iterate_acl_tree("/", $cfg->{acl_root}, sub {
PVE::AccessControl::iterate_acl_tree(
"/",
$cfg->{acl_root},
sub {
my ($path, $node) = @_;
$paths->{$path} = 1;
});
},
);
# paths referenced by pool definitions
foreach my $pool (keys %{ $cfg->{pools} }) {
@ -290,10 +299,10 @@ sub check {
return undef if $noerr;
raise_perm_exc("$path, $priv");
}
};
}
return 1;
};
}
sub check_any {
my ($self, $user, $path, $privs, $noerr) = @_;
@ -307,14 +316,14 @@ sub check_any {
$found = 1;
last;
}
};
}
return 1 if $found;
return undef if $noerr;
raise_perm_exc("$path, " . join("|", @$privs));
};
}
sub check_full {
my ($self, $username, $path, $privs, $any, $noerr) = @_;
@ -385,7 +394,7 @@ sub check_vm_perm {
return if $self->check_full($user, "/pool/$pool", $privs, $any, 1);
}
return $self->check_full($user, "/vms/$vmid", $privs, $any, $noerr);
};
}
sub is_group_member {
my ($self, $group, $user) = @_;
@ -535,7 +544,7 @@ sub exec_api2_perm_check {
} else {
die "unknown permission test";
}
};
}
sub check_api2_permissions {
my ($self, $perm, $username, $param) = @_;
@ -581,8 +590,7 @@ sub init {
$self->{aclversion} = undef;
return $self;
};
}
# init_request - must be called before each RPC request
sub init_request {
@ -609,8 +617,12 @@ sub init_request {
$self->{user_cfg} = $cfg;
} else {
my $ucvers = PVE::Cluster::cfs_file_version('user.cfg');
if (!$self->{aclcache} || !defined($self->{aclversion}) ||
!defined($ucvers) || ($ucvers ne $self->{aclversion})) {
if (
!$self->{aclcache}
|| !defined($self->{aclversion})
|| !defined($ucvers)
|| ($ucvers ne $self->{aclversion})
) {
$self->{aclversion} = $ucvers;
my $cfg = PVE::Cluster::cfs_read_file('user.cfg');
$self->{user_cfg} = $cfg;

View File

@ -49,7 +49,10 @@ sub generate_token {
PVE::AccessControl::pve_verify_tokenid($tokenid);
my $token_value = PVE::Cluster::cfs_lock_file('priv/token.cfg', 10, sub {
my $token_value = PVE::Cluster::cfs_lock_file(
'priv/token.cfg',
10,
sub {
my $uuid = UUID::uuid();
my $token_cfg = PVE::Cluster::cfs_read_file('priv/token.cfg');
@ -58,7 +61,8 @@ sub generate_token {
PVE::Cluster::cfs_write_file('priv/token.cfg', $token_cfg);
return $uuid;
});
},
);
die "$@\n" if defined($@);
@ -68,13 +72,17 @@ sub generate_token {
sub delete_token {
my ($tokenid) = @_;
PVE::Cluster::cfs_lock_file('priv/token.cfg', 10, sub {
PVE::Cluster::cfs_lock_file(
'priv/token.cfg',
10,
sub {
my $token_cfg = PVE::Cluster::cfs_read_file('priv/token.cfg');
delete $token_cfg->{$tokenid};
PVE::Cluster::cfs_write_file('priv/token.cfg', $token_cfg);
});
},
);
die "$@\n" if defined($@);
}

View File

@ -79,13 +79,15 @@ my $stranger_user_tests = [
my $stranger_nonprivsep_tests = [
{
description => 'get stranger-owned non-priv-sep\'d token\'s perms without passing the token',
description =>
'get stranger-owned non-priv-sep\'d token\'s perms without passing the token',
rpcuser => 'stranger@pve!noprivsep',
params => {},
result => $stranger_perms,
},
{
description => 'get stranger-owned non-priv-sep\'d token\'s perms with passing the token',
description =>
'get stranger-owned non-priv-sep\'d token\'s perms with passing the token',
rpcuser => 'stranger@pve!noprivsep',
params => {
userid => 'stranger@pve!noprivsep',
@ -110,18 +112,20 @@ my $stranger_nonprivsep_tests = [
},
},
{
description => 'get auditor-owned token\'s perms from stranger-owned non-priv-sep\'d token',
description =>
'get auditor-owned token\'s perms from stranger-owned non-priv-sep\'d token',
should_fail => 1,
rpcuser => 'stranger@pve!noprivsep',
params => {
userid => 'auditor@pam!noprivsep',
}
},
},
];
my $stranger_privsep_tests = [
{
description => 'get stranger-owned priv-sep\'d token\'s perms without passing the token',
description =>
'get stranger-owned priv-sep\'d token\'s perms without passing the token',
rpcuser => 'stranger@pve!privsep',
params => {},
result => $stranger_privsep_perms,
@ -157,7 +161,7 @@ my $stranger_privsep_tests = [
rpcuser => 'stranger@pve!privsep',
params => {
userid => 'auditor@pam!noprivsep',
}
},
},
];
@ -216,13 +220,15 @@ my $auditor_user_tests = [
my $auditor_nonprivsep_tests = [
{
description => 'get auditor-owned non-priv-sep\'d token\'s perms without passing the token',
description =>
'get auditor-owned non-priv-sep\'d token\'s perms without passing the token',
rpcuser => 'auditor@pam!noprivsep',
params => {},
result => $auditor_perms,
},
{
description => 'get auditor-owned non-priv-sep\'d token\'s perms with passing the token',
description =>
'get auditor-owned non-priv-sep\'d token\'s perms with passing the token',
rpcuser => 'auditor@pam!noprivsep',
params => {
userid => 'auditor@pam!noprivsep',
@ -247,7 +253,8 @@ my $auditor_nonprivsep_tests = [
result => $auditor_privsep_perms,
},
{
description => 'get stranger-owned token\'s perms from auditor-owned non-priv-sep\'d token',
description =>
'get stranger-owned token\'s perms from auditor-owned non-priv-sep\'d token',
rpcuser => 'auditor@pam!noprivsep',
params => {
userid => 'stranger@pve!noprivsep',

View File

@ -443,7 +443,8 @@ my $default_raw = {
'acl_complex_users_1' => 'acl:1:/storage:test@pam:PVEDatastoreAdmin:',
'acl_complex_users_2' => 'acl:1:/storage:test2@pam:PVEDatastoreUser:',
'acl_simple_token' => 'acl:1:/:test@pam!full:PVEVMAdmin:',
'acl_complex_tokens_1' => 'acl:1:/storage:test2@pam!expired,test@pam!full:PVEDatastoreAdmin:',
'acl_complex_tokens_1' =>
'acl:1:/storage:test2@pam!expired,test@pam!full:PVEDatastoreAdmin:',
'acl_complex_tokens_2' => 'acl:1:/storage:test2@pam!privsep:PVEDatastoreUser:',
'acl_complex_tokens_1_missing' => 'acl:1:/storage:test2@pam!expired:PVEDatastoreAdmin:',
'acl_simple_group' => 'acl:1:/:@testgroup:PVEVMAdmin:',
@ -488,10 +489,9 @@ my $tests = [
roles => default_roles(),
groups => default_groups_with([$default_cfg->{'test_group_empty'}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n".
$default_raw->{groups}->{'test_group_empty'}."\n\n".
"\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_empty'} . "\n\n" . "\n\n",
},
{
name => "group_inexisting_member",
@ -501,14 +501,13 @@ my $tests = [
roles => default_roles(),
groups => default_groups_with([$default_cfg->{'test_group_empty'}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n".
"group:testgroup:does_not_exist::".
"\n\n\n\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n\n".
$default_raw->{groups}->{'test_group_empty'}."\n\n".
"\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n\n"
. "group:testgroup:does_not_exist::"
. "\n\n\n\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_empty'} . "\n\n" . "\n\n",
},
{
name => "group_invalid_member",
@ -517,10 +516,9 @@ my $tests = [
users => default_users(),
roles => default_roles(),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n".
'group:inval!d:root@pam:'.
"\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n\n"
. 'group:inval!d:root@pam:' . "\n\n",
},
{
name => "group_with_one_member",
@ -530,26 +528,26 @@ my $tests = [
roles => default_roles(),
groups => default_groups_with([$default_cfg->{'test_group_single_member'}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_single_member'}."\n\n".
"\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_single_member'} . "\n\n" . "\n\n",
},
{
name => "group_with_members",
config => {
acl_root => default_acls(),
users => default_users_with([$default_cfg->{test_pam_with_group}, $default_cfg->{test2_pam_with_group}]),
users => default_users_with(
[$default_cfg->{test_pam_with_group}, $default_cfg->{test2_pam_with_group}]
),
roles => default_roles(),
groups => default_groups_with([$default_cfg->{'test_group_members'}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_members'}."\n\n".
"\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_members'} . "\n\n" . "\n\n",
},
{
name => "token_simple",
@ -558,27 +556,30 @@ my $tests = [
users => default_users_with([$default_cfg->{test_pam_with_token}]),
roles => default_roles(),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n".
$default_raw->{tokens}->{'test_token_simple'}."\n\n\n\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_simple'}
. "\n\n\n\n\n",
},
{
name => "token_multi",
config => {
acl_root => default_acls(),
users => default_users_with([$default_cfg->{test_pam_with_token}, $default_cfg->{test_pam2_with_token}]),
users => default_users_with(
[$default_cfg->{test_pam_with_token}, $default_cfg->{test_pam2_with_token}]
),
roles => default_roles(),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
$default_raw->{users}->{'test_pam'}."\n".
$default_raw->{tokens}->{'test_token_simple'}."\n".
"\n\n\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_simple'} . "\n"
. "\n\n\n\n",
},
{
name => "custom_role_with_single_priv",
@ -587,9 +588,10 @@ my $tests = [
users => default_users(),
roles => default_roles_with([$default_cfg->{test_role_single_priv}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
$default_raw->{roles}->{'test_role_single_priv'}."\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n\n"
. $default_raw->{roles}->{'test_role_single_priv'} . "\n\n",
},
{
name => "custom_role_with_privs",
@ -598,9 +600,10 @@ my $tests = [
users => default_users(),
roles => default_roles_with([$default_cfg->{test_role_privs}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
$default_raw->{roles}->{'test_role_privs'}."\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n\n"
. $default_raw->{roles}->{'test_role_privs'} . "\n\n",
},
{
name => "custom_role_with_duplicate_privs",
@ -609,12 +612,14 @@ my $tests = [
users => default_users(),
roles => default_roles_with([$default_cfg->{test_role_privs}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
$default_raw->{roles}->{'test_role_privs_duplicate'}."\n\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
$default_raw->{roles}->{'test_role_privs'}."\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n\n"
. $default_raw->{roles}->{'test_role_privs_duplicate'} . "\n\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n\n"
. $default_raw->{roles}->{'test_role_privs'} . "\n\n",
},
{
name => "custom_role_with_invalid_priv",
@ -623,12 +628,14 @@ my $tests = [
users => default_users(),
roles => default_roles_with([$default_cfg->{test_role_privs}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
$default_raw->{roles}->{'test_role_privs_invalid'}."\n\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
$default_raw->{roles}->{'test_role_privs'}."\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n\n"
. $default_raw->{roles}->{'test_role_privs_invalid'} . "\n\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n\n"
. $default_raw->{roles}->{'test_role_privs'} . "\n\n",
},
{
name => "pool_empty",
@ -638,9 +645,11 @@ my $tests = [
roles => default_roles(),
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n".
$default_raw->{pools}->{'test_pool_empty'}."\n\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n"
. $default_raw->{pools}->{'test_pool_empty'}
. "\n\n\n",
},
{
name => "pool_invalid",
@ -650,12 +659,16 @@ my $tests = [
roles => default_roles(),
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n".
$default_raw->{pools}->{'test_pool_invalid'}."\n\n\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n".
$default_raw->{pools}->{'test_pool_empty'}."\n\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n"
. $default_raw->{pools}->{'test_pool_invalid'}
. "\n\n\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n"
. $default_raw->{pools}->{'test_pool_empty'}
. "\n\n\n",
},
{
name => "pool_members",
@ -666,9 +679,11 @@ my $tests = [
pools => default_pools_with([$default_cfg->{test_pool_members}]),
vms => default_pool_vms_with([$default_cfg->{test_pool_members}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n".
$default_raw->{pools}->{'test_pool_members'}."\n\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n"
. $default_raw->{pools}->{'test_pool_members'}
. "\n\n\n",
},
{
name => "pool_duplicate_members",
@ -676,19 +691,28 @@ my $tests = [
acl_root => default_acls(),
users => default_users(),
roles => default_roles(),
pools => default_pools_with([$default_cfg->{test_pool_members}, $default_cfg->{test_pool_duplicate_vms}, $default_cfg->{test_pool_duplicate_storages}]),
pools => default_pools_with(
[
$default_cfg->{test_pool_members},
$default_cfg->{test_pool_duplicate_vms},
$default_cfg->{test_pool_duplicate_storages},
],
),
vms => default_pool_vms_with([$default_cfg->{test_pool_members}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n".
$default_raw->{pools}->{'test_pool_members'}."\n".
$default_raw->{pools}->{'test_pool_duplicate_vms'}."\n".
$default_raw->{pools}->{'test_pool_duplicate_storages'}."\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n".
$default_raw->{pools}->{'test_pool_duplicate_storages'}."\n".
$default_raw->{pools}->{'test_pool_duplicate_vms_expected'}."\n".
$default_raw->{pools}->{'test_pool_members'}."\n\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n"
. $default_raw->{pools}->{'test_pool_members'} . "\n"
. $default_raw->{pools}->{'test_pool_duplicate_vms'} . "\n"
. $default_raw->{pools}->{'test_pool_duplicate_storages'} . "\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n"
. $default_raw->{pools}->{'test_pool_duplicate_storages'} . "\n"
. $default_raw->{pools}->{'test_pool_duplicate_vms_expected'} . "\n"
. $default_raw->{pools}->{'test_pool_members'}
. "\n\n\n",
},
{
name => "acl_simple_user",
@ -697,39 +721,47 @@ my $tests = [
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_user}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_simple_user'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n",
},
{
name => "acl_complex_users",
config => {
users => default_users_with([$default_cfg->{test_pam}, $default_cfg->{'test2_pam'}]),
users =>
default_users_with([$default_cfg->{test_pam}, $default_cfg->{'test2_pam'}]),
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_user}, $default_cfg->{acl_complex_users}]),
acl_root => default_acls_with(
[$default_cfg->{acl_simple_user}, $default_cfg->{acl_complex_users}]
),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_simple_user'}."\n".
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test_pam'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
},
{
name => "acl_complex_missing_user",
config => {
users => default_users_with([$default_cfg->{test2_pam}]),
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_user}, $default_cfg->{acl_complex_missing_user}]),
acl_root => default_acls_with(
[$default_cfg->{acl_simple_user}, $default_cfg->{acl_complex_missing_user}]
),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_simple_user'}."\n".
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
},
{
name => "acl_simple_group",
@ -739,57 +771,78 @@ my $tests = [
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_group}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_single_member'}."\n\n\n\n".
$default_raw->{acl}->{'acl_simple_group'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_single_member'}
. "\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_group'} . "\n",
},
{
name => "acl_complex_groups",
config => {
users => default_users_with([$default_cfg->{test_pam_with_group}, $default_cfg->{'test2_pam_with_group'}, $default_cfg->{'test3_pam'}]),
groups => default_groups_with([$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]),
users => default_users_with(
[
$default_cfg->{test_pam_with_group},
$default_cfg->{'test2_pam_with_group'},
$default_cfg->{'test3_pam'},
],
),
groups => default_groups_with(
[$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]
),
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_group}, $default_cfg->{acl_complex_groups}]),
acl_root => default_acls_with(
[$default_cfg->{acl_simple_group}, $default_cfg->{acl_complex_groups}]
),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_second'}."\n".
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
$default_raw->{acl}->{'acl_simple_group'}."\n".
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_second'} . "\n"
. $default_raw->{groups}->{'test_group_members'}
. "\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
},
{
name => "acl_complex_missing_group",
config => {
users => default_users_with([$default_cfg->{test_pam}, $default_cfg->{'test2_pam'}, $default_cfg->{'test3_pam'}]),
users => default_users_with(
[
$default_cfg->{test_pam},
$default_cfg->{'test2_pam'},
$default_cfg->{'test3_pam'},
],
),
groups => default_groups_with([$default_cfg->{'test_group_second'}]),
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_group}, $default_cfg->{acl_complex_missing_group}]),
acl_root => default_acls_with(
[$default_cfg->{acl_simple_group}, $default_cfg->{acl_complex_missing_group}]
),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_second'}."\n".
$default_raw->{acl}->{'acl_simple_group'}."\n".
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_second'}."\n\n\n\n".
$default_raw->{acl}->{'acl_simple_group'}."\n".
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_second'} . "\n"
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_second'}
. "\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
},
{
name => "acl_simple_token",
@ -798,57 +851,66 @@ my $tests = [
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_token}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n".
$default_raw->{tokens}->{'test_token_simple'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_simple_token'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_simple'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_token'} . "\n",
},
{
name => "acl_complex_tokens",
config => {
users => default_users_with([$default_cfg->{test_pam_with_token}, $default_cfg->{'test_pam2_with_token'}]),
users => default_users_with(
[$default_cfg->{test_pam_with_token}, $default_cfg->{'test_pam2_with_token'}]
),
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_token}, $default_cfg->{acl_complex_tokens}]),
acl_root => default_acls_with(
[$default_cfg->{acl_simple_token}, $default_cfg->{acl_complex_tokens}]
),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
$default_raw->{users}->{'test_pam'}."\n".
$default_raw->{tokens}->{'test_token_simple'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_simple_token'}."\n".
$default_raw->{acl}->{'acl_complex_tokens_1'}."\n".
$default_raw->{acl}->{'acl_complex_tokens_2'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_simple'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_token'} . "\n"
. $default_raw->{acl}->{'acl_complex_tokens_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_tokens_2'} . "\n",
},
{
name => "acl_complex_missing_token",
config => {
users => default_users_with([$default_cfg->{test_pam}, $default_cfg->{test_pam2_with_token}]),
users => default_users_with(
[$default_cfg->{test_pam}, $default_cfg->{test_pam2_with_token}]
),
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_complex_missing_token}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
$default_raw->{users}->{'test_pam'}."\n".
$default_raw->{acl}->{'acl_simple_token'}."\n".
$default_raw->{acl}->{'acl_complex_tokens_1'}."\n".
$default_raw->{acl}->{'acl_complex_tokens_2'}."\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_complex_tokens_1_missing'}."\n".
$default_raw->{acl}->{'acl_complex_tokens_2'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n"
. $default_raw->{acl}->{'acl_simple_token'} . "\n"
. $default_raw->{acl}->{'acl_complex_tokens_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_tokens_2'} . "\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
. $default_raw->{users}->{'test_pam'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_complex_tokens_1_missing'} . "\n"
. $default_raw->{acl}->{'acl_complex_tokens_2'} . "\n",
},
{
name => "acl_missing_role",
@ -857,127 +919,149 @@ my $tests = [
roles => default_roles(),
acl_root => default_acls_with([$default_cfg->{acl_simple_user}]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_simple_user'}."\n".
$default_raw->{acl}->{'acl_missing_role'}."\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
$default_raw->{acl}->{'acl_simple_user'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
. $default_raw->{acl}->{'acl_missing_role'} . "\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'}
. "\n\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n",
},
{
name => "acl_complex_mixed",
config => {
users => default_users_with([$default_cfg->{test_pam_with_group}, $default_cfg->{'test2_pam_with_group'}, $default_cfg->{'test3_pam'}]),
groups => default_groups_with([$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]),
users => default_users_with(
[
$default_cfg->{test_pam_with_group},
$default_cfg->{'test2_pam_with_group'},
$default_cfg->{'test3_pam'},
],
),
groups => default_groups_with(
[$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]
),
roles => default_roles(),
acl_root => default_acls_with([
$default_cfg->{acl_complex_mixed_root},
$default_cfg->{acl_complex_mixed_storage},
]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_second'}."\n".
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
$default_raw->{acl}->{'acl_simple_group'}."\n".
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
$default_raw->{acl}->{'acl_complex_groups_2'}."\n".
$default_raw->{acl}->{'acl_simple_user'}."\n".
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_second'}."\n".
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
$default_raw->{acl}->{'acl_complex_mixed_1'}."\n".
$default_raw->{acl}->{'acl_complex_mixed_2'}."\n".
$default_raw->{acl}->{'acl_complex_mixed_3'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_second'} . "\n"
. $default_raw->{groups}->{'test_group_members'}
. "\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_second'} . "\n"
. $default_raw->{groups}->{'test_group_members'}
. "\n\n\n\n"
. $default_raw->{acl}->{'acl_complex_mixed_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_mixed_2'} . "\n"
. $default_raw->{acl}->{'acl_complex_mixed_3'} . "\n",
},
{
name => "acl_complex_mixed_prop_noprop_no_merge_sort_by_path",
config => {
users => default_users_with([$default_cfg->{test_pam_with_group}, $default_cfg->{'test2_pam_with_group'}, $default_cfg->{'test3_pam'}]),
groups => default_groups_with([$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]),
users => default_users_with(
[
$default_cfg->{test_pam_with_group},
$default_cfg->{'test2_pam_with_group'},
$default_cfg->{'test3_pam'},
],
),
groups => default_groups_with(
[$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]
),
roles => default_roles(),
acl_root => default_acls_with([
$default_cfg->{acl_complex_mixed_root_noprop},
$default_cfg->{acl_complex_mixed_storage_noprop},
]),
},
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_second'}."\n".
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
$default_raw->{acl}->{'acl_simple_group_noprop'}."\n".
$default_raw->{acl}->{'acl_simple_user'}."\n".
$default_raw->{acl}->{'acl_complex_groups_1_noprop'}."\n".
$default_raw->{acl}->{'acl_complex_groups_2_noprop'}."\n".
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_second'} . "\n"
. $default_raw->{groups}->{'test_group_members'}
. "\n\n\n\n"
. $default_raw->{acl}->{'acl_simple_group_noprop'} . "\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_1_noprop'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_2_noprop'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
},
{
name => "sort_roles_and_privs",
raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{roles}->{'test_role_single_priv'}."\n\n".
$default_raw->{roles}->{'test_role_privs_out_of_order'}."\n\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
$default_raw->{roles}->{'test_role_privs'}."\n".
$default_raw->{roles}->{'test_role_single_priv'}."\n\n",
raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{roles}->{'test_role_single_priv'} . "\n\n"
. $default_raw->{roles}->{'test_role_privs_out_of_order'} . "\n\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'}
. "\n\n\n\n"
. $default_raw->{roles}->{'test_role_privs'} . "\n"
. $default_raw->{roles}->{'test_role_single_priv'} . "\n\n",
},
{
name => "sort_users_and_group_members",
raw => "".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_members_out_of_order'}."\n\n".
"\n\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_members'}."\n\n".
"\n\n",
raw => ""
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_members_out_of_order'} . "\n\n" . "\n\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_members'} . "\n\n" . "\n\n",
},
{
name => "sort_user_groups_and_acls",
raw => "".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{groups}->{'test_group_members_out_of_order'}."\n\n\n\n".
$default_raw->{groups}->{'test_group_second'}."\n".
$default_raw->{acl}->{'acl_simple_user'}."\n".
$default_raw->{acl}->{'acl_simple_group'}."\n".
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
$default_raw->{acl}->{'acl_complex_users_2'}."\n".
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
expected_raw => "".
$default_raw->{users}->{'root@pam'}."\n".
$default_raw->{users}->{'test2_pam'}."\n".
$default_raw->{users}->{'test3_pam'}."\n".
$default_raw->{users}->{'test_pam'}."\n\n".
$default_raw->{groups}->{'test_group_second'}."\n".
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
$default_raw->{acl}->{'acl_complex_mixed_1'}."\n".
$default_raw->{acl}->{'acl_complex_mixed_2'}."\n".
$default_raw->{acl}->{'acl_complex_mixed_3'}."\n",
raw => ""
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{groups}->{'test_group_members_out_of_order'}
. "\n\n\n\n"
. $default_raw->{groups}->{'test_group_second'} . "\n"
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
expected_raw => ""
. $default_raw->{users}->{'root@pam'} . "\n"
. $default_raw->{users}->{'test2_pam'} . "\n"
. $default_raw->{users}->{'test3_pam'} . "\n"
. $default_raw->{users}->{'test_pam'} . "\n\n"
. $default_raw->{groups}->{'test_group_second'} . "\n"
. $default_raw->{groups}->{'test_group_members'}
. "\n\n\n\n"
. $default_raw->{acl}->{'acl_complex_mixed_1'} . "\n"
. $default_raw->{acl}->{'acl_complex_mixed_2'} . "\n"
. $default_raw->{acl}->{'acl_complex_mixed_3'} . "\n",
},
{
name => 'default_values',
@ -1005,25 +1089,24 @@ my $tests = [
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
acl_root => {},
},
raw => "".
'user:root@pam'."\n".
'user:test@pam'."\n".
'token:test@pam!test'."\n\n".
'group:testgroup'."\n\n".
'pool:testpool'."\n\n".
'role:testrole'."\n\n".
'acl::/:',
expected_raw => "".
'user:root@pam:0:0::::::'."\n".
'user:test@pam:0:0::::::'."\n".
'token:test@pam!test:0:0::'."\n\n".
'group:testgroup:::'."\n\n".
'pool:testpool::::'."\n\n".
'role:testrole::'."\n\n",
raw => ""
. 'user:root@pam' . "\n"
. 'user:test@pam' . "\n"
. 'token:test@pam!test' . "\n\n"
. 'group:testgroup' . "\n\n"
. 'pool:testpool' . "\n\n"
. 'role:testrole' . "\n\n"
. 'acl::/:',
expected_raw => ""
. 'user:root@pam:0:0::::::' . "\n"
. 'user:test@pam:0:0::::::' . "\n"
. 'token:test@pam!test:0:0::' . "\n\n"
. 'group:testgroup:::' . "\n\n"
. 'pool:testpool::::' . "\n\n"
. 'role:testrole::' . "\n\n",
},
];
my $number_of_tests_run = 0;
foreach my $t (@$tests) {
my $expected_config = $t->{expected_config} // $t->{config};
@ -1035,7 +1118,11 @@ foreach my $t (@$tests) {
$number_of_tests_run++;
}
if (defined($t->{expected_raw}) && !defined($t->{config})) {
is(PVE::AccessControl::write_user_config($t->{name}, $parsed), $t->{expected_raw}, "$t->{name}_rewrite");
is(
PVE::AccessControl::write_user_config($t->{name}, $parsed),
$t->{expected_raw},
"$t->{name}_rewrite",
);
$number_of_tests_run++;
}
@ -1047,10 +1134,14 @@ foreach my $t (@$tests) {
$number_of_tests_run++;
}
if (defined($t->{expected_config}) && !defined($t->{raw})) {
is_deeply(PVE::AccessControl::parse_user_config($t->{name}, $t->{written}), $t->{expected_config}, "$t->{name}_reparse");
is_deeply(
PVE::AccessControl::parse_user_config($t->{name}, $t->{written}),
$t->{expected_config},
"$t->{name}_reparse",
);
$number_of_tests_run++;
}
}
};
}
done_testing($number_of_tests_run);

View File

@ -65,7 +65,7 @@ check_permission(
'' # sorted, comma-separated expected privilege string
. 'VM.Allocate,VM.Audit,VM.Backup,VM.Clone,VM.Config.CDROM,VM.Config.CPU,VM.Config.Cloudinit,'
. 'VM.Config.Disk,VM.Config.HWType,VM.Config.Memory,VM.Config.Network,VM.Config.Options,'
. 'VM.Console,VM.Migrate,VM.Monitor,VM.PowerMgmt,VM.Snapshot,VM.Snapshot.Rollback'
. 'VM.Console,VM.Migrate,VM.Monitor,VM.PowerMgmt,VM.Snapshot,VM.Snapshot.Rollback',
);
# Administrator -> Permissions.Modify!
check_permission(

View File

@ -27,7 +27,6 @@ sub check_roles {
print "ROLES:$path:$user:$res\n";
}
check_roles('User1@pve', '/vms/300', 'Role1');
check_roles('User2@pve', '/vms/300', 'NoAccess');

View File

@ -27,7 +27,6 @@ sub check_roles {
print "ROLES:$path:$user:$res\n";
}
check_roles('User1@pve', '/vms', 'Role1');
check_roles('User1@pve', '/vms/100', 'Role1');
check_roles('User1@pve', '/vms/100/a', 'Role1');

View File

@ -14,13 +14,13 @@ my $domainscfg = {
ids => {
"pam" => { type => 'pam' },
"pve" => { type => 'pve' },
"syncedrealm" => { type => 'ldap' }
"syncedrealm" => { type => 'ldap' },
},
};
my $initialusercfg = {
users => {
'root@pam' => { username => 'root', },
'root@pam' => { username => 'root' },
'user1@syncedrealm' => {
username => 'user1',
enable => 1,
@ -36,8 +36,8 @@ my $initialusercfg = {
},
},
groups => {
'group1-syncedrealm' => { users => {}, },
'group2-syncedrealm' => { users => {}, },
'group1-syncedrealm' => { users => {} },
'group2-syncedrealm' => { users => {} },
},
acl_root => {
users => {
@ -50,15 +50,15 @@ my $initialusercfg = {
my $sync_response = {
user => [
{
attributes => { 'uid' => ['user1'], },
attributes => { 'uid' => ['user1'] },
dn => 'uid=user1,dc=syncedrealm',
},
{
attributes => { 'uid' => ['user2'], },
attributes => { 'uid' => ['user2'] },
dn => 'uid=user2,dc=syncedrealm',
},
{
attributes => { 'uid' => ['user4'], },
attributes => { 'uid' => ['user4'] },
dn => 'uid=user4,dc=syncedrealm',
},
],
@ -74,7 +74,7 @@ my $sync_response = {
members => [
'uid=nonexisting,dc=syncedrealm',
],
}
},
],
};
@ -152,7 +152,7 @@ my $tests = [
},
{
users => {
'root@pam' => { username => 'root', },
'root@pam' => { username => 'root' },
'user1@syncedrealm' => {
username => 'user1',
enable => 1,
@ -177,8 +177,8 @@ my $tests = [
'user1@syncedrealm' => 1,
},
},
'group2-syncedrealm' => { users => {}, },
'group3-syncedrealm' => { users => {}, },
'group2-syncedrealm' => { users => {} },
'group3-syncedrealm' => { users => {} },
},
acl_root => {
users => {
@ -197,7 +197,7 @@ my $tests = [
},
{
users => {
'root@pam' => { username => 'root', },
'root@pam' => { username => 'root' },
'user1@syncedrealm' => {
username => 'user1',
enable => 1,
@ -217,7 +217,7 @@ my $tests = [
'user1@syncedrealm' => 1,
},
},
'group3-syncedrealm' => { users => {}, }
'group3-syncedrealm' => { users => {} },
},
acl_root => {
users => {
@ -236,7 +236,7 @@ my $tests = [
},
{
users => {
'root@pam' => { username => 'root', },
'root@pam' => { username => 'root' },
'user1@syncedrealm' => {
username => 'user1',
enable => 1,
@ -261,8 +261,8 @@ my $tests = [
'user1@syncedrealm' => 1,
},
},
'group2-syncedrealm' => { users => {}, },
'group3-syncedrealm' => { users => {}, },
'group2-syncedrealm' => { users => {} },
'group3-syncedrealm' => { users => {} },
},
acl_root => {
users => {},
@ -279,7 +279,7 @@ my $tests = [
},
{
users => {
'root@pam' => { username => 'root', },
'root@pam' => { username => 'root' },
'user1@syncedrealm' => {
username => 'user1',
enable => 1,
@ -299,7 +299,7 @@ my $tests = [
'user1@syncedrealm' => 1,
},
},
'group3-syncedrealm' => { users => {}, },
'group3-syncedrealm' => { users => {} },
},
acl_root => {
users => {},
@ -316,7 +316,7 @@ my $tests = [
},
{
users => {
'root@pam' => { username => 'root', },
'root@pam' => { username => 'root' },
'user1@syncedrealm' => {
username => 'user1',
enable => 1,
@ -337,7 +337,7 @@ my $tests = [
'user1@syncedrealm' => 1,
},
},
'group3-syncedrealm' => { users => {}, },
'group3-syncedrealm' => { users => {} },
},
acl_root => {
users => {},