mirror of
https://git.proxmox.com/git/pve-access-control
synced 2025-10-04 08:21:57 +00:00
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:
parent
124e7a199b
commit
9590c6bdfe
@ -14,24 +14,31 @@ use PVE::RESTHandler;
|
|||||||
|
|
||||||
use base qw(PVE::RESTHandler);
|
use base qw(PVE::RESTHandler);
|
||||||
|
|
||||||
register_standard_option('acl-propagate', {
|
register_standard_option(
|
||||||
|
'acl-propagate',
|
||||||
|
{
|
||||||
description => "Allow to propagate (inherit) permissions.",
|
description => "Allow to propagate (inherit) permissions.",
|
||||||
type => 'boolean',
|
type => 'boolean',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 1,
|
default => 1,
|
||||||
});
|
},
|
||||||
register_standard_option('acl-path', {
|
);
|
||||||
|
register_standard_option(
|
||||||
|
'acl-path',
|
||||||
|
{
|
||||||
description => "Access control path",
|
description => "Access control path",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'read_acl',
|
name => 'read_acl',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
description => "Get Access Control List (ACLs).",
|
description => "Get Access Control List (ACLs).",
|
||||||
permissions => {
|
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',
|
user => 'all',
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -67,16 +74,20 @@ __PACKAGE__->register_method ({
|
|||||||
my $audit = $rpcenv->check($authuser, '/access', ['Sys.Audit'], 1);
|
my $audit = $rpcenv->check($authuser, '/access', ['Sys.Audit'], 1);
|
||||||
|
|
||||||
my $root = $usercfg->{acl_root};
|
my $root = $usercfg->{acl_root};
|
||||||
PVE::AccessControl::iterate_acl_tree("/", $root, sub {
|
PVE::AccessControl::iterate_acl_tree(
|
||||||
|
"/",
|
||||||
|
$root,
|
||||||
|
sub {
|
||||||
my ($path, $node) = @_;
|
my ($path, $node) = @_;
|
||||||
foreach my $type (qw(user group token)) {
|
foreach my $type (qw(user group token)) {
|
||||||
my $d = $node->{"${type}s"};
|
my $d = $node->{"${type}s"};
|
||||||
next if !$d;
|
next if !$d;
|
||||||
next if !($audit || $rpcenv->check_perm_modify($authuser, $path, 1));
|
next if !($audit || $rpcenv->check_perm_modify($authuser, $path, 1));
|
||||||
foreach my $id (keys %$d) {
|
foreach my $id (keys %$d) {
|
||||||
foreach my $role (keys %{$d->{$id}}) {
|
foreach my $role (keys %{ $d->{$id} }) {
|
||||||
my $propagate = $d->{$id}->{$role};
|
my $propagate = $d->{$id}->{$role};
|
||||||
push @$res, {
|
push @$res,
|
||||||
|
{
|
||||||
path => $path,
|
path => $path,
|
||||||
type => $type,
|
type => $type,
|
||||||
ugid => $id,
|
ugid => $id,
|
||||||
@ -86,12 +97,14 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'update_acl',
|
name => 'update_acl',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '',
|
path => '',
|
||||||
@ -107,22 +120,26 @@ __PACKAGE__->register_method ({
|
|||||||
path => get_standard_option('acl-path'),
|
path => get_standard_option('acl-path'),
|
||||||
users => {
|
users => {
|
||||||
description => "List of users.",
|
description => "List of users.",
|
||||||
type => 'string', format => 'pve-userid-list',
|
type => 'string',
|
||||||
|
format => 'pve-userid-list',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
groups => {
|
groups => {
|
||||||
description => "List of groups.",
|
description => "List of groups.",
|
||||||
type => 'string', format => 'pve-groupid-list',
|
type => 'string',
|
||||||
|
format => 'pve-groupid-list',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
tokens => {
|
tokens => {
|
||||||
description => "List of API tokens.",
|
description => "List of API tokens.",
|
||||||
type => 'string', format => 'pve-tokenid-list',
|
type => 'string',
|
||||||
|
format => 'pve-tokenid-list',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
roles => {
|
roles => {
|
||||||
description => "List of roles.",
|
description => "List of roles.",
|
||||||
type => 'string', format => 'pve-roleid-list',
|
type => 'string',
|
||||||
|
format => 'pve-roleid-list',
|
||||||
},
|
},
|
||||||
delete => {
|
delete => {
|
||||||
description => "Remove permissions (instead of adding it).",
|
description => "Remove permissions (instead of adding it).",
|
||||||
@ -136,7 +153,10 @@ __PACKAGE__->register_method ({
|
|||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
|
|
||||||
if (!($param->{users} || $param->{groups} || $param->{tokens})) {
|
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});
|
my $path = PVE::AccessControl::normalize_path($param->{path});
|
||||||
@ -173,17 +193,32 @@ __PACKAGE__->register_method ({
|
|||||||
my $role_privs = $cfg->{roles}->{$role};
|
my $role_privs = $cfg->{roles}->{$role};
|
||||||
my $verb = $param->{delete} ? 'remove' : 'add';
|
my $verb = $param->{delete} ? 'remove' : 'add';
|
||||||
foreach my $priv (keys $role_privs->%*) {
|
foreach my $priv (keys $role_privs->%*) {
|
||||||
raise_param_exc({ role => "Cannot $verb role '$role' - requires 'Permissions.Modify' or superset of privileges." })
|
raise_param_exc(
|
||||||
if !defined($auth_user_privs->{$priv});
|
{
|
||||||
|
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..
|
# 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." })
|
raise_param_exc(
|
||||||
if $propagate && $auth_user_privs->{$priv} != $propagate && !$param->{delete};
|
{
|
||||||
|
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
|
# NoAccess has no privs, needs an explicit check
|
||||||
raise_param_exc({ role => "Cannot $verb role '$role' - requires 'Permissions.Modify'"})
|
raise_param_exc(
|
||||||
if $role eq 'NoAccess';
|
{
|
||||||
|
role =>
|
||||||
|
"Cannot $verb role '$role' - requires 'Permissions.Modify'",
|
||||||
|
},
|
||||||
|
) if $role eq 'NoAccess';
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $group (split_list($param->{groups})) {
|
foreach my $group (split_list($param->{groups})) {
|
||||||
@ -205,7 +240,7 @@ __PACKAGE__->register_method ({
|
|||||||
if !$cfg->{users}->{$username};
|
if !$cfg->{users}->{$username};
|
||||||
|
|
||||||
if ($param->{delete}) {
|
if ($param->{delete}) {
|
||||||
delete ($node->{users}->{$username}->{$role});
|
delete($node->{users}->{$username}->{$role});
|
||||||
} else {
|
} else {
|
||||||
$node->{users}->{$username}->{$role} = $propagate;
|
$node->{users}->{$username}->{$role} = $propagate;
|
||||||
}
|
}
|
||||||
@ -224,9 +259,12 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $cfg);
|
cfs_write_file("user.cfg", $cfg);
|
||||||
}, "ACL update failed");
|
},
|
||||||
|
"ACL update failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -32,42 +32,42 @@ eval {
|
|||||||
|
|
||||||
use base qw(PVE::RESTHandler);
|
use base qw(PVE::RESTHandler);
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
subclass => "PVE::API2::User",
|
subclass => "PVE::API2::User",
|
||||||
path => 'users',
|
path => 'users',
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
subclass => "PVE::API2::Group",
|
subclass => "PVE::API2::Group",
|
||||||
path => 'groups',
|
path => 'groups',
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
subclass => "PVE::API2::Role",
|
subclass => "PVE::API2::Role",
|
||||||
path => 'roles',
|
path => 'roles',
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
subclass => "PVE::API2::ACL",
|
subclass => "PVE::API2::ACL",
|
||||||
path => 'acl',
|
path => 'acl',
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
subclass => "PVE::API2::Domains",
|
subclass => "PVE::API2::Domains",
|
||||||
path => 'domains',
|
path => 'domains',
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
subclass => "PVE::API2::OpenId",
|
subclass => "PVE::API2::OpenId",
|
||||||
path => 'openid',
|
path => 'openid',
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
subclass => "PVE::API2::TFA",
|
subclass => "PVE::API2::TFA",
|
||||||
path => 'tfa',
|
path => 'tfa',
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'index',
|
name => 'index',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -87,7 +87,7 @@ __PACKAGE__->register_method ({
|
|||||||
subdir => { type => 'string' },
|
subdir => { type => 'string' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
links => [{ rel => 'child', href => "{subdir}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -108,8 +108,8 @@ __PACKAGE__->register_method ({
|
|||||||
push @$res, { subdir => 'password' };
|
push @$res, { subdir => 'password' };
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
my sub verify_auth : prototype($$$$$$) {
|
my sub verify_auth : prototype($$$$$$) {
|
||||||
my ($rpcenv, $username, $pw_or_ticket, $otp, $path, $privs) = @_;
|
my ($rpcenv, $username, $pw_or_ticket, $otp, $path, $privs) = @_;
|
||||||
@ -118,26 +118,26 @@ my sub verify_auth : prototype($$$$$$) {
|
|||||||
die "invalid path - $path\n" if defined($path) && !defined($normpath);
|
die "invalid path - $path\n" if defined($path) && !defined($normpath);
|
||||||
|
|
||||||
my $ticketuser;
|
my $ticketuser;
|
||||||
if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) &&
|
if (
|
||||||
($ticketuser eq $username)) {
|
($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1))
|
||||||
|
&& ($ticketuser eq $username)
|
||||||
|
) {
|
||||||
# valid ticket
|
# valid ticket
|
||||||
} elsif (PVE::AccessControl::verify_vnc_ticket($pw_or_ticket, $username, $normpath, 1)) {
|
} elsif (PVE::AccessControl::verify_vnc_ticket($pw_or_ticket, $username, $normpath, 1)) {
|
||||||
# valid vnc ticket
|
# valid vnc ticket
|
||||||
} else {
|
} else {
|
||||||
$username = PVE::AccessControl::authenticate_user(
|
$username = PVE::AccessControl::authenticate_user(
|
||||||
$username,
|
$username, $pw_or_ticket, $otp,
|
||||||
$pw_or_ticket,
|
|
||||||
$otp,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $privlist = [ PVE::Tools::split_list($privs) ];
|
my $privlist = [PVE::Tools::split_list($privs)];
|
||||||
if (!($normpath && scalar(@$privlist) && $rpcenv->check($username, $normpath, $privlist))) {
|
if (!($normpath && scalar(@$privlist) && $rpcenv->check($username, $normpath, $privlist))) {
|
||||||
die "no permission ($path, $privs)\n";
|
die "no permission ($path, $privs)\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return { username => $username };
|
return { username => $username };
|
||||||
};
|
}
|
||||||
|
|
||||||
my sub create_ticket_do : prototype($$$$$) {
|
my sub create_ticket_do : prototype($$$$$) {
|
||||||
my ($rpcenv, $username, $pw_or_ticket, $otp, $tfa_challenge) = @_;
|
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
|
# valid ticket. Note: root@pam can create tickets for other users
|
||||||
} else {
|
} else {
|
||||||
($username, $tfa_info) = PVE::AccessControl::authenticate_user(
|
($username, $tfa_info) = PVE::AccessControl::authenticate_user(
|
||||||
$username,
|
$username, $pw_or_ticket, $otp, $tfa_challenge,
|
||||||
$pw_or_ticket,
|
|
||||||
$otp,
|
|
||||||
$tfa_challenge,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,9 +182,9 @@ my sub create_ticket_do : prototype($$$$$) {
|
|||||||
CSRFPreventionToken => $csrftoken,
|
CSRFPreventionToken => $csrftoken,
|
||||||
%extra,
|
%extra,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'get_ticket',
|
name => 'get_ticket',
|
||||||
path => 'ticket',
|
path => 'ticket',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -197,15 +194,16 @@ __PACKAGE__->register_method ({
|
|||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
},
|
},
|
||||||
returns => { type => "null" },
|
returns => { type => "null" },
|
||||||
code => sub { return undef; }});
|
code => sub { return undef; },
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'create_ticket',
|
name => 'create_ticket',
|
||||||
path => 'ticket',
|
path => 'ticket',
|
||||||
method => 'POST',
|
method => 'POST',
|
||||||
permissions => {
|
permissions => {
|
||||||
description => "You need to pass valid credientials.",
|
description => "You need to pass valid credientials.",
|
||||||
user => 'world'
|
user => 'world',
|
||||||
},
|
},
|
||||||
protected => 1, # else we can't access shadow files
|
protected => 1, # else we can't access shadow files
|
||||||
allowtoken => 0, # we don't want tokens to create tickets
|
allowtoken => 0, # we don't want tokens to create tickets
|
||||||
@ -219,12 +217,16 @@ __PACKAGE__->register_method ({
|
|||||||
maxLength => 64,
|
maxLength => 64,
|
||||||
completion => \&PVE::AccessControl::complete_username,
|
completion => \&PVE::AccessControl::complete_username,
|
||||||
},
|
},
|
||||||
realm => get_standard_option('realm', {
|
realm => get_standard_option(
|
||||||
description => "You can optionally pass the realm using this parameter. Normally"
|
'realm',
|
||||||
." the realm is simply added to the username <username>\@<realm>.",
|
{
|
||||||
|
description =>
|
||||||
|
"You can optionally pass the realm using this parameter. Normally"
|
||||||
|
. " the realm is simply added to the username <username>\@<realm>.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
completion => \&PVE::AccessControl::complete_realm,
|
completion => \&PVE::AccessControl::complete_realm,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
password => {
|
password => {
|
||||||
description => "The secret password. This can also be a valid ticket.",
|
description => "The secret password. This can also be a valid ticket.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
@ -243,7 +245,8 @@ __PACKAGE__->register_method ({
|
|||||||
},
|
},
|
||||||
privs => {
|
privs => {
|
||||||
description => "Verify ticket, and check if user have access 'privs' on 'path'",
|
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',
|
requires => 'path',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
maxLength => 64,
|
maxLength => 64,
|
||||||
@ -259,17 +262,17 @@ __PACKAGE__->register_method ({
|
|||||||
description => "The signed TFA challenge string the user wants to respond to.",
|
description => "The signed TFA challenge string the user wants to respond to.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
type => "object",
|
type => "object",
|
||||||
properties => {
|
properties => {
|
||||||
username => { type => 'string' },
|
username => { type => 'string' },
|
||||||
ticket => { type => 'string', optional => 1},
|
ticket => { type => 'string', optional => 1 },
|
||||||
CSRFPreventionToken => { type => 'string', optional => 1 },
|
CSRFPreventionToken => { type => 'string', optional => 1 },
|
||||||
clustername => { type => 'string', optional => 1 },
|
clustername => { type => 'string', optional => 1 },
|
||||||
# cap => computed api permissions, unless there's a u2f challenge
|
# cap => computed api permissions, unless there's a u2f challenge
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -286,8 +289,14 @@ __PACKAGE__->register_method ({
|
|||||||
$rpcenv->check_user_enabled($username);
|
$rpcenv->check_user_enabled($username);
|
||||||
|
|
||||||
if ($param->{path} && $param->{privs}) {
|
if ($param->{path} && $param->{privs}) {
|
||||||
$res = verify_auth($rpcenv, $username, $param->{password}, $param->{otp},
|
$res = verify_auth(
|
||||||
$param->{path}, $param->{privs});
|
$rpcenv,
|
||||||
|
$username,
|
||||||
|
$param->{password},
|
||||||
|
$param->{otp},
|
||||||
|
$param->{path},
|
||||||
|
$param->{privs},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$res = create_ticket_do(
|
$res = create_ticket_do(
|
||||||
$rpcenv,
|
$rpcenv,
|
||||||
@ -316,24 +325,27 @@ __PACKAGE__->register_method ({
|
|||||||
PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
|
PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'change_password',
|
name => 'change_password',
|
||||||
path => 'password',
|
path => 'password',
|
||||||
method => 'PUT',
|
method => 'PUT',
|
||||||
permissions => {
|
permissions => {
|
||||||
description => "Each user is allowed to change their own password. A user can change the"
|
description =>
|
||||||
." password of another user if they have 'Realm.AllocateUser' (on the realm of user"
|
"Each user is allowed to change their own password. A user can change the"
|
||||||
." <userid>) and 'User.Modify' permission on /access/groups/<group> on a group where"
|
. " password of another user if they have 'Realm.AllocateUser' (on the realm of user"
|
||||||
." user <userid> is member of. For the PAM realm, a password change does not take "
|
. " <userid>) and 'User.Modify' permission on /access/groups/<group> on a group where"
|
||||||
." effect cluster-wide, but only applies to the local node.",
|
. " user <userid> is member of. For the PAM realm, a password change does not take "
|
||||||
check => [ 'or',
|
. " effect cluster-wide, but only applies to the local node.",
|
||||||
|
check => [
|
||||||
|
'or',
|
||||||
['userid-param', 'self'],
|
['userid-param', 'self'],
|
||||||
[ 'and',
|
[
|
||||||
[ 'userid-param', 'Realm.AllocateUser'],
|
'and', ['userid-param', 'Realm.AllocateUser'],
|
||||||
[ 'userid-group', ['User.Modify']]
|
['userid-group', ['User.Modify']],
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
protected => 1, # else we can't access shadow files
|
protected => 1, # else we can't access shadow files
|
||||||
@ -350,7 +362,7 @@ __PACKAGE__->register_method ({
|
|||||||
maxLength => 64,
|
maxLength => 64,
|
||||||
},
|
},
|
||||||
'confirmation-password' => $PVE::API2::TFA::OPTIONAL_PASSWORD_SCHEMA,
|
'confirmation-password' => $PVE::API2::TFA::OPTIONAL_PASSWORD_SCHEMA,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
returns => { type => "null" },
|
returns => { type => "null" },
|
||||||
code => sub {
|
code => sub {
|
||||||
@ -385,7 +397,8 @@ __PACKAGE__->register_method ({
|
|||||||
PVE::Cluster::log_msg('info', 'root@pam', "changed password for user '$userid'");
|
PVE::Cluster::log_msg('info', 'root@pam', "changed password for user '$userid'");
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
sub get_u2f_config() {
|
sub get_u2f_config() {
|
||||||
die "u2f support not available\n" if !$u2f_available;
|
die "u2f support not available\n" if !$u2f_available;
|
||||||
@ -458,16 +471,16 @@ sub verify_user_tfa_config {
|
|||||||
PVE::OTP::oath_verify_otp($value, $secret, $step, $digits);
|
PVE::OTP::oath_verify_otp($value, $secret, $step, $digits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'permissions',
|
name => 'permissions',
|
||||||
path => 'permissions',
|
path => 'permissions',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
description => 'Retrieve effective permissions of given user/token.',
|
description => 'Retrieve effective permissions of given user/token.',
|
||||||
permissions => {
|
permissions => {
|
||||||
description => "Each user/token is allowed to dump their own permissions (or that of owned"
|
description =>
|
||||||
." tokens). A user can dump the permissions of another user or their tokens if they"
|
"Each user/token is allowed to dump their own permissions (or that of owned"
|
||||||
." have 'Sys.Audit' permission on /access.",
|
. " tokens). A user can dump the permissions of another user or their tokens if they"
|
||||||
|
. " have 'Sys.Audit' permission on /access.",
|
||||||
user => 'all',
|
user => 'all',
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -479,10 +492,13 @@ __PACKAGE__->register_method({
|
|||||||
pattern => $PVE::AccessControl::userid_or_token_regex,
|
pattern => $PVE::AccessControl::userid_or_token_regex,
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
path => get_standard_option('acl-path', {
|
path => get_standard_option(
|
||||||
|
'acl-path',
|
||||||
|
{
|
||||||
description => "Only dump this specific path, not the whole tree.",
|
description => "Only dump this specific path, not the whole tree.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
@ -519,6 +535,7 @@ __PACKAGE__->register_method({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -48,16 +48,18 @@ my $map_sync_default_options = sub {
|
|||||||
|
|
||||||
my $new_opt = $map_remove_vanished->($old_opt, $delete_deprecated);
|
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 ({
|
__PACKAGE__->register_method({
|
||||||
name => 'index',
|
name => 'index',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
description => "Authentication domain index.",
|
description => "Authentication domain index.",
|
||||||
permissions => {
|
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',
|
user => 'world',
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -74,17 +76,18 @@ __PACKAGE__->register_method ({
|
|||||||
tfa => {
|
tfa => {
|
||||||
description => "Two-factor authentication provider.",
|
description => "Two-factor authentication provider.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
enum => [ 'yubico', 'oath' ],
|
enum => ['yubico', 'oath'],
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
comment => {
|
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',
|
type => 'string',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{realm}" } ],
|
links => [{ rel => 'child', href => "{realm}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -106,9 +109,10 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'create',
|
name => 'create',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '',
|
path => '',
|
||||||
@ -117,14 +121,17 @@ __PACKAGE__->register_method ({
|
|||||||
check => ['perm', '/access/realm', ['Realm.Allocate']],
|
check => ['perm', '/access/realm', ['Realm.Allocate']],
|
||||||
},
|
},
|
||||||
description => "Add an authentication server.",
|
description => "Add an authentication server.",
|
||||||
parameters => PVE::Auth::Plugin->createSchema(0, {
|
parameters => PVE::Auth::Plugin->createSchema(
|
||||||
|
0,
|
||||||
|
{
|
||||||
'check-connection' => {
|
'check-connection' => {
|
||||||
description => 'Check bind connection to the server.',
|
description => 'Check bind connection to the server.',
|
||||||
type => 'boolean',
|
type => 'boolean',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 0,
|
default => 0,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -151,7 +158,8 @@ __PACKAGE__->register_method ({
|
|||||||
die "unable to create builtin type '$type'\n"
|
die "unable to create builtin type '$type'\n"
|
||||||
if ($type eq 'pam' || $type eq 'pve');
|
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 defined($check_connection) && !($type eq 'ldap' || $type eq 'ad');
|
||||||
|
|
||||||
if ($type eq 'ad' || $type eq 'ldap') {
|
if ($type eq 'ad' || $type eq 'ldap') {
|
||||||
@ -181,12 +189,15 @@ __PACKAGE__->register_method ({
|
|||||||
if $check_connection;
|
if $check_connection;
|
||||||
|
|
||||||
cfs_write_file($domainconfigfile, $cfg);
|
cfs_write_file($domainconfigfile, $cfg);
|
||||||
}, "add auth server failed");
|
},
|
||||||
|
"add auth server failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'update',
|
name => 'update',
|
||||||
path => '{realm}',
|
path => '{realm}',
|
||||||
method => 'PUT',
|
method => 'PUT',
|
||||||
@ -195,14 +206,17 @@ __PACKAGE__->register_method ({
|
|||||||
},
|
},
|
||||||
description => "Update authentication server settings.",
|
description => "Update authentication server settings.",
|
||||||
protected => 1,
|
protected => 1,
|
||||||
parameters => PVE::Auth::Plugin->updateSchema(0, {
|
parameters => PVE::Auth::Plugin->updateSchema(
|
||||||
|
0,
|
||||||
|
{
|
||||||
'check-connection' => {
|
'check-connection' => {
|
||||||
description => 'Check bind connection to the server.',
|
description => 'Check bind connection to the server.',
|
||||||
type => 'boolean',
|
type => 'boolean',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 0,
|
default => 0,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -226,7 +240,8 @@ __PACKAGE__->register_method ({
|
|||||||
die "domain '$realm' does not exist\n"
|
die "domain '$realm' does not exist\n"
|
||||||
if !$ids->{$realm};
|
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');
|
if defined($check_connection) && !($type eq 'ldap' || $type eq 'ad');
|
||||||
|
|
||||||
my $delete_str = extract_param($param, 'delete');
|
my $delete_str = extract_param($param, 'delete');
|
||||||
@ -268,13 +283,16 @@ __PACKAGE__->register_method ({
|
|||||||
if $check_connection;
|
if $check_connection;
|
||||||
|
|
||||||
cfs_write_file($domainconfigfile, $cfg);
|
cfs_write_file($domainconfigfile, $cfg);
|
||||||
}, "update auth server failed");
|
},
|
||||||
|
"update auth server failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
# fixme: return format!
|
# fixme: return format!
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'read',
|
name => 'read',
|
||||||
path => '{realm}',
|
path => '{realm}',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -307,10 +325,10 @@ __PACKAGE__->register_method ({
|
|||||||
$data->{digest} = $cfg->{digest};
|
$data->{digest} = $cfg->{digest};
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
__PACKAGE__->register_method({
|
||||||
__PACKAGE__->register_method ({
|
|
||||||
name => 'delete',
|
name => 'delete',
|
||||||
path => '{realm}',
|
path => '{realm}',
|
||||||
method => 'DELETE',
|
method => 'DELETE',
|
||||||
@ -323,7 +341,7 @@ __PACKAGE__->register_method ({
|
|||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
realm => get_standard_option('realm'),
|
realm => get_standard_option('realm'),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
@ -345,10 +363,13 @@ __PACKAGE__->register_method ({
|
|||||||
delete $ids->{$realm};
|
delete $ids->{$realm};
|
||||||
|
|
||||||
cfs_write_file($domainconfigfile, $cfg);
|
cfs_write_file($domainconfigfile, $cfg);
|
||||||
}, "delete auth server failed");
|
},
|
||||||
|
"delete auth server failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
my $update_users = sub {
|
my $update_users = sub {
|
||||||
my ($usercfg, $realm, $synced_users, $opts) = @_;
|
my ($usercfg, $realm, $synced_users, $opts) = @_;
|
||||||
@ -474,31 +495,35 @@ my $parse_sync_opts = sub {
|
|||||||
|
|
||||||
# only scope has no implicit value
|
# only scope has no implicit value
|
||||||
raise_param_exc({
|
raise_param_exc({
|
||||||
"scope" => 'Not passed as parameter and not defined in realm default sync options.'
|
"scope" => 'Not passed as parameter and not defined in realm default sync options.',
|
||||||
}) if !defined($res->{scope});
|
})
|
||||||
|
if !defined($res->{scope});
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
};
|
};
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'sync',
|
name => 'sync',
|
||||||
path => '{realm}/sync',
|
path => '{realm}/sync',
|
||||||
method => 'POST',
|
method => 'POST',
|
||||||
permissions => {
|
permissions => {
|
||||||
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
|
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
|
||||||
." 'User.Modify' permissions to '/access/groups/'.",
|
. " 'User.Modify' permissions to '/access/groups/'.",
|
||||||
check => [ 'and',
|
check => [
|
||||||
|
'and',
|
||||||
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
|
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
|
||||||
['perm', '/access/groups', ['User.Modify']],
|
['perm', '/access/groups', ['User.Modify']],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description => "Syncs users and/or groups from the configured LDAP to user.cfg."
|
description => "Syncs users and/or groups from the configured LDAP to user.cfg."
|
||||||
." NOTE: Synced groups will have the name 'name-\$realm', so make sure"
|
. " NOTE: Synced groups will have the name 'name-\$realm', so make sure"
|
||||||
." those groups do not exist to prevent overwriting.",
|
. " those groups do not exist to prevent overwriting.",
|
||||||
protected => 1,
|
protected => 1,
|
||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => get_standard_option('realm-sync-options', {
|
properties => get_standard_option(
|
||||||
|
'realm-sync-options',
|
||||||
|
{
|
||||||
realm => get_standard_option('realm'),
|
realm => get_standard_option('realm'),
|
||||||
'dry-run' => {
|
'dry-run' => {
|
||||||
description => "If set, does not write anything.",
|
description => "If set, does not write anything.",
|
||||||
@ -506,11 +531,12 @@ __PACKAGE__->register_method ({
|
|||||||
optional => 1,
|
optional => 1,
|
||||||
default => 0,
|
default => 0,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
description => 'Worker Task-UPID',
|
description => 'Worker Task-UPID',
|
||||||
type => 'string'
|
type => 'string',
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -547,7 +573,8 @@ __PACKAGE__->register_method ({
|
|||||||
$synced_groups = $plugin->get_groups($realmconfig, $realm, $dnmap);
|
$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");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
print "got data from server, updating $whatstring\n";
|
print "got data from server, updating $whatstring\n";
|
||||||
|
|
||||||
@ -560,16 +587,20 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($dry_run) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
print "successfully updated $whatstring configuration\n";
|
print "successfully updated $whatstring configuration\n";
|
||||||
}, "syncing $whatstring failed");
|
},
|
||||||
|
"syncing $whatstring failed",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
my $workerid = !$dry_run ? 'auth-realm-sync' : 'auth-realm-sync-test';
|
my $workerid = !$dry_run ? 'auth-realm-sync' : 'auth-realm-sync-test';
|
||||||
return $rpcenv->fork_worker($workerid, $realm, $authuser, $worker);
|
return $rpcenv->fork_worker($workerid, $realm, $authuser, $worker);
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -10,21 +10,25 @@ use PVE::JSONSchema qw(get_standard_option register_standard_option);
|
|||||||
|
|
||||||
use base qw(PVE::RESTHandler);
|
use base qw(PVE::RESTHandler);
|
||||||
|
|
||||||
register_standard_option('group-id', {
|
register_standard_option(
|
||||||
|
'group-id',
|
||||||
|
{
|
||||||
type => 'string',
|
type => 'string',
|
||||||
format => 'pve-groupid',
|
format => 'pve-groupid',
|
||||||
completion => \&PVE::AccessControl::complete_group,
|
completion => \&PVE::AccessControl::complete_group,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
register_standard_option('group-comment', { type => 'string', optional => 1 });
|
register_standard_option('group-comment', { type => 'string', optional => 1 });
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'index',
|
name => 'index',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
description => "Group index.",
|
description => "Group index.",
|
||||||
permissions => {
|
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',
|
user => 'all',
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -46,7 +50,7 @@ __PACKAGE__->register_method ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{groupid}" } ],
|
links => [{ rel => 'child', href => "{groupid}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -57,21 +61,23 @@ __PACKAGE__->register_method ({
|
|||||||
my $usercfg = cfs_read_file("user.cfg");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
my $authuser = $rpcenv->get_user();
|
my $authuser = $rpcenv->get_user();
|
||||||
|
|
||||||
my $privs = [ 'User.Modify', 'Sys.Audit', 'Group.Allocate'];
|
my $privs = ['User.Modify', 'Sys.Audit', 'Group.Allocate'];
|
||||||
|
|
||||||
foreach my $group (keys %{$usercfg->{groups}}) {
|
foreach my $group (keys %{ $usercfg->{groups} }) {
|
||||||
next if !$rpcenv->check_any($authuser, "/access/groups/$group", $privs, 1);
|
next if !$rpcenv->check_any($authuser, "/access/groups/$group", $privs, 1);
|
||||||
my $data = $usercfg->{groups}->{$group};
|
my $data = $usercfg->{groups}->{$group};
|
||||||
my $entry = { groupid => $group };
|
my $entry = { groupid => $group };
|
||||||
$entry->{comment} = $data->{comment} if defined($data->{comment});
|
$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;
|
push @$res, $entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'create_group',
|
name => 'create_group',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '',
|
path => '',
|
||||||
@ -103,16 +109,19 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
$usercfg->{groups}->{$group} = { users => {} };
|
$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);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "create group failed");
|
},
|
||||||
|
"create group failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'update_group',
|
name => 'update_group',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '{groupid}',
|
path => '{groupid}',
|
||||||
@ -147,12 +156,15 @@ __PACKAGE__->register_method ({
|
|||||||
$data->{comment} = $param->{comment} if defined($param->{comment});
|
$data->{comment} = $param->{comment} if defined($param->{comment});
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "update group failed");
|
},
|
||||||
|
"update group failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'read_group',
|
name => 'read_group',
|
||||||
path => '{groupid}',
|
path => '{groupid}',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -173,7 +185,7 @@ __PACKAGE__->register_method ({
|
|||||||
comment => get_standard_option('group-comment'),
|
comment => get_standard_option('group-comment'),
|
||||||
members => {
|
members => {
|
||||||
type => 'array',
|
type => 'array',
|
||||||
items => get_standard_option('userid-completed')
|
items => get_standard_option('userid-completed'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -188,17 +200,17 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
die "group '$group' does not exist\n" if !$data;
|
die "group '$group' does not exist\n" if !$data;
|
||||||
|
|
||||||
my $members = $data->{users} ? [ keys %{$data->{users}} ] : [];
|
my $members = $data->{users} ? [keys %{ $data->{users} }] : [];
|
||||||
|
|
||||||
my $res = { members => $members };
|
my $res = { members => $members };
|
||||||
|
|
||||||
$res->{comment} = $data->{comment} if defined($data->{comment});
|
$res->{comment} = $data->{comment} if defined($data->{comment});
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
__PACKAGE__->register_method({
|
||||||
__PACKAGE__->register_method ({
|
|
||||||
name => 'delete_group',
|
name => 'delete_group',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '{groupid}',
|
path => '{groupid}',
|
||||||
@ -211,7 +223,7 @@ __PACKAGE__->register_method ({
|
|||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
groupid => get_standard_option('group-id'),
|
groupid => get_standard_option('group-id'),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
@ -227,14 +239,17 @@ __PACKAGE__->register_method ({
|
|||||||
die "group '$group' does not exist\n"
|
die "group '$group' does not exist\n"
|
||||||
if !$usercfg->{groups}->{$group};
|
if !$usercfg->{groups}->{$group};
|
||||||
|
|
||||||
delete ($usercfg->{groups}->{$group});
|
delete($usercfg->{groups}->{$group});
|
||||||
|
|
||||||
PVE::AccessControl::delete_group_acl($group, $usercfg);
|
PVE::AccessControl::delete_group_acl($group, $usercfg);
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "delete group failed");
|
},
|
||||||
|
"delete group failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -31,7 +31,7 @@ my $get_cluster_last_run = sub {
|
|||||||
return undef;
|
return undef;
|
||||||
};
|
};
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'syncjob_index',
|
name => 'syncjob_index',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -50,7 +50,7 @@ __PACKAGE__->register_method ({
|
|||||||
properties => {
|
properties => {
|
||||||
id => {
|
id => {
|
||||||
description => "The ID of the entry.",
|
description => "The ID of the entry.",
|
||||||
type => 'string'
|
type => 'string',
|
||||||
},
|
},
|
||||||
enabled => {
|
enabled => {
|
||||||
description => "If the job is enabled or not.",
|
description => "If the job is enabled or not.",
|
||||||
@ -69,18 +69,20 @@ __PACKAGE__->register_method ({
|
|||||||
scope => get_standard_option('sync-scope'),
|
scope => get_standard_option('sync-scope'),
|
||||||
'remove-vanished' => get_standard_option('sync-remove-vanished'),
|
'remove-vanished' => get_standard_option('sync-remove-vanished'),
|
||||||
'last-run' => {
|
'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',
|
type => 'integer',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
'next-run' => {
|
'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',
|
type => 'integer',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{id}" } ],
|
links => [{ rel => 'child', href => "{id}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -111,7 +113,8 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'read_job',
|
name => 'read_job',
|
||||||
@ -143,7 +146,8 @@ __PACKAGE__->register_method({
|
|||||||
|
|
||||||
raise_param_exc({ id => "No such job '$id'" });
|
raise_param_exc({ id => "No such job '$id'" });
|
||||||
|
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'create_job',
|
name => 'create_job',
|
||||||
@ -153,8 +157,9 @@ __PACKAGE__->register_method({
|
|||||||
description => "Create new realm-sync job.",
|
description => "Create new realm-sync job.",
|
||||||
permissions => {
|
permissions => {
|
||||||
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
|
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and "
|
||||||
."'User.Modify' permissions to '/access/groups/'.",
|
. "'User.Modify' permissions to '/access/groups/'.",
|
||||||
check => [ 'and',
|
check => [
|
||||||
|
'and',
|
||||||
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
|
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
|
||||||
['perm', '/access/groups', ['User.Modify']],
|
['perm', '/access/groups', ['User.Modify']],
|
||||||
],
|
],
|
||||||
@ -166,7 +171,10 @@ __PACKAGE__->register_method({
|
|||||||
|
|
||||||
my $id = extract_param($param, 'id');
|
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');
|
my $data = cfs_read_file('jobs.cfg');
|
||||||
|
|
||||||
die "Job '$id' already exists\n"
|
die "Job '$id' already exists\n"
|
||||||
@ -188,11 +196,13 @@ __PACKAGE__->register_method({
|
|||||||
$data->{ids}->{$id} = $opts;
|
$data->{ids}->{$id} = $opts;
|
||||||
|
|
||||||
cfs_write_file('jobs.cfg', $data);
|
cfs_write_file('jobs.cfg', $data);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
die "$@" if ($@);
|
die "$@" if ($@);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'update_job',
|
name => 'update_job',
|
||||||
@ -202,8 +212,9 @@ __PACKAGE__->register_method({
|
|||||||
description => "Update realm-sync job definition.",
|
description => "Update realm-sync job definition.",
|
||||||
permissions => {
|
permissions => {
|
||||||
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and 'User.Modify'"
|
description => "'Realm.AllocateUser' on '/access/realm/<realm>' and 'User.Modify'"
|
||||||
." permissions to '/access/groups/'.",
|
. " permissions to '/access/groups/'.",
|
||||||
check => [ 'and',
|
check => [
|
||||||
|
'and',
|
||||||
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
|
['perm', '/access/realm/{realm}', ['Realm.AllocateUser']],
|
||||||
['perm', '/access/groups', ['User.Modify']],
|
['perm', '/access/groups', ['User.Modify']],
|
||||||
],
|
],
|
||||||
@ -219,7 +230,10 @@ __PACKAGE__->register_method({
|
|||||||
|
|
||||||
die "no job options specified\n" if !scalar(keys %$param);
|
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 $jobs = cfs_read_file('jobs.cfg');
|
||||||
|
|
||||||
my $plugin = PVE::Job::Registry->lookup('realm-sync');
|
my $plugin = PVE::Job::Registry->lookup('realm-sync');
|
||||||
@ -236,10 +250,11 @@ __PACKAGE__->register_method({
|
|||||||
cfs_write_file('jobs.cfg', $jobs);
|
cfs_write_file('jobs.cfg', $jobs);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
die "$@" if ($@);
|
die "$@" if ($@);
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'delete_job',
|
name => 'delete_job',
|
||||||
@ -265,20 +280,28 @@ __PACKAGE__->register_method({
|
|||||||
|
|
||||||
my $id = $param->{id};
|
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');
|
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'" });
|
raise_param_exc({ id => "No such job '$id'" });
|
||||||
}
|
}
|
||||||
delete $jobs->{ids}->{$id};
|
delete $jobs->{ids}->{$id};
|
||||||
|
|
||||||
cfs_write_file('jobs.cfg', $jobs);
|
cfs_write_file('jobs.cfg', $jobs);
|
||||||
PVE::Jobs::RealmSync::save_state($id, undef);
|
PVE::Jobs::RealmSync::save_state($id, undef);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
die "$@" if $@;
|
die "$@" if $@;
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -40,17 +40,17 @@ my $lookup_openid_auth = sub {
|
|||||||
$openid_config->{prompt} = $config->{'prompt'} if defined($config->{'prompt'});
|
$openid_config->{prompt} = $config->{'prompt'} if defined($config->{'prompt'});
|
||||||
|
|
||||||
my $scopes = $config->{'scopes'} // 'email profile';
|
my $scopes = $config->{'scopes'} // 'email profile';
|
||||||
$openid_config->{scopes} = [ PVE::Tools::split_list($scopes) ];
|
$openid_config->{scopes} = [PVE::Tools::split_list($scopes)];
|
||||||
|
|
||||||
if (defined(my $acr = $config->{'acr-values'})) {
|
if (defined(my $acr = $config->{'acr-values'})) {
|
||||||
$openid_config->{acr_values} = [ PVE::Tools::split_list($acr) ];
|
$openid_config->{acr_values} = [PVE::Tools::split_list($acr)];
|
||||||
}
|
}
|
||||||
|
|
||||||
my $openid = PVE::RS::OpenId->discover($openid_config, $redirect_url);
|
my $openid = PVE::RS::OpenId->discover($openid_config, $redirect_url);
|
||||||
return ($config, $openid);
|
return ($config, $openid);
|
||||||
};
|
};
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'index',
|
name => 'index',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -70,18 +70,18 @@ __PACKAGE__->register_method ({
|
|||||||
subdir => { type => 'string' },
|
subdir => { type => 'string' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
links => [{ rel => 'child', href => "{subdir}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ subdir => 'auth-url' },
|
{ subdir => 'auth-url' }, { subdir => 'login' },
|
||||||
{ subdir => 'login' },
|
|
||||||
];
|
];
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'auth_url',
|
name => 'auth_url',
|
||||||
path => 'auth-url',
|
path => 'auth-url',
|
||||||
method => 'POST',
|
method => 'POST',
|
||||||
@ -92,7 +92,8 @@ __PACKAGE__->register_method ({
|
|||||||
properties => {
|
properties => {
|
||||||
realm => get_standard_option('realm'),
|
realm => get_standard_option('realm'),
|
||||||
'redirect-url' => {
|
'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',
|
type => 'string',
|
||||||
maxLength => 255,
|
maxLength => 255,
|
||||||
},
|
},
|
||||||
@ -113,12 +114,13 @@ __PACKAGE__->register_method ({
|
|||||||
my $redirect_url = extract_param($param, 'redirect-url');
|
my $redirect_url = extract_param($param, 'redirect-url');
|
||||||
|
|
||||||
my ($config, $openid) = $lookup_openid_auth->($realm, $redirect_url);
|
my ($config, $openid) = $lookup_openid_auth->($realm, $redirect_url);
|
||||||
my $url = $openid->authorize_url($openid_state_path , $realm);
|
my $url = $openid->authorize_url($openid_state_path, $realm);
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'login',
|
name => 'login',
|
||||||
path => 'login',
|
path => 'login',
|
||||||
method => 'POST',
|
method => 'POST',
|
||||||
@ -138,7 +140,8 @@ __PACKAGE__->register_method ({
|
|||||||
maxLength => 4096,
|
maxLength => 4096,
|
||||||
},
|
},
|
||||||
'redirect-url' => {
|
'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',
|
type => 'string',
|
||||||
maxLength => 255,
|
maxLength => 255,
|
||||||
},
|
},
|
||||||
@ -164,8 +167,8 @@ __PACKAGE__->register_method ({
|
|||||||
my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||||
local $ENV{all_proxy} = $dcconf->{http_proxy} if exists $dcconf->{http_proxy};
|
local $ENV{all_proxy} = $dcconf->{http_proxy} if exists $dcconf->{http_proxy};
|
||||||
|
|
||||||
my ($realm, $private_auth_state) = PVE::RS::OpenId::verify_public_auth_state(
|
my ($realm, $private_auth_state) =
|
||||||
$openid_state_path, $param->{'state'});
|
PVE::RS::OpenId::verify_public_auth_state($openid_state_path, $param->{'state'});
|
||||||
|
|
||||||
my $redirect_url = extract_param($param, 'redirect-url');
|
my $redirect_url = extract_param($param, 'redirect-url');
|
||||||
|
|
||||||
@ -200,10 +203,12 @@ __PACKAGE__->register_method ({
|
|||||||
PVE::Auth::Plugin::verify_username($username);
|
PVE::Auth::Plugin::verify_username($username);
|
||||||
|
|
||||||
if ($config->{'autocreate'} && !$rpcenv->check_user_exist($username, 1)) {
|
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");
|
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 };
|
my $entry = { enable => 1 };
|
||||||
if (defined(my $email = $info->{'email'})) {
|
if (defined(my $email = $info->{'email'})) {
|
||||||
@ -219,7 +224,9 @@ __PACKAGE__->register_method ({
|
|||||||
$usercfg->{users}->{$username} = $entry;
|
$usercfg->{users}->{$username} = $entry;
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "autocreate openid user failed");
|
},
|
||||||
|
"autocreate openid user failed",
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
# test if user exists and is enabled
|
# test if user exists and is enabled
|
||||||
$rpcenv->check_user_enabled($username);
|
$rpcenv->check_user_enabled($username);
|
||||||
@ -228,7 +235,8 @@ __PACKAGE__->register_method ({
|
|||||||
if (defined(my $groups_claim = $config->{'groups-claim'})) {
|
if (defined(my $groups_claim = $config->{'groups-claim'})) {
|
||||||
if (defined(my $groups_list = $info->{$groups_claim})) {
|
if (defined(my $groups_list = $info->{$groups_claim})) {
|
||||||
if (ref($groups_list) eq 'ARRAY') {
|
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 $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
my $oidc_groups;
|
my $oidc_groups;
|
||||||
@ -240,7 +248,7 @@ __PACKAGE__->register_method ({
|
|||||||
# ignore any groups in the list that have invalid characters
|
# ignore any groups in the list that have invalid characters
|
||||||
syslog(
|
syslog(
|
||||||
'warn',
|
'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
|
# get groups that exist in OIDC and PVE
|
||||||
my $groups_intersect;
|
my $groups_intersect;
|
||||||
for my $group (keys %$oidc_groups) {
|
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'}) {
|
if ($config->{'groups-autocreate'}) {
|
||||||
@ -256,27 +265,28 @@ __PACKAGE__->register_method ({
|
|||||||
$groups_intersect = $oidc_groups;
|
$groups_intersect = $oidc_groups;
|
||||||
my $groups_to_create;
|
my $groups_to_create;
|
||||||
for my $group (keys %$oidc_groups) {
|
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) {
|
if ($groups_to_create) {
|
||||||
# log a messages about created groups here
|
# 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(
|
syslog(
|
||||||
'info',
|
'info',
|
||||||
"groups created automatically from openid claim: $groups_to_create_string"
|
"groups created automatically from openid claim: $groups_to_create_string",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# if groups should be overwritten, delete all the users groups first
|
# if groups should be overwritten, delete all the users groups first
|
||||||
if ($config->{'groups-overwrite'} ) {
|
if ($config->{'groups-overwrite'}) {
|
||||||
PVE::AccessControl::delete_user_group(
|
PVE::AccessControl::delete_user_group(
|
||||||
$username,
|
$username, $usercfg,
|
||||||
$usercfg,
|
|
||||||
);
|
);
|
||||||
syslog(
|
syslog(
|
||||||
'info',
|
'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(
|
PVE::AccessControl::add_user_group(
|
||||||
$username,
|
$username,
|
||||||
$usercfg,
|
$usercfg,
|
||||||
$group
|
$group,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $groups_intersect_string = join(', ', sort keys %$groups_intersect);
|
my $groups_intersect_string =
|
||||||
|
join(', ', sort keys %$groups_intersect);
|
||||||
syslog(
|
syslog(
|
||||||
'info',
|
'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);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "openid group mapping failed");
|
},
|
||||||
|
"openid group mapping failed",
|
||||||
|
);
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
syslog('err', "openid groups claim '$groups_claim' is not found in claims");
|
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();
|
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};
|
$res->{clustername} = $clinfo->{cluster}->{name};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -330,7 +348,12 @@ __PACKAGE__->register_method ({
|
|||||||
die PVE::Exception->new("authentication failure\n", code => 401);
|
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;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
@ -10,17 +10,23 @@ use PVE::JSONSchema qw(get_standard_option register_standard_option);
|
|||||||
|
|
||||||
use base qw(PVE::RESTHandler);
|
use base qw(PVE::RESTHandler);
|
||||||
|
|
||||||
register_standard_option('role-id', {
|
register_standard_option(
|
||||||
|
'role-id',
|
||||||
|
{
|
||||||
type => 'string',
|
type => 'string',
|
||||||
format => 'pve-roleid',
|
format => 'pve-roleid',
|
||||||
});
|
},
|
||||||
register_standard_option('role-privs', {
|
);
|
||||||
type => 'string' ,
|
register_standard_option(
|
||||||
|
'role-privs',
|
||||||
|
{
|
||||||
|
type => 'string',
|
||||||
format => 'pve-priv-list',
|
format => 'pve-priv-list',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'index',
|
name => 'index',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -42,7 +48,7 @@ __PACKAGE__->register_method ({
|
|||||||
special => { type => 'boolean', optional => 1, default => 0 },
|
special => { type => 'boolean', optional => 1, default => 0 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{roleid}" } ],
|
links => [{ rel => 'child', href => "{roleid}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -51,9 +57,10 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
my $usercfg = cfs_read_file("user.cfg");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
foreach my $role (keys %{$usercfg->{roles}}) {
|
foreach my $role (keys %{ $usercfg->{roles} }) {
|
||||||
my $privs = join(',', sort keys %{$usercfg->{roles}->{$role}});
|
my $privs = join(',', sort keys %{ $usercfg->{roles}->{$role} });
|
||||||
push @$res, {
|
push @$res,
|
||||||
|
{
|
||||||
roleid => $role,
|
roleid => $role,
|
||||||
privs => $privs,
|
privs => $privs,
|
||||||
special => PVE::AccessControl::role_is_special($role),
|
special => PVE::AccessControl::role_is_special($role),
|
||||||
@ -61,9 +68,10 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'create_role',
|
name => 'create_role',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '',
|
path => '',
|
||||||
@ -87,11 +95,13 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
if ($role =~ /^PVE/i) {
|
if ($role =~ /^PVE/i) {
|
||||||
raise_param_exc({
|
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");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
die "role '$role' already exists\n" if $usercfg->{roles}->{$role};
|
die "role '$role' already exists\n" if $usercfg->{roles}->{$role};
|
||||||
@ -101,12 +111,15 @@ __PACKAGE__->register_method ({
|
|||||||
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
|
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "create role failed");
|
},
|
||||||
|
"create role failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'update_role',
|
name => 'update_role',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '{roleid}',
|
path => '{roleid}',
|
||||||
@ -132,7 +145,8 @@ __PACKAGE__->register_method ({
|
|||||||
die "auto-generated role '$role' cannot be modified\n"
|
die "auto-generated role '$role' cannot be modified\n"
|
||||||
if PVE::AccessControl::role_is_special($role);
|
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");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
|
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
|
||||||
@ -142,12 +156,15 @@ __PACKAGE__->register_method ({
|
|||||||
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
|
PVE::AccessControl::add_role_privs($role, $usercfg, $param->{privs});
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "update role failed");
|
},
|
||||||
|
"update role failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'read_role',
|
name => 'read_role',
|
||||||
path => '{roleid}',
|
path => '{roleid}',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -178,10 +195,10 @@ __PACKAGE__->register_method ({
|
|||||||
die "role '$role' does not exist\n" if !$data;
|
die "role '$role' does not exist\n" if !$data;
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'delete_role',
|
name => 'delete_role',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '{roleid}',
|
path => '{roleid}',
|
||||||
@ -205,20 +222,23 @@ __PACKAGE__->register_method ({
|
|||||||
die "auto-generated role '$role' cannot be deleted\n"
|
die "auto-generated role '$role' cannot be deleted\n"
|
||||||
if PVE::AccessControl::role_is_special($role);
|
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");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
|
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
|
||||||
|
|
||||||
delete ($usercfg->{roles}->{$role});
|
delete($usercfg->{roles}->{$role});
|
||||||
|
|
||||||
# fixme: delete role from acl?
|
# fixme: delete role from acl?
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "delete role failed");
|
},
|
||||||
|
"delete role failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -23,7 +23,7 @@ our $OPTIONAL_PASSWORD_SCHEMA = {
|
|||||||
type => 'string',
|
type => 'string',
|
||||||
optional => 1, # Only required if not root@pam
|
optional => 1, # Only required if not root@pam
|
||||||
minLength => 5,
|
minLength => 5,
|
||||||
maxLength => 64
|
maxLength => 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
my $TFA_TYPE_SCHEMA = {
|
my $TFA_TYPE_SCHEMA = {
|
||||||
@ -79,17 +79,17 @@ my $TFA_UPDATE_INFO_SCHEMA = {
|
|||||||
optional => 1,
|
optional => 1,
|
||||||
description =>
|
description =>
|
||||||
'When adding u2f entries, this contains a challenge the user must respond to in order'
|
'When adding u2f entries, this contains a challenge the user must respond to in order'
|
||||||
.' to finish the registration.'
|
. ' to finish the registration.',
|
||||||
},
|
},
|
||||||
recovery => {
|
recovery => {
|
||||||
type => 'array',
|
type => 'array',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
description =>
|
description =>
|
||||||
'When adding recovery codes, this contains the list of codes to be displayed to'
|
'When adding recovery codes, this contains the list of codes to be displayed to'
|
||||||
.' the user',
|
. ' the user',
|
||||||
items => {
|
items => {
|
||||||
type => 'string',
|
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 sub set_user_tfa_enabled : prototype($$$) {
|
||||||
my ($userid, $realm, $tfa_cfg) = @_;
|
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_cfg = cfs_read_file('user.cfg');
|
||||||
my $user = $user_cfg->{users}->{$userid};
|
my $user = $user_cfg->{users}->{$userid};
|
||||||
my $keys = $user->{keys};
|
my $keys = $user->{keys};
|
||||||
@ -115,21 +116,24 @@ my sub set_user_tfa_enabled : prototype($$$) {
|
|||||||
my $realm_tfa = $realm_cfg->{tfa};
|
my $realm_tfa = $realm_cfg->{tfa};
|
||||||
$realm_tfa = PVE::Auth::Plugin::parse_tfa_config($realm_tfa) if $realm_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;
|
$user->{keys} = $tfa_cfg ? 'x' : undef;
|
||||||
cfs_write_file("user.cfg", $user_cfg);
|
cfs_write_file("user.cfg", $user_cfg);
|
||||||
}, "enabling TFA for the user failed");
|
},
|
||||||
|
"enabling TFA for the user failed",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'list_user_tfa',
|
name => 'list_user_tfa',
|
||||||
path => '{userid}',
|
path => '{userid}',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'or',
|
check => [
|
||||||
['userid-param', 'self'],
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify', 'Sys.Audit']],
|
||||||
['userid-group', ['User.Modify', 'Sys.Audit']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
protected => 1, # else we can't access shadow files
|
protected => 1, # else we can't access shadow files
|
||||||
@ -137,31 +141,34 @@ __PACKAGE__->register_method ({
|
|||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
userid => get_standard_option('userid', {
|
userid => get_standard_option(
|
||||||
|
'userid',
|
||||||
|
{
|
||||||
completion => \&PVE::AccessControl::complete_username,
|
completion => \&PVE::AccessControl::complete_username,
|
||||||
}),
|
},
|
||||||
}
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
description => "A list of the user's TFA entries.",
|
description => "A list of the user's TFA entries.",
|
||||||
type => 'array',
|
type => 'array',
|
||||||
items => $TYPED_TFA_ENTRY_SCHEMA,
|
items => $TYPED_TFA_ENTRY_SCHEMA,
|
||||||
links => [ { rel => 'child', href => "{id}" } ],
|
links => [{ rel => 'child', href => "{id}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
||||||
return $tfa_cfg->api_list_user_tfa($param->{userid});
|
return $tfa_cfg->api_list_user_tfa($param->{userid});
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'get_tfa_entry',
|
name => 'get_tfa_entry',
|
||||||
path => '{userid}/{id}',
|
path => '{userid}/{id}',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'or',
|
check => [
|
||||||
['userid-param', 'self'],
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify', 'Sys.Audit']],
|
||||||
['userid-group', ['User.Modify', 'Sys.Audit']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
protected => 1, # else we can't access shadow files
|
protected => 1, # else we can't access shadow files
|
||||||
@ -169,11 +176,14 @@ __PACKAGE__->register_method ({
|
|||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
userid => get_standard_option('userid', {
|
userid => get_standard_option(
|
||||||
|
'userid',
|
||||||
|
{
|
||||||
completion => \&PVE::AccessControl::complete_username,
|
completion => \&PVE::AccessControl::complete_username,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
id => $TFA_ID_SCHEMA,
|
id => $TFA_ID_SCHEMA,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
returns => $TYPED_TFA_ENTRY_SCHEMA,
|
returns => $TYPED_TFA_ENTRY_SCHEMA,
|
||||||
code => sub {
|
code => sub {
|
||||||
@ -183,16 +193,16 @@ __PACKAGE__->register_method ({
|
|||||||
my $entry = $tfa_cfg->api_get_tfa_entry($param->{userid}, $id);
|
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;
|
raise("No such tfa entry '$id'", code => HTTP::Status::HTTP_NOT_FOUND) if !$entry;
|
||||||
return $entry;
|
return $entry;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'delete_tfa',
|
name => 'delete_tfa',
|
||||||
path => '{userid}/{id}',
|
path => '{userid}/{id}',
|
||||||
method => 'DELETE',
|
method => 'DELETE',
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'or',
|
check => [
|
||||||
['userid-param', 'self'],
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
protected => 1, # else we can't access shadow files
|
protected => 1, # else we can't access shadow files
|
||||||
@ -201,12 +211,15 @@ __PACKAGE__->register_method ({
|
|||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
userid => get_standard_option('userid', {
|
userid => get_standard_option(
|
||||||
|
'userid',
|
||||||
|
{
|
||||||
completion => \&PVE::AccessControl::complete_username,
|
completion => \&PVE::AccessControl::complete_username,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
id => $TFA_ID_SCHEMA,
|
id => $TFA_ID_SCHEMA,
|
||||||
password => $OPTIONAL_PASSWORD_SCHEMA,
|
password => $OPTIONAL_PASSWORD_SCHEMA,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
@ -215,9 +228,7 @@ __PACKAGE__->register_method ({
|
|||||||
my $rpcenv = PVE::RPCEnvironment::get();
|
my $rpcenv = PVE::RPCEnvironment::get();
|
||||||
my $authuser = $rpcenv->get_user();
|
my $authuser = $rpcenv->get_user();
|
||||||
my $userid = $rpcenv->reauth_user_for_user_modification(
|
my $userid = $rpcenv->reauth_user_for_user_modification(
|
||||||
$authuser,
|
$authuser, $param->{userid}, $param->{password},
|
||||||
$param->{userid},
|
|
||||||
$param->{password},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
my $has_entries_left = PVE::AccessControl::lock_tfa_config(sub {
|
my $has_entries_left = PVE::AccessControl::lock_tfa_config(sub {
|
||||||
@ -229,9 +240,10 @@ __PACKAGE__->register_method ({
|
|||||||
if (!$has_entries_left) {
|
if (!$has_entries_left) {
|
||||||
set_user_tfa_enabled($userid, undef, undef);
|
set_user_tfa_enabled($userid, undef, undef);
|
||||||
}
|
}
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'list_tfa',
|
name => 'list_tfa',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -243,7 +255,7 @@ __PACKAGE__->register_method ({
|
|||||||
description => 'List TFA configurations of users.',
|
description => 'List TFA configurations of users.',
|
||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {}
|
properties => {},
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
description => "The list tuples of user and TFA entries.",
|
description => "The list tuples of user and TFA entries.",
|
||||||
@ -272,7 +284,7 @@ __PACKAGE__->register_method ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{userid}" } ],
|
links => [{ rel => 'child', href => "{userid}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -283,7 +295,7 @@ __PACKAGE__->register_method ({
|
|||||||
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
||||||
my $entries = $tfa_cfg->api_list_tfa($authuser, 1);
|
my $entries = $tfa_cfg->api_list_tfa($authuser, 1);
|
||||||
|
|
||||||
my $privs = [ 'User.Modify', 'Sys.Audit' ];
|
my $privs = ['User.Modify', 'Sys.Audit'];
|
||||||
if ($rpcenv->check_any($authuser, "/access/groups", $privs, 1)) {
|
if ($rpcenv->check_any($authuser, "/access/groups", $privs, 1)) {
|
||||||
# can modify all
|
# can modify all
|
||||||
return $entries;
|
return $entries;
|
||||||
@ -297,16 +309,16 @@ __PACKAGE__->register_method ({
|
|||||||
$userid eq $authuser || $allowed_users->{$userid}
|
$userid eq $authuser || $allowed_users->{$userid}
|
||||||
} $entries->@*
|
} $entries->@*
|
||||||
];
|
];
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'add_tfa_entry',
|
name => 'add_tfa_entry',
|
||||||
path => '{userid}',
|
path => '{userid}',
|
||||||
method => 'POST',
|
method => 'POST',
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'or',
|
check => [
|
||||||
['userid-param', 'self'],
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
protected => 1, # else we can't access shadow files
|
protected => 1, # else we can't access shadow files
|
||||||
@ -315,9 +327,12 @@ __PACKAGE__->register_method ({
|
|||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
userid => get_standard_option('userid', {
|
userid => get_standard_option(
|
||||||
|
'userid',
|
||||||
|
{
|
||||||
completion => \&PVE::AccessControl::complete_username,
|
completion => \&PVE::AccessControl::complete_username,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
type => $TFA_TYPE_SCHEMA,
|
type => $TFA_TYPE_SCHEMA,
|
||||||
description => {
|
description => {
|
||||||
type => 'string',
|
type => 'string',
|
||||||
@ -332,14 +347,14 @@ __PACKAGE__->register_method ({
|
|||||||
},
|
},
|
||||||
value => {
|
value => {
|
||||||
type => 'string',
|
type => 'string',
|
||||||
description =>
|
description => 'The current value for the provided totp URI, or a Webauthn/U2F'
|
||||||
'The current value for the provided totp URI, or a Webauthn/U2F'
|
. ' challenge response',
|
||||||
.' challenge response',
|
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
challenge => {
|
challenge => {
|
||||||
type => 'string',
|
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,
|
optional => 1,
|
||||||
},
|
},
|
||||||
password => $OPTIONAL_PASSWORD_SCHEMA,
|
password => $OPTIONAL_PASSWORD_SCHEMA,
|
||||||
@ -352,9 +367,7 @@ __PACKAGE__->register_method ({
|
|||||||
my $rpcenv = PVE::RPCEnvironment::get();
|
my $rpcenv = PVE::RPCEnvironment::get();
|
||||||
my $authuser = $rpcenv->get_user();
|
my $authuser = $rpcenv->get_user();
|
||||||
my ($userid, undef, $realm) = $rpcenv->reauth_user_for_user_modification(
|
my ($userid, undef, $realm) = $rpcenv->reauth_user_for_user_modification(
|
||||||
$authuser,
|
$authuser, $param->{userid}, $param->{password},
|
||||||
$param->{userid},
|
|
||||||
$param->{password},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
my $type = delete $param->{type};
|
my $type = delete $param->{type};
|
||||||
@ -383,7 +396,8 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
});
|
});
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
sub validate_yubico_otp : prototype($$$) {
|
sub validate_yubico_otp : prototype($$$) {
|
||||||
my ($userid, $realm, $value) = @_;
|
my ($userid, $realm, $value) = @_;
|
||||||
@ -407,14 +421,13 @@ sub validate_yubico_otp : prototype($$$) {
|
|||||||
return $public_key;
|
return $public_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'update_tfa_entry',
|
name => 'update_tfa_entry',
|
||||||
path => '{userid}/{id}',
|
path => '{userid}/{id}',
|
||||||
method => 'PUT',
|
method => 'PUT',
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'or',
|
check => [
|
||||||
['userid-param', 'self'],
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
protected => 1, # else we can't access shadow files
|
protected => 1, # else we can't access shadow files
|
||||||
@ -423,9 +436,12 @@ __PACKAGE__->register_method ({
|
|||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
userid => get_standard_option('userid', {
|
userid => get_standard_option(
|
||||||
|
'userid',
|
||||||
|
{
|
||||||
completion => \&PVE::AccessControl::complete_username,
|
completion => \&PVE::AccessControl::complete_username,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
id => $TFA_ID_SCHEMA,
|
id => $TFA_ID_SCHEMA,
|
||||||
description => {
|
description => {
|
||||||
type => 'string',
|
type => 'string',
|
||||||
@ -448,23 +464,19 @@ __PACKAGE__->register_method ({
|
|||||||
my $rpcenv = PVE::RPCEnvironment::get();
|
my $rpcenv = PVE::RPCEnvironment::get();
|
||||||
my $authuser = $rpcenv->get_user();
|
my $authuser = $rpcenv->get_user();
|
||||||
my $userid = $rpcenv->reauth_user_for_user_modification(
|
my $userid = $rpcenv->reauth_user_for_user_modification(
|
||||||
$authuser,
|
$authuser, $param->{userid}, $param->{password},
|
||||||
$param->{userid},
|
|
||||||
$param->{password},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
PVE::AccessControl::lock_tfa_config(sub {
|
PVE::AccessControl::lock_tfa_config(sub {
|
||||||
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
||||||
|
|
||||||
$tfa_cfg->api_update_tfa_entry(
|
$tfa_cfg->api_update_tfa_entry(
|
||||||
$userid,
|
$userid, $param->{id}, $param->{description}, $param->{enable},
|
||||||
$param->{id},
|
|
||||||
$param->{description},
|
|
||||||
$param->{enable},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
cfs_write_file('priv/tfa.cfg', $tfa_cfg);
|
cfs_write_file('priv/tfa.cfg', $tfa_cfg);
|
||||||
});
|
});
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -17,69 +17,104 @@ use PVE::RESTHandler;
|
|||||||
|
|
||||||
use base qw(PVE::RESTHandler);
|
use base qw(PVE::RESTHandler);
|
||||||
|
|
||||||
register_standard_option('user-enable', {
|
register_standard_option(
|
||||||
description => "Enable the account (default). You can set this to '0' to disable the account",
|
'user-enable',
|
||||||
|
{
|
||||||
|
description =>
|
||||||
|
"Enable the account (default). You can set this to '0' to disable the account",
|
||||||
type => 'boolean',
|
type => 'boolean',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 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',
|
type => 'integer',
|
||||||
minimum => 0,
|
minimum => 0,
|
||||||
optional => 1,
|
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-firstname', { type => 'string', optional => 1, maxLength => 1024 });
|
||||||
register_standard_option('user-email', {
|
register_standard_option('user-lastname', { type => 'string', optional => 1, maxLength => 1024 });
|
||||||
|
register_standard_option(
|
||||||
|
'user-email',
|
||||||
|
{
|
||||||
type => 'string',
|
type => 'string',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
format => 'email-opt',
|
format => 'email-opt',
|
||||||
maxLength => 254, # 256 including punctuation and separator is the max path as per RFC 5321
|
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',
|
type => 'string',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
maxLength => 2048,
|
maxLength => 2048,
|
||||||
});
|
},
|
||||||
register_standard_option('user-keys', {
|
);
|
||||||
|
register_standard_option(
|
||||||
|
'user-keys',
|
||||||
|
{
|
||||||
description => "Keys for two factor auth (yubico).",
|
description => "Keys for two factor auth (yubico).",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
pattern => '[0-9a-zA-Z!=]{0,4096}',
|
pattern => '[0-9a-zA-Z!=]{0,4096}',
|
||||||
optional => 1,
|
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,
|
optional => 1,
|
||||||
completion => \&PVE::AccessControl::complete_group,
|
completion => \&PVE::AccessControl::complete_group,
|
||||||
});
|
},
|
||||||
register_standard_option('token-subid', {
|
);
|
||||||
|
register_standard_option(
|
||||||
|
'token-subid',
|
||||||
|
{
|
||||||
type => 'string',
|
type => 'string',
|
||||||
pattern => $PVE::AccessControl::token_subid_regex,
|
pattern => $PVE::AccessControl::token_subid_regex,
|
||||||
description => 'User-specific token identifier.',
|
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',
|
type => 'integer',
|
||||||
minimum => 0,
|
minimum => 0,
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 'same as user',
|
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',
|
type => 'boolean',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 1,
|
default => 1,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
register_standard_option('token-comment', { type => 'string', optional => 1 });
|
register_standard_option('token-comment', { type => 'string', optional => 1 });
|
||||||
register_standard_option('token-info', {
|
register_standard_option(
|
||||||
|
'token-info',
|
||||||
|
{
|
||||||
type => 'object',
|
type => 'object',
|
||||||
properties => {
|
properties => {
|
||||||
expire => get_standard_option('token-expire'),
|
expire => get_standard_option('token-expire'),
|
||||||
privsep => get_standard_option('token-privsep'),
|
privsep => get_standard_option('token-privsep'),
|
||||||
comment => get_standard_option('token-comment'),
|
comment => get_standard_option('token-comment'),
|
||||||
}
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
my $token_info_extend = sub {
|
my $token_info_extend = sub {
|
||||||
my ($props) = @_;
|
my ($props) = @_;
|
||||||
@ -110,19 +145,20 @@ my $extract_user_data = sub {
|
|||||||
|
|
||||||
return $res if !$full;
|
return $res if !$full;
|
||||||
|
|
||||||
$res->{groups} = $data->{groups} ? [ sort keys %{$data->{groups}} ] : [];
|
$res->{groups} = $data->{groups} ? [sort keys %{ $data->{groups} }] : [];
|
||||||
$res->{tokens} = $data->{tokens};
|
$res->{tokens} = $data->{tokens};
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
};
|
};
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'index',
|
name => 'index',
|
||||||
path => '',
|
path => '',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
description => "User index.",
|
description => "User index.",
|
||||||
permissions => {
|
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',
|
user => 'all',
|
||||||
},
|
},
|
||||||
protected => 1, # to access priv/tfa.cfg
|
protected => 1, # to access priv/tfa.cfg
|
||||||
@ -139,7 +175,7 @@ __PACKAGE__->register_method ({
|
|||||||
description => "Include group and token information.",
|
description => "Include group and token information.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 0,
|
default => 0,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
@ -164,7 +200,8 @@ __PACKAGE__->register_method ({
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
'realm-type' => {
|
'realm-type' => {
|
||||||
type => 'string', format => 'pve-realm',
|
type => 'string',
|
||||||
|
format => 'pve-realm',
|
||||||
description => 'The type of the users realm',
|
description => 'The type of the users realm',
|
||||||
optional => 1, # it should always be there, but we use conditional code below, so..
|
optional => 1, # it should always be there, but we use conditional code below, so..
|
||||||
},
|
},
|
||||||
@ -181,7 +218,7 @@ __PACKAGE__->register_method ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
links => [ { rel => 'child', href => "{userid}" } ],
|
links => [{ rel => 'child', href => "{userid}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -195,14 +232,14 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
my $res = [];
|
my $res = [];
|
||||||
|
|
||||||
my $privs = [ 'User.Modify', 'Sys.Audit' ];
|
my $privs = ['User.Modify', 'Sys.Audit'];
|
||||||
my $canUserMod = $rpcenv->check_any($authuser, "/access/groups", $privs, 1);
|
my $canUserMod = $rpcenv->check_any($authuser, "/access/groups", $privs, 1);
|
||||||
my $groups = $rpcenv->filter_groups($authuser, $privs, 1);
|
my $groups = $rpcenv->filter_groups($authuser, $privs, 1);
|
||||||
my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
|
my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
|
||||||
|
|
||||||
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
||||||
|
|
||||||
foreach my $user (sort keys %{$usercfg->{users}}) {
|
foreach my $user (sort keys %{ $usercfg->{users} }) {
|
||||||
if (!($canUserMod || $user eq $authuser)) {
|
if (!($canUserMod || $user eq $authuser)) {
|
||||||
next if !$allowed_users->{$user};
|
next if !$allowed_users->{$user};
|
||||||
}
|
}
|
||||||
@ -214,17 +251,17 @@ __PACKAGE__->register_method ({
|
|||||||
next if !$entry->{enable} && $param->{enabled};
|
next if !$entry->{enable} && $param->{enabled};
|
||||||
}
|
}
|
||||||
|
|
||||||
$entry->{groups} = join(',', @{$entry->{groups}}) if $entry->{groups};
|
$entry->{groups} = join(',', @{ $entry->{groups} }) if $entry->{groups};
|
||||||
|
|
||||||
if (defined(my $tokens = $entry->{tokens})) {
|
if (defined(my $tokens = $entry->{tokens})) {
|
||||||
$entry->{tokens} = [
|
$entry->{tokens} =
|
||||||
map { { tokenid => $_, %{$tokens->{$_}} } } sort keys %$tokens
|
[map { { tokenid => $_, %{ $tokens->{$_} } } } sort keys %$tokens];
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user =~ /($PVE::Auth::Plugin::realm_regex)$/) {
|
if ($user =~ /($PVE::Auth::Plugin::realm_regex)$/) {
|
||||||
my $realm = $1;
|
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;
|
$entry->{userid} = $user;
|
||||||
@ -241,19 +278,21 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'create_user',
|
name => 'create_user',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '',
|
path => '',
|
||||||
method => 'POST',
|
method => 'POST',
|
||||||
permissions => {
|
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 => [
|
check => [
|
||||||
'and',
|
'and',
|
||||||
[ 'userid-param', 'Realm.AllocateUser'],
|
['userid-param', 'Realm.AllocateUser'],
|
||||||
[ 'userid-group', ['User.Modify'], groups_param => 'create'],
|
['userid-group', ['User.Modify'], groups_param => 'create'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description => "Create new user.",
|
description => "Create new user.",
|
||||||
@ -273,7 +312,7 @@ __PACKAGE__->register_method ({
|
|||||||
type => 'string',
|
type => 'string',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
minLength => 8,
|
minLength => 8,
|
||||||
maxLength => 64
|
maxLength => 64,
|
||||||
},
|
},
|
||||||
groups => get_standard_option('group-list'),
|
groups => get_standard_option('group-list'),
|
||||||
},
|
},
|
||||||
@ -282,8 +321,10 @@ __PACKAGE__->register_method ({
|
|||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
|
|
||||||
PVE::AccessControl::lock_user_config(sub {
|
PVE::AccessControl::lock_user_config(
|
||||||
my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
|
sub {
|
||||||
|
my ($username, $ruid, $realm) =
|
||||||
|
PVE::AccessControl::verify_username($param->{userid});
|
||||||
|
|
||||||
my $usercfg = cfs_read_file("user.cfg");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
@ -308,19 +349,25 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$usercfg->{users}->{$username}->{firstname} = $param->{firstname} if $param->{firstname};
|
$usercfg->{users}->{$username}->{firstname} = $param->{firstname}
|
||||||
$usercfg->{users}->{$username}->{lastname} = $param->{lastname} if $param->{lastname};
|
if $param->{firstname};
|
||||||
|
$usercfg->{users}->{$username}->{lastname} = $param->{lastname}
|
||||||
|
if $param->{lastname};
|
||||||
$usercfg->{users}->{$username}->{email} = $param->{email} if $param->{email};
|
$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};
|
$usercfg->{users}->{$username}->{keys} = $param->{keys} if $param->{keys};
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "create user failed");
|
},
|
||||||
|
"create user failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'read_user',
|
name => 'read_user',
|
||||||
path => '{userid}',
|
path => '{userid}',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
@ -358,7 +405,7 @@ __PACKAGE__->register_method ({
|
|||||||
additionalProperties => get_standard_option('token-info'),
|
additionalProperties => get_standard_option('token-info'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type => "object"
|
type => "object",
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -370,15 +417,16 @@ __PACKAGE__->register_method ({
|
|||||||
my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
|
my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
|
||||||
|
|
||||||
return &$extract_user_data($data, 1);
|
return &$extract_user_data($data, 1);
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'update_user',
|
name => 'update_user',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '{userid}',
|
path => '{userid}',
|
||||||
method => 'PUT',
|
method => 'PUT',
|
||||||
permissions => {
|
permissions => {
|
||||||
check => ['userid-group', ['User.Modify'], groups_param => 'update' ],
|
check => ['userid-group', ['User.Modify'], groups_param => 'update'],
|
||||||
},
|
},
|
||||||
description => "Update user configuration.",
|
description => "Update user configuration.",
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -406,13 +454,16 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
|
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");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
PVE::AccessControl::check_user_exist($usercfg, $username);
|
PVE::AccessControl::check_user_exist($usercfg, $username);
|
||||||
|
|
||||||
$usercfg->{users}->{$username}->{enable} = $param->{enable} if defined($param->{enable});
|
$usercfg->{users}->{$username}->{enable} = $param->{enable}
|
||||||
$usercfg->{users}->{$username}->{expire} = $param->{expire} if defined($param->{expire});
|
if defined($param->{enable});
|
||||||
|
$usercfg->{users}->{$username}->{expire} = $param->{expire}
|
||||||
|
if defined($param->{expire});
|
||||||
|
|
||||||
PVE::AccessControl::delete_user_group($username, $usercfg)
|
PVE::AccessControl::delete_user_group($username, $usercfg)
|
||||||
if (!$param->{append} && defined($param->{groups}));
|
if (!$param->{append} && defined($param->{groups}));
|
||||||
@ -427,35 +478,42 @@ __PACKAGE__->register_method ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$usercfg->{users}->{$username}->{firstname} = $param->{firstname} if defined($param->{firstname});
|
$usercfg->{users}->{$username}->{firstname} = $param->{firstname}
|
||||||
$usercfg->{users}->{$username}->{lastname} = $param->{lastname} if defined($param->{lastname});
|
if defined($param->{firstname});
|
||||||
$usercfg->{users}->{$username}->{email} = $param->{email} if defined($param->{email});
|
$usercfg->{users}->{$username}->{lastname} = $param->{lastname}
|
||||||
$usercfg->{users}->{$username}->{comment} = $param->{comment} if defined($param->{comment});
|
if defined($param->{lastname});
|
||||||
$usercfg->{users}->{$username}->{keys} = $param->{keys} if defined($param->{keys});
|
$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);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, "update user failed");
|
},
|
||||||
|
"update user failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'delete_user',
|
name => 'delete_user',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
path => '{userid}',
|
path => '{userid}',
|
||||||
method => 'DELETE',
|
method => 'DELETE',
|
||||||
description => "Delete user.",
|
description => "Delete user.",
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'and',
|
check => [
|
||||||
[ 'userid-param', 'Realm.AllocateUser'],
|
'and', ['userid-param', 'Realm.AllocateUser'], ['userid-group', ['User.Modify']],
|
||||||
[ 'userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
properties => {
|
properties => {
|
||||||
userid => get_standard_option('userid-completed'),
|
userid => get_standard_option('userid-completed'),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
@ -466,7 +524,8 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
my ($userid, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
|
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");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
# NOTE: disable the user first (transaction like), so if (e.g.) we fail in the middle of
|
# NOTE: disable the user first (transaction like), so if (e.g.) we fail in the middle of
|
||||||
@ -497,21 +556,23 @@ __PACKAGE__->register_method ({
|
|||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
};
|
};
|
||||||
die "$@$partial_deletion\n" if $@;
|
die "$@$partial_deletion\n" if $@;
|
||||||
}, "delete user failed");
|
},
|
||||||
|
"delete user failed",
|
||||||
|
);
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'read_user_tfa_type',
|
name => 'read_user_tfa_type',
|
||||||
path => '{userid}/tfa',
|
path => '{userid}/tfa',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
description => "Get user TFA types (Personal and Realm).",
|
description => "Get user TFA types (Personal and Realm).",
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'or',
|
check => [
|
||||||
['userid-param', 'self'],
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify', 'Sys.Audit']],
|
||||||
['userid-group', ['User.Modify', 'Sys.Audit']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -538,15 +599,13 @@ __PACKAGE__->register_method ({
|
|||||||
user => {
|
user => {
|
||||||
type => 'string',
|
type => 'string',
|
||||||
enum => [qw(oath u2f)],
|
enum => [qw(oath u2f)],
|
||||||
description =>
|
description => "The type of TFA the user has set, if any."
|
||||||
"The type of TFA the user has set, if any."
|
|
||||||
. " Only set if 'multiple' was not passed.",
|
. " Only set if 'multiple' was not passed.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
types => {
|
types => {
|
||||||
type => 'array',
|
type => 'array',
|
||||||
description =>
|
description => "Array of the user configured TFA types, if any."
|
||||||
"Array of the user configured TFA types, if any."
|
|
||||||
. " Only available if 'multiple' was not passed.",
|
. " Only available if 'multiple' was not passed.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
items => {
|
items => {
|
||||||
@ -556,7 +615,7 @@ __PACKAGE__->register_method ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type => "object"
|
type => "object",
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -569,7 +628,8 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
my $res = {};
|
my $res = {};
|
||||||
my $realm_tfa = {};
|
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};
|
$res->{realm} = $realm_tfa->{type} if $realm_tfa->{type};
|
||||||
|
|
||||||
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
||||||
@ -586,16 +646,17 @@ __PACKAGE__->register_method ({
|
|||||||
$res->{user} = $tfa->{type} if $tfa->{type};
|
$res->{user} = $tfa->{type} if $tfa->{type};
|
||||||
}
|
}
|
||||||
return $res;
|
return $res;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'unlock_tfa',
|
name => 'unlock_tfa',
|
||||||
path => '{userid}/unlock-tfa',
|
path => '{userid}/unlock-tfa',
|
||||||
method => 'PUT',
|
method => 'PUT',
|
||||||
protected => 1,
|
protected => 1,
|
||||||
description => "Unlock a user's TFA authentication.",
|
description => "Unlock a user's TFA authentication.",
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [ 'userid-group', ['User.Modify']],
|
check => ['userid-group', ['User.Modify']],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
additionalProperties => 0,
|
additionalProperties => 0,
|
||||||
@ -618,18 +679,17 @@ __PACKAGE__->register_method ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return $user_was_locked;
|
return $user_was_locked;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'token_index',
|
name => 'token_index',
|
||||||
path => '{userid}/token',
|
path => '{userid}/token',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
description => "Get user API tokens.",
|
description => "Get user API tokens.",
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [
|
check => [
|
||||||
'or',
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-param', 'self'],
|
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -643,7 +703,7 @@ __PACKAGE__->register_method ({
|
|||||||
items => $token_info_extend->({
|
items => $token_info_extend->({
|
||||||
tokenid => get_standard_option('token-subid'),
|
tokenid => get_standard_option('token-subid'),
|
||||||
}),
|
}),
|
||||||
links => [ { rel => 'child', href => "{tokenid}" } ],
|
links => [{ rel => 'child', href => "{tokenid}" }],
|
||||||
},
|
},
|
||||||
code => sub {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
@ -654,19 +714,18 @@ __PACKAGE__->register_method ({
|
|||||||
my $user = PVE::AccessControl::check_user_exist($usercfg, $userid);
|
my $user = PVE::AccessControl::check_user_exist($usercfg, $userid);
|
||||||
|
|
||||||
my $tokens = $user->{tokens} // {};
|
my $tokens = $user->{tokens} // {};
|
||||||
return [ map { $tokens->{$_}->{tokenid} = $_; $tokens->{$_} } keys %$tokens];
|
return [map { $tokens->{$_}->{tokenid} = $_; $tokens->{$_} } keys %$tokens];
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'read_token',
|
name => 'read_token',
|
||||||
path => '{userid}/token/{tokenid}',
|
path => '{userid}/token/{tokenid}',
|
||||||
method => 'GET',
|
method => 'GET',
|
||||||
description => "Get specific API token information.",
|
description => "Get specific API token information.",
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [
|
check => [
|
||||||
'or',
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-param', 'self'],
|
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -686,19 +745,19 @@ __PACKAGE__->register_method ({
|
|||||||
my $usercfg = cfs_read_file("user.cfg");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
return PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
return PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method ({
|
__PACKAGE__->register_method({
|
||||||
name => 'generate_token',
|
name => 'generate_token',
|
||||||
path => '{userid}/token/{tokenid}',
|
path => '{userid}/token/{tokenid}',
|
||||||
method => 'POST',
|
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,
|
protected => 1,
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [
|
check => [
|
||||||
'or',
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-param', 'self'],
|
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -744,7 +803,8 @@ __PACKAGE__->register_method ({
|
|||||||
my $generate_and_add_token = sub {
|
my $generate_and_add_token = sub {
|
||||||
$usercfg = cfs_read_file("user.cfg");
|
$usercfg = cfs_read_file("user.cfg");
|
||||||
PVE::AccessControl::check_user_exist($usercfg, $userid);
|
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);
|
$full_tokenid = PVE::AccessControl::join_tokenid($userid, $tokenid);
|
||||||
$value = PVE::TokenConfig::generate_token($full_tokenid);
|
$value = PVE::TokenConfig::generate_token($full_tokenid);
|
||||||
@ -758,17 +818,18 @@ __PACKAGE__->register_method ({
|
|||||||
cfs_write_file("user.cfg", $usercfg);
|
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 {
|
return {
|
||||||
info => $token,
|
info => $token,
|
||||||
value => $value,
|
value => $value,
|
||||||
'full-tokenid' => $full_tokenid,
|
'full-tokenid' => $full_tokenid,
|
||||||
};
|
};
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
__PACKAGE__->register_method({
|
||||||
__PACKAGE__->register_method ({
|
|
||||||
name => 'update_token_info',
|
name => 'update_token_info',
|
||||||
path => '{userid}/token/{tokenid}',
|
path => '{userid}/token/{tokenid}',
|
||||||
method => 'PUT',
|
method => 'PUT',
|
||||||
@ -776,9 +837,7 @@ __PACKAGE__->register_method ({
|
|||||||
protected => 1,
|
protected => 1,
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [
|
check => [
|
||||||
'or',
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-param', 'self'],
|
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -791,7 +850,8 @@ __PACKAGE__->register_method ({
|
|||||||
comment => get_standard_option('token-comment'),
|
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 {
|
code => sub {
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
|
|
||||||
@ -801,7 +861,8 @@ __PACKAGE__->register_method ({
|
|||||||
my $usercfg = cfs_read_file("user.cfg");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
my $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
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");
|
$usercfg = cfs_read_file("user.cfg");
|
||||||
$token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
$token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
||||||
|
|
||||||
@ -813,13 +874,15 @@ __PACKAGE__->register_method ({
|
|||||||
|
|
||||||
$usercfg->{users}->{$userid}->{tokens}->{$tokenid} = $token;
|
$usercfg->{users}->{$userid}->{tokens}->{$tokenid} = $token;
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, 'updating token info failed');
|
},
|
||||||
|
'updating token info failed',
|
||||||
|
);
|
||||||
|
|
||||||
return $token;
|
return $token;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
__PACKAGE__->register_method({
|
||||||
__PACKAGE__->register_method ({
|
|
||||||
name => 'remove_token',
|
name => 'remove_token',
|
||||||
path => '{userid}/token/{tokenid}',
|
path => '{userid}/token/{tokenid}',
|
||||||
method => 'DELETE',
|
method => 'DELETE',
|
||||||
@ -827,9 +890,7 @@ __PACKAGE__->register_method ({
|
|||||||
protected => 1,
|
protected => 1,
|
||||||
permissions => {
|
permissions => {
|
||||||
check => [
|
check => [
|
||||||
'or',
|
'or', ['userid-param', 'self'], ['userid-group', ['User.Modify']],
|
||||||
['userid-param', 'self'],
|
|
||||||
['userid-group', ['User.Modify']],
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
parameters => {
|
parameters => {
|
||||||
@ -849,7 +910,8 @@ __PACKAGE__->register_method ({
|
|||||||
my $usercfg = cfs_read_file("user.cfg");
|
my $usercfg = cfs_read_file("user.cfg");
|
||||||
my $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
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");
|
$usercfg = cfs_read_file("user.cfg");
|
||||||
|
|
||||||
PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
|
||||||
@ -859,8 +921,11 @@ __PACKAGE__->register_method ({
|
|||||||
delete $usercfg->{users}->{$userid}->{tokens}->{$tokenid};
|
delete $usercfg->{users}->{$userid}->{tokens}->{$tokenid};
|
||||||
|
|
||||||
cfs_write_file("user.cfg", $usercfg);
|
cfs_write_file("user.cfg", $usercfg);
|
||||||
}, 'deleting token failed');
|
},
|
||||||
|
'deleting token failed',
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
1;
|
1;
|
||||||
|
@ -77,6 +77,7 @@ sub pve_verify_realm {
|
|||||||
# 2) user config
|
# 2) user config
|
||||||
# If we permit the other way round, too, we might end up deadlocking!
|
# If we permit the other way round, too, we might end up deadlocking!
|
||||||
my $user_config_locked;
|
my $user_config_locked;
|
||||||
|
|
||||||
sub lock_user_config {
|
sub lock_user_config {
|
||||||
my ($code, $errmsg) = @_;
|
my ($code, $errmsg) = @_;
|
||||||
|
|
||||||
@ -179,7 +180,7 @@ sub check_authkey {
|
|||||||
} else {
|
} else {
|
||||||
my $now = time();
|
my $now = time();
|
||||||
if ($now - $mtime >= $authkey_lifetime) {
|
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;
|
return 0;
|
||||||
} elsif ($mtime > $now + $auth_graceperiod) {
|
} elsif ($mtime > $now + $auth_graceperiod) {
|
||||||
# a nodes RTC had a time set in the future during key generation -> ticket
|
# a nodes RTC had a time set in the future during key generation -> ticket
|
||||||
@ -187,14 +188,16 @@ sub check_authkey {
|
|||||||
my (undef, $old_mtime) = get_pubkey(1);
|
my (undef, $old_mtime) = get_pubkey(1);
|
||||||
if ($old_mtime && $mtime >= $old_mtime && $mtime - $old_mtime < $ticket_lifetime) {
|
if ($old_mtime && $mtime >= $old_mtime && $mtime - $old_mtime < $ticket_lifetime) {
|
||||||
warn "auth key pair generated in the future (key $mtime > host $now),"
|
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"
|
. " 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;
|
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;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
warn "auth key new enough, skipping rotation\n" if !$quiet;;
|
warn "auth key new enough, skipping rotation\n" if !$quiet;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,7 +206,9 @@ sub check_authkey {
|
|||||||
sub rotate_authkey {
|
sub rotate_authkey {
|
||||||
return if $authkey_lifetime == 0;
|
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
|
# 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
|
# 1s, so this special dance is needed to avoid a double rotation in
|
||||||
# clusters *despite* the cfs_lock context..
|
# clusters *despite* the cfs_lock context..
|
||||||
@ -258,22 +263,28 @@ sub rotate_authkey {
|
|||||||
unlink $pve_auth_key_files->{pub};
|
unlink $pve_auth_key_files->{pub};
|
||||||
unlink $pve_auth_key_files->{priv};
|
unlink $pve_auth_key_files->{priv};
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
die $@ if $@;
|
die $@ if $@;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_standard_option('tokenid', {
|
PVE::JSONSchema::register_standard_option(
|
||||||
|
'tokenid',
|
||||||
|
{
|
||||||
description => "API token identifier.",
|
description => "API token identifier.",
|
||||||
type => "string",
|
type => "string",
|
||||||
format => "pve-tokenid",
|
format => "pve-tokenid",
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
our $token_subid_regex = $PVE::Auth::Plugin::realm_regex;
|
our $token_subid_regex = $PVE::Auth::Plugin::realm_regex;
|
||||||
|
|
||||||
# username@realm username realm tokenid
|
# 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 {
|
sub split_tokenid {
|
||||||
my ($tokenid, $noerr) = @_;
|
my ($tokenid, $noerr) = @_;
|
||||||
@ -282,7 +293,8 @@ sub split_tokenid {
|
|||||||
return ($1, $4);
|
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;
|
return undef;
|
||||||
}
|
}
|
||||||
@ -296,6 +308,7 @@ sub join_tokenid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-tokenid', \&pve_verify_tokenid);
|
PVE::JSONSchema::register_format('pve-tokenid', \&pve_verify_tokenid);
|
||||||
|
|
||||||
sub pve_verify_tokenid {
|
sub pve_verify_tokenid {
|
||||||
my ($tokenid, $noerr) = @_;
|
my ($tokenid, $noerr) = @_;
|
||||||
|
|
||||||
@ -308,7 +321,6 @@ sub pve_verify_tokenid {
|
|||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
my $csrf_prevention_secret;
|
my $csrf_prevention_secret;
|
||||||
my $csrf_prevention_secret_legacy;
|
my $csrf_prevention_secret_legacy;
|
||||||
my $get_csrfr_secret = sub {
|
my $get_csrfr_secret = sub {
|
||||||
@ -325,7 +337,7 @@ sub assemble_csrf_prevention_token {
|
|||||||
|
|
||||||
my $secret = &$get_csrfr_secret();
|
my $secret = &$get_csrfr_secret();
|
||||||
|
|
||||||
return PVE::Ticket::assemble_csrf_prevention_token ($secret, $username);
|
return PVE::Ticket::assemble_csrf_prevention_token($secret, $username);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub verify_csrf_prevention_token {
|
sub verify_csrf_prevention_token {
|
||||||
@ -343,7 +355,8 @@ sub verify_csrf_prevention_token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return PVE::Ticket::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 {
|
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);
|
my ($min, $max) = $get_ticket_age_range->($now, $rsa_mtime, $old);
|
||||||
return undef if !defined($min);
|
return undef if !defined($min);
|
||||||
|
|
||||||
return PVE::Ticket::verify_rsa_ticket(
|
return PVE::Ticket::verify_rsa_ticket($rsa_pub, 'PVE', $ticket, $tfa_ticket_aad, $min,
|
||||||
$rsa_pub, 'PVE', $ticket, $tfa_ticket_aad, $min, $max, 1);
|
$max, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
my ($data, $age) = $check->();
|
my ($data, $age) = $check->();
|
||||||
@ -494,7 +507,8 @@ sub verify_token {
|
|||||||
my $token_info = $user->{tokens}->{$token};
|
my $token_info = $user->{tokens}->{$token};
|
||||||
|
|
||||||
my $ctime = time();
|
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);
|
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";
|
my $secret_data = "$username:$path";
|
||||||
|
|
||||||
return PVE::Ticket::assemble_rsa_ticket(
|
return PVE::Ticket::assemble_rsa_ticket($rsa_priv, $prefix, undef, $secret_data);
|
||||||
$rsa_priv, $prefix, undef, $secret_data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
my $verify_short_lived_ticket = sub {
|
my $verify_short_lived_ticket = sub {
|
||||||
@ -535,8 +548,8 @@ my $verify_short_lived_ticket = sub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PVE::Ticket::verify_rsa_ticket(
|
return PVE::Ticket::verify_rsa_ticket($rsa_pub, $prefix, $ticket, $secret_data, -20, 40,
|
||||||
$rsa_pub, $prefix, $ticket, $secret_data, -20, 40, $noerr);
|
$noerr);
|
||||||
};
|
};
|
||||||
|
|
||||||
# VNC tickets
|
# VNC tickets
|
||||||
@ -574,8 +587,7 @@ sub assemble_spice_ticket {
|
|||||||
|
|
||||||
my $secret = &$get_csrfr_secret();
|
my $secret = &$get_csrfr_secret();
|
||||||
|
|
||||||
return PVE::Ticket::assemble_spice_ticket(
|
return PVE::Ticket::assemble_spice_ticket($secret, $username, $vmid, $node);
|
||||||
$secret, $username, $vmid, $node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub verify_spice_connect_url {
|
sub verify_spice_connect_url {
|
||||||
@ -707,8 +719,9 @@ sub verify_one_time_pw {
|
|||||||
my $proxy;
|
my $proxy;
|
||||||
|
|
||||||
if ($type eq 'yubico') {
|
if ($type eq 'yubico') {
|
||||||
PVE::OTP::yubico_verify_otp($otp, $keys, $tfa_cfg->{url},
|
PVE::OTP::yubico_verify_otp(
|
||||||
$tfa_cfg->{id}, $tfa_cfg->{key}, $proxy);
|
$otp, $keys, $tfa_cfg->{url}, $tfa_cfg->{id}, $tfa_cfg->{key}, $proxy,
|
||||||
|
);
|
||||||
} elsif ($type eq 'oath') {
|
} elsif ($type eq 'oath') {
|
||||||
PVE::OTP::oath_verify_otp($otp, $keys, $tfa_cfg->{step}, $tfa_cfg->{digits});
|
PVE::OTP::oath_verify_otp($otp, $keys, $tfa_cfg->{step}, $tfa_cfg->{digits});
|
||||||
} else {
|
} else {
|
||||||
@ -816,7 +829,8 @@ sub authenticate_2nd_new_do : prototype($$$$) {
|
|||||||
if (defined($tfa_response)) {
|
if (defined($tfa_response)) {
|
||||||
if (defined($tfa_challenge)) {
|
if (defined($tfa_challenge)) {
|
||||||
$tfa_done = 1;
|
$tfa_done = 1;
|
||||||
$result = $tfa_cfg->authentication_verify2($username, $tfa_challenge, $tfa_response);
|
$result =
|
||||||
|
$tfa_cfg->authentication_verify2($username, $tfa_challenge, $tfa_response);
|
||||||
} else {
|
} else {
|
||||||
die "no such challenge\n";
|
die "no such challenge\n";
|
||||||
}
|
}
|
||||||
@ -960,7 +974,7 @@ sub iterate_acl_tree {
|
|||||||
sub find_acl_tree_node {
|
sub find_acl_tree_node {
|
||||||
my ($root, $path) = @_;
|
my ($root, $path) = @_;
|
||||||
|
|
||||||
my $split_path = [ split("/", $path) ];
|
my $split_path = [split("/", $path)];
|
||||||
|
|
||||||
if (!$split_path) {
|
if (!$split_path) {
|
||||||
return $root;
|
return $root;
|
||||||
@ -989,9 +1003,9 @@ sub add_user_group {
|
|||||||
sub delete_user_group {
|
sub delete_user_group {
|
||||||
my ($username, $usercfg) = @_;
|
my ($username, $usercfg) = @_;
|
||||||
|
|
||||||
foreach my $group (keys %{$usercfg->{groups}}) {
|
foreach my $group (keys %{ $usercfg->{groups} }) {
|
||||||
|
|
||||||
delete ($usercfg->{groups}->{$group}->{users}->{$username})
|
delete($usercfg->{groups}->{$group}->{users}->{$username})
|
||||||
if $usercfg->{groups}->{$group}->{users}->{$username};
|
if $usercfg->{groups}->{$group}->{users}->{$username};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1002,7 +1016,7 @@ sub delete_user_acl {
|
|||||||
my $code = sub {
|
my $code = sub {
|
||||||
my ($path, $acl_node) = @_;
|
my ($path, $acl_node) = @_;
|
||||||
|
|
||||||
delete ($acl_node->{users}->{$username})
|
delete($acl_node->{users}->{$username})
|
||||||
if $acl_node->{users}->{$username};
|
if $acl_node->{users}->{$username};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1015,7 +1029,7 @@ sub delete_group_acl {
|
|||||||
my $code = sub {
|
my $code = sub {
|
||||||
my ($path, $acl_node) = @_;
|
my ($path, $acl_node) = @_;
|
||||||
|
|
||||||
delete ($acl_node->{groups}->{$group})
|
delete($acl_node->{groups}->{$group})
|
||||||
if $acl_node->{groups}->{$group};
|
if $acl_node->{groups}->{$group};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1025,7 +1039,7 @@ sub delete_group_acl {
|
|||||||
sub delete_pool_acl {
|
sub delete_pool_acl {
|
||||||
my ($pool, $usercfg) = @_;
|
my ($pool, $usercfg) = @_;
|
||||||
|
|
||||||
delete ($usercfg->{acl_root}->{children}->{pool}->{children}->{$pool});
|
delete($usercfg->{acl_root}->{children}->{pool}->{children}->{$pool});
|
||||||
}
|
}
|
||||||
|
|
||||||
# we automatically create some predefined roles by splitting privs
|
# we automatically create some predefined roles by splitting privs
|
||||||
@ -1069,8 +1083,7 @@ my $privgroups = {
|
|||||||
'Sys.AccessNetwork', # for, e.g., downloading ISOs from any URL
|
'Sys.AccessNetwork', # for, e.g., downloading ISOs from any URL
|
||||||
],
|
],
|
||||||
admin => [
|
admin => [
|
||||||
'Sys.Console',
|
'Sys.Console', 'Sys.Syslog',
|
||||||
'Sys.Syslog',
|
|
||||||
],
|
],
|
||||||
user => [],
|
user => [],
|
||||||
audit => [
|
audit => [
|
||||||
@ -1080,8 +1093,7 @@ my $privgroups = {
|
|||||||
Datastore => {
|
Datastore => {
|
||||||
root => [],
|
root => [],
|
||||||
admin => [
|
admin => [
|
||||||
'Datastore.Allocate',
|
'Datastore.Allocate', 'Datastore.AllocateTemplate',
|
||||||
'Datastore.AllocateTemplate',
|
|
||||||
],
|
],
|
||||||
user => [
|
user => [
|
||||||
'Datastore.AllocateSpace',
|
'Datastore.AllocateSpace',
|
||||||
@ -1093,8 +1105,7 @@ my $privgroups = {
|
|||||||
SDN => {
|
SDN => {
|
||||||
root => [],
|
root => [],
|
||||||
admin => [
|
admin => [
|
||||||
'SDN.Allocate',
|
'SDN.Allocate', 'SDN.Audit',
|
||||||
'SDN.Audit',
|
|
||||||
],
|
],
|
||||||
user => [
|
user => [
|
||||||
'SDN.Use',
|
'SDN.Use',
|
||||||
@ -1155,21 +1166,21 @@ sub create_roles {
|
|||||||
for my $cat (keys %$privgroups) {
|
for my $cat (keys %$privgroups) {
|
||||||
my $cd = $privgroups->{$cat};
|
my $cd = $privgroups->{$cat};
|
||||||
# create map to easily check if a privilege is valid
|
# create map to easily check if a privilege is valid
|
||||||
for my $priv (@{$cd->{root}}, @{$cd->{admin}}, @{$cd->{user}}, @{$cd->{audit}}) {
|
for my $priv (@{ $cd->{root} }, @{ $cd->{admin} }, @{ $cd->{user} }, @{ $cd->{audit} }) {
|
||||||
$valid_privs->{$priv} = 1;
|
$valid_privs->{$priv} = 1;
|
||||||
}
|
}
|
||||||
# create grouped admin roles and PVEAdmin
|
# create grouped admin roles and PVEAdmin
|
||||||
for my $priv (@{$cd->{admin}}, @{$cd->{user}}, @{$cd->{audit}}) {
|
for my $priv (@{ $cd->{admin} }, @{ $cd->{user} }, @{ $cd->{audit} }) {
|
||||||
$special_roles->{"PVE${cat}Admin"}->{$priv} = 1;
|
$special_roles->{"PVE${cat}Admin"}->{$priv} = 1;
|
||||||
$special_roles->{"PVEAdmin"}->{$priv} = 1;
|
$special_roles->{"PVEAdmin"}->{$priv} = 1;
|
||||||
}
|
}
|
||||||
# create grouped user and audit roles
|
# create grouped user and audit roles
|
||||||
if (scalar(@{$cd->{user}})) {
|
if (scalar(@{ $cd->{user} })) {
|
||||||
for my $priv (@{$cd->{user}}, @{$cd->{audit}}) {
|
for my $priv (@{ $cd->{user} }, @{ $cd->{audit} }) {
|
||||||
$special_roles->{"PVE${cat}User"}->{$priv} = 1;
|
$special_roles->{"PVE${cat}User"}->{$priv} = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for my $priv (@{$cd->{audit}}) {
|
for my $priv (@{ $cd->{audit} }) {
|
||||||
$special_roles->{"PVEAuditor"}->{$priv} = 1;
|
$special_roles->{"PVEAuditor"}->{$priv} = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1179,7 +1190,7 @@ sub create_roles {
|
|||||||
delete $special_roles->{"PVEAdmin"}->{"Mapping.Modify"};
|
delete $special_roles->{"PVEAdmin"}->{"Mapping.Modify"};
|
||||||
|
|
||||||
$special_roles->{"PVETemplateUser"} = { 'VM.Clone' => 1, 'VM.Audit' => 1 };
|
$special_roles->{"PVETemplateUser"} = { 'VM.Clone' => 1, 'VM.Audit' => 1 };
|
||||||
};
|
}
|
||||||
|
|
||||||
create_roles();
|
create_roles();
|
||||||
|
|
||||||
@ -1207,7 +1218,7 @@ sub add_role_privs {
|
|||||||
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
|
die "role '$role' does not exist\n" if !$usercfg->{roles}->{$role};
|
||||||
|
|
||||||
foreach my $priv (split_list($privs)) {
|
foreach my $priv (split_list($privs)) {
|
||||||
if (defined ($valid_privs->{$priv})) {
|
if (defined($valid_privs->{$priv})) {
|
||||||
$usercfg->{roles}->{$role}->{$priv} = 1;
|
$usercfg->{roles}->{$role}->{$priv} = 1;
|
||||||
} else {
|
} else {
|
||||||
die "invalid privilege '$priv'\n";
|
die "invalid privilege '$priv'\n";
|
||||||
@ -1226,9 +1237,10 @@ sub lookup_username {
|
|||||||
|
|
||||||
if (!$casesensitive) {
|
if (!$casesensitive) {
|
||||||
my $usercfg = cfs_read_file('user.cfg');
|
my $usercfg = cfs_read_file('user.cfg');
|
||||||
my @matches = grep { lc $username eq lc $_ } (keys %{$usercfg->{users}});
|
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;
|
if scalar @matches > 1 && !$noerr;
|
||||||
|
|
||||||
return $matches[0] if defined($matches[0]);
|
return $matches[0] if defined($matches[0]);
|
||||||
@ -1290,6 +1302,7 @@ sub check_path {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-groupid', \&verify_groupname);
|
PVE::JSONSchema::register_format('pve-groupid', \&verify_groupname);
|
||||||
|
|
||||||
sub verify_groupname {
|
sub verify_groupname {
|
||||||
my ($groupname, $noerr) = @_;
|
my ($groupname, $noerr) = @_;
|
||||||
|
|
||||||
@ -1304,6 +1317,7 @@ sub verify_groupname {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-roleid', \&verify_rolename);
|
PVE::JSONSchema::register_format('pve-roleid', \&verify_rolename);
|
||||||
|
|
||||||
sub verify_rolename {
|
sub verify_rolename {
|
||||||
my ($rolename, $noerr) = @_;
|
my ($rolename, $noerr) = @_;
|
||||||
|
|
||||||
@ -1318,6 +1332,7 @@ sub verify_rolename {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-poolid', \&verify_poolname);
|
PVE::JSONSchema::register_format('pve-poolid', \&verify_poolname);
|
||||||
|
|
||||||
sub verify_poolname {
|
sub verify_poolname {
|
||||||
my ($poolname, $noerr) = @_;
|
my ($poolname, $noerr) = @_;
|
||||||
|
|
||||||
@ -1338,6 +1353,7 @@ sub verify_poolname {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-priv', \&verify_privname);
|
PVE::JSONSchema::register_format('pve-priv', \&verify_privname);
|
||||||
|
|
||||||
sub verify_privname {
|
sub verify_privname {
|
||||||
my ($priv, $noerr) = @_;
|
my ($priv, $noerr) = @_;
|
||||||
|
|
||||||
@ -1380,10 +1396,10 @@ sub parse_user_config {
|
|||||||
my $line = $1;
|
my $line = $1;
|
||||||
my @data;
|
my @data;
|
||||||
|
|
||||||
foreach my $d (split (/:/, $line)) {
|
foreach my $d (split(/:/, $line)) {
|
||||||
$d =~ s/^\s+//;
|
$d =~ s/^\s+//;
|
||||||
$d =~ s/\s+$//;
|
$d =~ s/\s+$//;
|
||||||
push @data, $d
|
push @data, $d;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $et = shift @data;
|
my $et = shift @data;
|
||||||
@ -1402,7 +1418,8 @@ sub parse_user_config {
|
|||||||
$expire = 0 if !$expire;
|
$expire = 0 if !$expire;
|
||||||
|
|
||||||
if ($expire !~ m/^\d+$/) {
|
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;
|
next;
|
||||||
}
|
}
|
||||||
$expire = int($expire);
|
$expire = int($expire);
|
||||||
@ -1467,7 +1484,7 @@ sub parse_user_config {
|
|||||||
$cfg->{roles}->{$role} = {} if !$cfg->{roles}->{$role};
|
$cfg->{roles}->{$role} = {} if !$cfg->{roles}->{$role};
|
||||||
|
|
||||||
foreach my $priv (split_list($privlist)) {
|
foreach my $priv (split_list($privlist)) {
|
||||||
if (defined ($valid_privs->{$priv})) {
|
if (defined($valid_privs->{$priv})) {
|
||||||
$cfg->{roles}->{$role}->{$priv} = 1;
|
$cfg->{roles}->{$role}->{$priv} = 1;
|
||||||
} else {
|
} else {
|
||||||
warn "user config - ignore invalid privilege '$priv'\n";
|
warn "user config - ignore invalid privilege '$priv'\n";
|
||||||
@ -1510,7 +1527,8 @@ sub parse_user_config {
|
|||||||
$acl_node->{users}->{$ug}->{$role} = $propagate;
|
$acl_node->{users}->{$ug}->{$role} = $propagate;
|
||||||
} elsif (my ($user, $token) = split_tokenid($ug, 1)) {
|
} elsif (my ($user, $token) = split_tokenid($ug, 1)) {
|
||||||
if (check_token_exist($cfg, $user, $token, 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;
|
$acl_node->{tokens}->{$ug}->{$role} = $propagate;
|
||||||
} else {
|
} else {
|
||||||
warn "user config - ignore invalid acl token '$ug'\n";
|
warn "user config - ignore invalid acl token '$ug'\n";
|
||||||
@ -1589,7 +1607,8 @@ sub parse_user_config {
|
|||||||
$expire = 0 if !$expire;
|
$expire = 0 if !$expire;
|
||||||
|
|
||||||
if ($expire !~ m/^\d+$/) {
|
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;
|
next;
|
||||||
}
|
}
|
||||||
$expire = int($expire);
|
$expire = int($expire);
|
||||||
@ -1618,7 +1637,7 @@ sub write_user_config {
|
|||||||
|
|
||||||
my $data = '';
|
my $data = '';
|
||||||
|
|
||||||
foreach my $user (sort keys %{$cfg->{users}}) {
|
foreach my $user (sort keys %{ $cfg->{users} }) {
|
||||||
my $d = $cfg->{users}->{$user};
|
my $d = $cfg->{users}->{$user};
|
||||||
my $firstname = $d->{firstname} ? PVE::Tools::encode_text($d->{firstname}) : '';
|
my $firstname = $d->{firstname} ? PVE::Tools::encode_text($d->{firstname}) : '';
|
||||||
my $lastname = $d->{lastname} ? PVE::Tools::encode_text($d->{lastname}) : '';
|
my $lastname = $d->{lastname} ? PVE::Tools::encode_text($d->{lastname}) : '';
|
||||||
@ -1642,30 +1661,30 @@ sub write_user_config {
|
|||||||
|
|
||||||
$data .= "\n";
|
$data .= "\n";
|
||||||
|
|
||||||
foreach my $group (sort keys %{$cfg->{groups}}) {
|
foreach my $group (sort keys %{ $cfg->{groups} }) {
|
||||||
my $d = $cfg->{groups}->{$group};
|
my $d = $cfg->{groups}->{$group};
|
||||||
my $list = join (',', sort keys %{$d->{users}});
|
my $list = join(',', sort keys %{ $d->{users} });
|
||||||
my $comment = $d->{comment} ? PVE::Tools::encode_text($d->{comment}) : '';
|
my $comment = $d->{comment} ? PVE::Tools::encode_text($d->{comment}) : '';
|
||||||
$data .= "group:$group:$list:$comment:\n";
|
$data .= "group:$group:$list:$comment:\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$data .= "\n";
|
$data .= "\n";
|
||||||
|
|
||||||
foreach my $pool (sort keys %{$cfg->{pools}}) {
|
foreach my $pool (sort keys %{ $cfg->{pools} }) {
|
||||||
my $d = $cfg->{pools}->{$pool};
|
my $d = $cfg->{pools}->{$pool};
|
||||||
my $vmlist = join (',', sort keys %{$d->{vms}});
|
my $vmlist = join(',', sort keys %{ $d->{vms} });
|
||||||
my $storelist = join (',', sort keys %{$d->{storage}});
|
my $storelist = join(',', sort keys %{ $d->{storage} });
|
||||||
my $comment = $d->{comment} ? PVE::Tools::encode_text($d->{comment}) : '';
|
my $comment = $d->{comment} ? PVE::Tools::encode_text($d->{comment}) : '';
|
||||||
$data .= "pool:$pool:$comment:$vmlist:$storelist:\n";
|
$data .= "pool:$pool:$comment:$vmlist:$storelist:\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$data .= "\n";
|
$data .= "\n";
|
||||||
|
|
||||||
foreach my $role (sort keys %{$cfg->{roles}}) {
|
foreach my $role (sort keys %{ $cfg->{roles} }) {
|
||||||
next if $special_roles->{$role};
|
next if $special_roles->{$role};
|
||||||
|
|
||||||
my $d = $cfg->{roles}->{$role};
|
my $d = $cfg->{roles}->{$role};
|
||||||
my $list = join (',', sort keys %$d);
|
my $list = join(',', sort keys %$d);
|
||||||
$data .= "role:$role:$list:\n";
|
$data .= "role:$role:$list:\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1679,7 +1698,7 @@ sub write_user_config {
|
|||||||
|
|
||||||
my $l0 = '';
|
my $l0 = '';
|
||||||
my $l1 = '';
|
my $l1 = '';
|
||||||
foreach my $role (sort keys %{$acl_members->{$member}}) {
|
foreach my $role (sort keys %{ $acl_members->{$member} }) {
|
||||||
my $propagate = $acl_members->{$member}->{$role};
|
my $propagate = $acl_members->{$member}->{$role};
|
||||||
if ($propagate) {
|
if ($propagate) {
|
||||||
$l1 .= ',' if $l1;
|
$l1 .= ',' if $l1;
|
||||||
@ -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 ($path, $d) = @_;
|
||||||
|
|
||||||
my $rolelist_members = {};
|
my $rolelist_members = {};
|
||||||
@ -1706,15 +1728,16 @@ sub write_user_config {
|
|||||||
|
|
||||||
$collect_rolelist_members->($d->{'tokens'}, $rolelist_members, '');
|
$collect_rolelist_members->($d->{'tokens'}, $rolelist_members, '');
|
||||||
|
|
||||||
foreach my $propagate (0,1) {
|
foreach my $propagate (0, 1) {
|
||||||
my $filtered = $rolelist_members->{$propagate};
|
my $filtered = $rolelist_members->{$propagate};
|
||||||
foreach my $rolelist (sort keys %$filtered) {
|
foreach my $rolelist (sort keys %$filtered) {
|
||||||
my $uglist = join (',', sort keys %{$filtered->{$rolelist}});
|
my $uglist = join(',', sort keys %{ $filtered->{$rolelist} });
|
||||||
$data .= "acl:$propagate:$path:$uglist:$rolelist:\n";
|
$data .= "acl:$propagate:$path:$uglist:$rolelist:\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
@ -1776,9 +1799,9 @@ sub roles {
|
|||||||
|
|
||||||
my $roles = {};
|
my $roles = {};
|
||||||
|
|
||||||
my $split = [ split("/", $path) ];
|
my $split = [split("/", $path)];
|
||||||
if ($path eq '/') {
|
if ($path eq '/') {
|
||||||
$split = [ '' ];
|
$split = [''];
|
||||||
}
|
}
|
||||||
|
|
||||||
my $acl = $cfg->{acl_root};
|
my $acl = $cfg->{acl_root};
|
||||||
@ -1826,7 +1849,7 @@ sub roles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
my $new;
|
my $new;
|
||||||
foreach my $g (keys %{$acl->{groups}}) {
|
foreach my $g (keys %{ $acl->{groups} }) {
|
||||||
next if !$cfg->{groups}->{$g}->{users}->{$user};
|
next if !$cfg->{groups}->{$g}->{users}->{$user};
|
||||||
if (my $ri = $acl->{groups}->{$g}) {
|
if (my $ri = $acl->{groups}->{$g}) {
|
||||||
foreach my $role (keys %$ri) {
|
foreach my $role (keys %$ri) {
|
||||||
@ -1845,7 +1868,7 @@ sub roles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { 'NoAccess' => $roles->{NoAccess} } if defined ($roles->{NoAccess});
|
return { 'NoAccess' => $roles->{NoAccess} } if defined($roles->{NoAccess});
|
||||||
#return () if defined ($roles->{NoAccess});
|
#return () if defined ($roles->{NoAccess});
|
||||||
|
|
||||||
#print "permission $user $path = " . Dumper ($roles);
|
#print "permission $user $path = " . Dumper ($roles);
|
||||||
@ -1889,15 +1912,17 @@ sub remove_storage_access {
|
|||||||
delete $usercfg->{acl_root}->{children}->{storage}->{children}->{$storeid};
|
delete $usercfg->{acl_root}->{children}->{storage}->{children}->{$storeid};
|
||||||
$modified = 1;
|
$modified = 1;
|
||||||
}
|
}
|
||||||
foreach my $pool (keys %{$usercfg->{pools}}) {
|
foreach my $pool (keys %{ $usercfg->{pools} }) {
|
||||||
delete $usercfg->{pools}->{$pool}->{storage}->{$storeid};
|
delete $usercfg->{pools}->{$pool}->{storage}->{$storeid};
|
||||||
$modified = 1;
|
$modified = 1;
|
||||||
}
|
}
|
||||||
cfs_write_file("user.cfg", $usercfg) if $modified;
|
cfs_write_file("user.cfg", $usercfg) if $modified;
|
||||||
};
|
};
|
||||||
|
|
||||||
lock_user_config($deleteStorageAccessFn,
|
lock_user_config(
|
||||||
"access permissions cleanup for storage $storeid failed");
|
$deleteStorageAccessFn,
|
||||||
|
"access permissions cleanup for storage $storeid failed",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub add_vm_to_pool {
|
sub add_vm_to_pool {
|
||||||
@ -2044,29 +2069,30 @@ sub user_get_tfa : prototype($$$) {
|
|||||||
|
|
||||||
# bash completion helpers
|
# bash completion helpers
|
||||||
|
|
||||||
register_standard_option('userid-completed',
|
register_standard_option(
|
||||||
get_standard_option('userid', { completion => \&complete_username}),
|
'userid-completed',
|
||||||
|
get_standard_option('userid', { completion => \&complete_username }),
|
||||||
);
|
);
|
||||||
|
|
||||||
sub complete_username {
|
sub complete_username {
|
||||||
|
|
||||||
my $user_cfg = cfs_read_file('user.cfg');
|
my $user_cfg = cfs_read_file('user.cfg');
|
||||||
|
|
||||||
return [ keys %{$user_cfg->{users}} ];
|
return [keys %{ $user_cfg->{users} }];
|
||||||
}
|
}
|
||||||
|
|
||||||
sub complete_group {
|
sub complete_group {
|
||||||
|
|
||||||
my $user_cfg = cfs_read_file('user.cfg');
|
my $user_cfg = cfs_read_file('user.cfg');
|
||||||
|
|
||||||
return [ keys %{$user_cfg->{groups}} ];
|
return [keys %{ $user_cfg->{groups} }];
|
||||||
}
|
}
|
||||||
|
|
||||||
sub complete_realm {
|
sub complete_realm {
|
||||||
|
|
||||||
my $domain_cfg = cfs_read_file('domains.cfg');
|
my $domain_cfg = cfs_read_file('domains.cfg');
|
||||||
|
|
||||||
return [ keys %{$domain_cfg->{ids}} ];
|
return [keys %{ $domain_cfg->{ids} }];
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -32,7 +32,8 @@ sub properties {
|
|||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
sslversion => {
|
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',
|
type => 'string',
|
||||||
enum => [qw(tlsv1 tlsv1_1 tlsv1_2 tlsv1_3)],
|
enum => [qw(tlsv1 tlsv1_1 tlsv1_2 tlsv1_3)],
|
||||||
optional => 1,
|
optional => 1,
|
||||||
@ -74,7 +75,7 @@ sub options {
|
|||||||
port => { optional => 1 },
|
port => { optional => 1 },
|
||||||
secure => { optional => 1 },
|
secure => { optional => 1 },
|
||||||
sslversion => { optional => 1 },
|
sslversion => { optional => 1 },
|
||||||
default => { optional => 1 },,
|
default => { optional => 1 },
|
||||||
comment => { optional => 1 },
|
comment => { optional => 1 },
|
||||||
tfa => { optional => 1 },
|
tfa => { optional => 1 },
|
||||||
verify => { optional => 1 },
|
verify => { optional => 1 },
|
||||||
|
@ -36,7 +36,8 @@ sub properties {
|
|||||||
maxLength => 256,
|
maxLength => 256,
|
||||||
},
|
},
|
||||||
password => {
|
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',
|
type => 'string',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
@ -70,10 +71,10 @@ sub properties {
|
|||||||
},
|
},
|
||||||
sync_attributes => {
|
sync_attributes => {
|
||||||
description => "Comma separated list of key=value pairs for specifying"
|
description => "Comma separated list of key=value pairs for specifying"
|
||||||
." which LDAP attributes map to which PVE user field. For example,"
|
. " which LDAP attributes map to which PVE user field. For example,"
|
||||||
." to map the LDAP attribute 'mail' to PVEs 'email', write "
|
. " to map the LDAP attribute 'mail' to PVEs 'email', write "
|
||||||
." 'email=mail'. By default, each PVE user field is represented "
|
. " 'email=mail'. By default, each PVE user field is represented "
|
||||||
." by an LDAP attribute of the same name.",
|
. " by an LDAP attribute of the same name.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
type => 'string',
|
type => 'string',
|
||||||
pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
|
pattern => '\w+=[^,]+(,\s*\w+=[^,]+)*',
|
||||||
@ -87,14 +88,14 @@ sub properties {
|
|||||||
},
|
},
|
||||||
group_dn => {
|
group_dn => {
|
||||||
description => "LDAP base domain name for group sync. If not set, the"
|
description => "LDAP base domain name for group sync. If not set, the"
|
||||||
." base_dn will be used.",
|
. " base_dn will be used.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
maxLength => 256,
|
maxLength => 256,
|
||||||
},
|
},
|
||||||
group_name_attr => {
|
group_name_attr => {
|
||||||
description => "LDAP attribute representing a groups name. If not set"
|
description => "LDAP attribute representing a groups name. If not set"
|
||||||
." or found, the first value of the DN will be used as name.",
|
. " or found, the first value of the DN will be used as name.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
format => 'ldap-simple-attr',
|
format => 'ldap-simple-attr',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
@ -122,7 +123,7 @@ sub properties {
|
|||||||
mode => {
|
mode => {
|
||||||
description => "LDAP protocol mode.",
|
description => "LDAP protocol mode.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
enum => [ 'ldap', 'ldaps', 'ldap+starttls'],
|
enum => ['ldap', 'ldaps', 'ldap+starttls'],
|
||||||
optional => 1,
|
optional => 1,
|
||||||
default => 'ldap',
|
default => 'ldap',
|
||||||
},
|
},
|
||||||
@ -238,7 +239,7 @@ sub connect_and_bind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$config->{base_dn}) {
|
if (!$config->{base_dn}) {
|
||||||
my $root = $ldap->root_dse(attrs => [ 'defaultNamingContext' ]);
|
my $root = $ldap->root_dse(attrs => ['defaultNamingContext']);
|
||||||
$config->{base_dn} = $root->get_value('defaultNamingContext');
|
$config->{base_dn} = $root->get_value('defaultNamingContext');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +307,8 @@ sub get_users {
|
|||||||
$config->{user_classes} //= 'inetorgperson, posixaccount, person, user';
|
$config->{user_classes} //= 'inetorgperson, posixaccount, person, user';
|
||||||
my $classes = [PVE::Tools::split_list($config->{user_classes})];
|
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 $ret = {};
|
||||||
my $dnmap = {};
|
my $dnmap = {};
|
||||||
@ -339,7 +341,7 @@ sub get_users {
|
|||||||
|
|
||||||
if (wantarray) {
|
if (wantarray) {
|
||||||
my $dn = $user->{dn};
|
my $dn = $user->{dn};
|
||||||
$dnmap->{lc($dn)} = $username;
|
$dnmap->{ lc($dn) } = $username;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +367,7 @@ sub get_groups {
|
|||||||
|
|
||||||
foreach my $group (@$groups) {
|
foreach my $group (@$groups) {
|
||||||
my $name = $group->{name};
|
my $name = $group->{name};
|
||||||
if (!$name && $group->{dn} =~ m/^[^=]+=([^,]+),/){
|
if (!$name && $group->{dn} =~ m/^[^=]+=([^,]+),/) {
|
||||||
$name = PVE::Tools::trim($1);
|
$name = PVE::Tools::trim($1);
|
||||||
}
|
}
|
||||||
if ($name) {
|
if ($name) {
|
||||||
@ -379,8 +381,8 @@ sub get_groups {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$ret->{$name} = { users => {} };
|
$ret->{$name} = { users => {} };
|
||||||
foreach my $member (@{$group->{members}}) {
|
foreach my $member (@{ $group->{members} }) {
|
||||||
if (my $user = $dnmap->{lc($member)}) {
|
if (my $user = $dnmap->{ lc($member) }) {
|
||||||
$ret->{$name}->{users}->{$user} = 1;
|
$ret->{$name}->{users}->{$user} = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -395,7 +397,8 @@ sub authenticate_user {
|
|||||||
|
|
||||||
my $ldap = $class->connect_and_bind($config, $realm);
|
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);
|
PVE::LDAP::auth_user_dn($ldap, $user_dn, $password);
|
||||||
|
|
||||||
$ldap->unbind();
|
$ldap->unbind();
|
||||||
@ -447,7 +450,7 @@ sub ldap_delete_credentials {
|
|||||||
my ($realmid) = @_;
|
my ($realmid) = @_;
|
||||||
|
|
||||||
if (my $cred_file = get_cred_file($realmid)) {
|
if (my $cred_file = get_cred_file($realmid)) {
|
||||||
return if ! -e $cred_file; # nothing to do
|
return if !-e $cred_file; # nothing to do
|
||||||
unlink($cred_file) or warn "removing LDAP credentials '$cred_file' failed: $!\n";
|
unlink($cred_file) or warn "removing LDAP credentials '$cred_file' failed: $!\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,21 +66,22 @@ sub properties {
|
|||||||
},
|
},
|
||||||
prompt => {
|
prompt => {
|
||||||
description => "Specifies whether the Authorization Server prompts the End-User for"
|
description => "Specifies whether the Authorization Server prompts the End-User for"
|
||||||
." reauthentication and consent.",
|
. " reauthentication and consent.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
pattern => '(?:none|login|consent|select_account|\S+)', # \S+ is the extension variant
|
pattern => '(?:none|login|consent|select_account|\S+)', # \S+ is the extension variant
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
scopes => {
|
scopes => {
|
||||||
description => "Specifies the scopes (user details) that should be authorized and"
|
description => "Specifies the scopes (user details) that should be authorized and"
|
||||||
." returned, for example 'email' or 'profile'.",
|
. " returned, for example 'email' or 'profile'.",
|
||||||
type => 'string', # format => 'some-safe-id-list', # FIXME: TODO
|
type => 'string', # format => 'some-safe-id-list', # FIXME: TODO
|
||||||
default => "email profile",
|
default => "email profile",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
'acr-values' => {
|
'acr-values' => {
|
||||||
description => "Specifies the Authentication Context Class Reference values that the"
|
description =>
|
||||||
."Authorization Server is being requested to use for the Auth Request.",
|
"Specifies the Authentication Context Class Reference values that the"
|
||||||
|
. "Authorization Server is being requested to use for the Auth Request.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
pattern => '^[^\x00-\x1F\x7F <>#"]*$', # Prohibit characters not allowed in URI RFC 2396.
|
pattern => '^[^\x00-\x1F\x7F <>#"]*$', # Prohibit characters not allowed in URI RFC 2396.
|
||||||
optional => 1,
|
optional => 1,
|
||||||
@ -119,5 +120,4 @@ sub authenticate_user {
|
|||||||
die "OpenID realm does not allow password verification.\n";
|
die "OpenID realm does not allow password verification.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -27,18 +27,22 @@ sub authenticate_user {
|
|||||||
# user (www-data) need to be able to read /etc/passwd /etc/shadow
|
# user (www-data) need to be able to read /etc/passwd /etc/shadow
|
||||||
die "no password\n" if !$password;
|
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;
|
my @res;
|
||||||
while(@_) {
|
while (@_) {
|
||||||
my $msg_type = shift;
|
my $msg_type = shift;
|
||||||
my $msg = shift;
|
my $msg = shift;
|
||||||
push @res, (0, $password);
|
push @res, (0, $password);
|
||||||
}
|
}
|
||||||
push @res, 0;
|
push @res, 0;
|
||||||
return @res;
|
return @res;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (!ref ($pamh)) {
|
if (!ref($pamh)) {
|
||||||
my $err = $pamh->pam_strerror($pamh);
|
my $err = $pamh->pam_strerror($pamh);
|
||||||
die "error during PAM init: $err";
|
die "error during PAM init: $err";
|
||||||
}
|
}
|
||||||
@ -56,7 +60,7 @@ sub authenticate_user {
|
|||||||
die "$err\n";
|
die "$err\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($res = $pamh->pam_acct_mgmt (0)) != PAM_SUCCESS) {
|
if (($res = $pamh->pam_acct_mgmt(0)) != PAM_SUCCESS) {
|
||||||
my $err = $pamh->pam_strerror($res);
|
my $err = $pamh->pam_strerror($res);
|
||||||
die "$err\n";
|
die "$err\n";
|
||||||
}
|
}
|
||||||
@ -66,7 +70,6 @@ sub authenticate_user {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub store_password {
|
sub store_password {
|
||||||
my ($class, $config, $realm, $username, $password) = @_;
|
my ($class, $config, $realm, $username, $password) = @_;
|
||||||
|
|
||||||
|
@ -12,9 +12,7 @@ use base qw(PVE::Auth::Plugin);
|
|||||||
|
|
||||||
my $shadowconfigfile = "priv/shadow.cfg";
|
my $shadowconfigfile = "priv/shadow.cfg";
|
||||||
|
|
||||||
cfs_register_file($shadowconfigfile,
|
cfs_register_file($shadowconfigfile, \&parse_shadow_passwd, \&write_shadow_config);
|
||||||
\&parse_shadow_passwd,
|
|
||||||
\&write_shadow_config);
|
|
||||||
|
|
||||||
sub parse_shadow_passwd {
|
sub parse_shadow_passwd {
|
||||||
my ($filename, $raw) = @_;
|
my ($filename, $raw) = @_;
|
||||||
@ -31,7 +29,7 @@ sub parse_shadow_passwd {
|
|||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
my ($userid, $crypt_pass) = split (/:/, $line);
|
my ($userid, $crypt_pass) = split(/:/, $line);
|
||||||
$shadow->{users}->{$userid}->{shadow} = $crypt_pass;
|
$shadow->{users}->{$userid}->{shadow} = $crypt_pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,12 +40,12 @@ sub write_shadow_config {
|
|||||||
my ($filename, $cfg) = @_;
|
my ($filename, $cfg) = @_;
|
||||||
|
|
||||||
my $data = '';
|
my $data = '';
|
||||||
foreach my $userid (keys %{$cfg->{users}}) {
|
foreach my $userid (keys %{ $cfg->{users} }) {
|
||||||
my $crypt_pass = $cfg->{users}->{$userid}->{shadow};
|
my $crypt_pass = $cfg->{users}->{$userid}->{shadow};
|
||||||
$data .= "$userid:$crypt_pass:\n";
|
$data .= "$userid:$crypt_pass:\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub lock_shadow_config {
|
sub lock_shadow_config {
|
||||||
@ -80,8 +78,8 @@ sub authenticate_user {
|
|||||||
my $shadow_cfg = cfs_read_file($shadowconfigfile);
|
my $shadow_cfg = cfs_read_file($shadowconfigfile);
|
||||||
|
|
||||||
if ($shadow_cfg->{users}->{$username}) {
|
if ($shadow_cfg->{users}->{$username}) {
|
||||||
my $encpw = crypt(Encode::encode('utf8', $password),
|
my $encpw =
|
||||||
$shadow_cfg->{users}->{$username}->{shadow});
|
crypt(Encode::encode('utf8', $password), $shadow_cfg->{users}->{$username}->{shadow});
|
||||||
die "invalid credentials\n" if ($encpw ne $shadow_cfg->{users}->{$username}->{shadow});
|
die "invalid credentials\n" if ($encpw ne $shadow_cfg->{users}->{$username}->{shadow});
|
||||||
} else {
|
} else {
|
||||||
die "no password set\n";
|
die "no password set\n";
|
||||||
|
@ -15,9 +15,11 @@ use base qw(PVE::SectionConfig);
|
|||||||
|
|
||||||
my $domainconfigfile = "domains.cfg";
|
my $domainconfigfile = "domains.cfg";
|
||||||
|
|
||||||
cfs_register_file($domainconfigfile,
|
cfs_register_file(
|
||||||
|
$domainconfigfile,
|
||||||
sub { __PACKAGE__->parse_config(@_); },
|
sub { __PACKAGE__->parse_config(@_); },
|
||||||
sub { __PACKAGE__->write_config(@_); });
|
sub { __PACKAGE__->write_config(@_); },
|
||||||
|
);
|
||||||
|
|
||||||
sub lock_domain_config {
|
sub lock_domain_config {
|
||||||
my ($code, $errmsg) = @_;
|
my ($code, $errmsg) = @_;
|
||||||
@ -34,6 +36,7 @@ our $user_regex = qr![^\s:/]+!;
|
|||||||
our $groupname_regex_chars = qr/A-Za-z0-9\.\-_/;
|
our $groupname_regex_chars = qr/A-Za-z0-9\.\-_/;
|
||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-realm', \&pve_verify_realm);
|
PVE::JSONSchema::register_format('pve-realm', \&pve_verify_realm);
|
||||||
|
|
||||||
sub pve_verify_realm {
|
sub pve_verify_realm {
|
||||||
my ($realm, $noerr) = @_;
|
my ($realm, $noerr) = @_;
|
||||||
|
|
||||||
@ -44,45 +47,56 @@ sub pve_verify_realm {
|
|||||||
return $realm;
|
return $realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_standard_option('realm', {
|
PVE::JSONSchema::register_standard_option(
|
||||||
|
'realm',
|
||||||
|
{
|
||||||
description => "Authentication domain ID",
|
description => "Authentication domain ID",
|
||||||
type => 'string', format => 'pve-realm',
|
type => 'string',
|
||||||
|
format => 'pve-realm',
|
||||||
maxLength => 32,
|
maxLength => 32,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
my $remove_options = "(?:acl|properties|entry)";
|
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.",
|
description => "Select what to sync.",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
enum => [qw(users groups both)],
|
enum => [qw(users groups both)],
|
||||||
optional => '1',
|
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"
|
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"
|
. " vanishes during a sync. The following values are possible: 'entry' removes the"
|
||||||
." user/group when not returned from the sync. 'properties' removes the set"
|
. " user/group when not returned from the sync. 'properties' removes the set"
|
||||||
." properties on existing user/group that do not appear in the source (even custom ones)."
|
. " properties on existing user/group that do not appear in the source (even custom ones)."
|
||||||
." 'acl' removes acls when the user/group is not returned from the sync."
|
. " 'acl' removes acls when the user/group is not returned from the sync."
|
||||||
." Instead of a list it also can be 'none' (the default).",
|
. " Instead of a list it also can be 'none' (the default).",
|
||||||
type => 'string',
|
type => 'string',
|
||||||
default => 'none',
|
default => 'none',
|
||||||
typetext => "([acl];[properties];[entry])|none",
|
typetext => "([acl];[properties];[entry])|none",
|
||||||
pattern => "(?:(?:$remove_options\;)*$remove_options)|none",
|
pattern => "(?:(?:$remove_options\;)*$remove_options)|none",
|
||||||
optional => '1',
|
optional => '1',
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
my $realm_sync_options_desc = {
|
my $realm_sync_options_desc = {
|
||||||
scope => get_standard_option('sync-scope'),
|
scope => get_standard_option('sync-scope'),
|
||||||
'remove-vanished' => get_standard_option('sync-remove-vanished'),
|
'remove-vanished' => get_standard_option('sync-remove-vanished'),
|
||||||
# TODO check/rewrite in pve7to8, and remove with 8.0
|
# TODO check/rewrite in pve7to8, and remove with 8.0
|
||||||
full => {
|
full => {
|
||||||
description => "DEPRECATED: use 'remove-vanished' instead. If set, uses the LDAP Directory as source of truth,"
|
description =>
|
||||||
." deleting users or groups not returned from the sync and removing"
|
"DEPRECATED: use 'remove-vanished' instead. If set, uses the LDAP Directory as source of truth,"
|
||||||
." all locally modified properties of synced users. If not set,"
|
. " deleting users or groups not returned from the sync and removing"
|
||||||
." only syncs information which is present in the synced data, and does not"
|
. " all locally modified properties of synced users. If not set,"
|
||||||
." delete or modify anything else.",
|
. " only syncs information which is present in the synced data, and does not"
|
||||||
|
. " delete or modify anything else.",
|
||||||
type => 'boolean',
|
type => 'boolean',
|
||||||
optional => '1',
|
optional => '1',
|
||||||
},
|
},
|
||||||
@ -94,7 +108,7 @@ my $realm_sync_options_desc = {
|
|||||||
},
|
},
|
||||||
purge => {
|
purge => {
|
||||||
description => "DEPRECATED: use 'remove-vanished' instead. Remove ACLs for users or"
|
description => "DEPRECATED: use 'remove-vanished' instead. Remove ACLs for users or"
|
||||||
." groups which were removed from the config during a sync.",
|
. " groups which were removed from the config during a sync.",
|
||||||
type => 'boolean',
|
type => 'boolean',
|
||||||
optional => '1',
|
optional => '1',
|
||||||
},
|
},
|
||||||
@ -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('realm-sync-options', $realm_sync_options_desc);
|
||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-userid', \&verify_username);
|
PVE::JSONSchema::register_format('pve-userid', \&verify_username);
|
||||||
|
|
||||||
sub verify_username {
|
sub verify_username {
|
||||||
my ($username, $noerr) = @_;
|
my ($username, $noerr) = @_;
|
||||||
|
|
||||||
@ -131,11 +146,15 @@ sub verify_username {
|
|||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVE::JSONSchema::register_standard_option('userid', {
|
PVE::JSONSchema::register_standard_option(
|
||||||
|
'userid',
|
||||||
|
{
|
||||||
description => "Full User ID, in the `name\@realm` format.",
|
description => "Full User ID, in the `name\@realm` format.",
|
||||||
type => 'string', format => 'pve-userid',
|
type => 'string',
|
||||||
|
format => 'pve-userid',
|
||||||
maxLength => 64,
|
maxLength => 64,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
my $tfa_format = {
|
my $tfa_format = {
|
||||||
type => {
|
type => {
|
||||||
@ -166,7 +185,8 @@ my $tfa_format = {
|
|||||||
description => "TOTP digits.",
|
description => "TOTP digits.",
|
||||||
format_description => 'COUNT',
|
format_description => 'COUNT',
|
||||||
type => 'integer',
|
type => 'integer',
|
||||||
minimum => 6, maximum => 8,
|
minimum => 6,
|
||||||
|
maximum => 8,
|
||||||
default => 6,
|
default => 6,
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
@ -182,12 +202,16 @@ my $tfa_format = {
|
|||||||
|
|
||||||
PVE::JSONSchema::register_format('pve-tfa-config', $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.",
|
description => "Use Two-factor authentication.",
|
||||||
type => 'string', format => 'pve-tfa-config',
|
type => 'string',
|
||||||
|
format => 'pve-tfa-config',
|
||||||
optional => 1,
|
optional => 1,
|
||||||
maxLength => 128,
|
maxLength => 128,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
sub parse_tfa_config {
|
sub parse_tfa_config {
|
||||||
my ($data) = @_;
|
my ($data) = @_;
|
||||||
@ -226,7 +250,7 @@ sub parse_config {
|
|||||||
my $cfg = $class->SUPER::parse_config($filename, $raw);
|
my $cfg = $class->SUPER::parse_config($filename, $raw);
|
||||||
|
|
||||||
my $default;
|
my $default;
|
||||||
foreach my $realm (keys %{$cfg->{ids}}) {
|
foreach my $realm (keys %{ $cfg->{ids} }) {
|
||||||
my $data = $cfg->{ids}->{$realm};
|
my $data = $cfg->{ids}->{$realm};
|
||||||
# make sure there is only one default marker
|
# make sure there is only one default marker
|
||||||
if ($data->{default}) {
|
if ($data->{default}) {
|
||||||
@ -255,12 +279,12 @@ sub parse_config {
|
|||||||
if !$cfg->{ids}->{pam}->{comment};
|
if !$cfg->{ids}->{pam}->{comment};
|
||||||
|
|
||||||
return $cfg;
|
return $cfg;
|
||||||
};
|
}
|
||||||
|
|
||||||
sub write_config {
|
sub write_config {
|
||||||
my ($class, $filename, $cfg) = @_;
|
my ($class, $filename, $cfg) = @_;
|
||||||
|
|
||||||
foreach my $realm (keys %{$cfg->{ids}}) {
|
foreach my $realm (keys %{ $cfg->{ids} }) {
|
||||||
my $data = $cfg->{ids}->{$realm};
|
my $data = $cfg->{ids}->{$realm};
|
||||||
if ($data->{comment}) {
|
if ($data->{comment}) {
|
||||||
$data->{comment} = PVE::Tools::encode_text($data->{comment});
|
$data->{comment} = PVE::Tools::encode_text($data->{comment});
|
||||||
|
@ -34,13 +34,16 @@ sub param_mapping {
|
|||||||
PVE::CLIHandler::get_standard_mapping('pve-password'),
|
PVE::CLIHandler::get_standard_mapping('pve-password'),
|
||||||
],
|
],
|
||||||
'create_ticket' => [
|
'create_ticket' => [
|
||||||
PVE::CLIHandler::get_standard_mapping('pve-password', {
|
PVE::CLIHandler::get_standard_mapping(
|
||||||
|
'pve-password',
|
||||||
|
{
|
||||||
func => sub {
|
func => sub {
|
||||||
# do not accept values given on cmdline
|
# do not accept values given on cmdline
|
||||||
return PVE::PTY::read_password('Enter password: ');
|
return PVE::PTY::read_password('Enter password: ');
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
]
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return $mapping->{$name};
|
return $mapping->{$name};
|
||||||
@ -93,10 +96,13 @@ __PACKAGE__->register_method({
|
|||||||
properties => {
|
properties => {
|
||||||
userid => get_standard_option('userid'),
|
userid => get_standard_option('userid'),
|
||||||
tokenid => get_standard_option('token-subid'),
|
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.",
|
description => "Only dump this specific path, not the whole tree.",
|
||||||
optional => 1,
|
optional => 1,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
@ -110,7 +116,8 @@ __PACKAGE__->register_method({
|
|||||||
$param->{userid} = PVE::AccessControl::join_tokenid($param->{userid}, $token_subid);
|
$param->{userid} = PVE::AccessControl::join_tokenid($param->{userid}, $token_subid);
|
||||||
|
|
||||||
return PVE::API2::AccessControl->permissions($param);
|
return PVE::API2::AccessControl->permissions($param);
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'delete_tfa',
|
name => 'delete_tfa',
|
||||||
@ -145,7 +152,8 @@ __PACKAGE__->register_method({
|
|||||||
cfs_write_file('priv/tfa.cfg', $tfa_cfg);
|
cfs_write_file('priv/tfa.cfg', $tfa_cfg);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
__PACKAGE__->register_method({
|
__PACKAGE__->register_method({
|
||||||
name => 'list_tfa',
|
name => 'list_tfa',
|
||||||
@ -175,7 +183,7 @@ __PACKAGE__->register_method({
|
|||||||
printf("${nl}${indent}%-9s %s\n${indent} %s\n", "$ty:", $id, $desc // '');
|
printf("${nl}${indent}%-9s %s\n${indent} %s\n", "$ty:", $id, $desc // '');
|
||||||
$nl = "\n";
|
$nl = "\n";
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
|
||||||
if (defined($userid)) {
|
if (defined($userid)) {
|
||||||
@ -190,60 +198,144 @@ __PACKAGE__->register_method({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
our $cmddef = {
|
our $cmddef = {
|
||||||
user => {
|
user => {
|
||||||
add => [ 'PVE::API2::User', 'create_user', ['userid'] ],
|
add => ['PVE::API2::User', 'create_user', ['userid']],
|
||||||
modify => [ 'PVE::API2::User', 'update_user', ['userid'] ],
|
modify => ['PVE::API2::User', 'update_user', ['userid']],
|
||||||
delete => [ 'PVE::API2::User', 'delete_user', ['userid'] ],
|
delete => ['PVE::API2::User', 'delete_user', ['userid']],
|
||||||
list => [ 'PVE::API2::User', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
|
list => [
|
||||||
permissions => [ 'PVE::API2::AccessControl', 'permissions', ['userid'], {}, $print_perm_result, $PVE::RESTHandler::standard_output_options],
|
'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 => {
|
tfa => {
|
||||||
delete => [ __PACKAGE__, 'delete_tfa', ['userid'] ],
|
delete => [__PACKAGE__, 'delete_tfa', ['userid']],
|
||||||
list => [ __PACKAGE__, 'list_tfa', ['userid'] ],
|
list => [__PACKAGE__, 'list_tfa', ['userid']],
|
||||||
unlock => [ 'PVE::API2::User', 'unlock_tfa', ['userid'] ],
|
unlock => ['PVE::API2::User', 'unlock_tfa', ['userid']],
|
||||||
},
|
},
|
||||||
token => {
|
token => {
|
||||||
add => [ 'PVE::API2::User', 'generate_token', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ],
|
add => [
|
||||||
modify => [ 'PVE::API2::User', 'update_token_info', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ],
|
'PVE::API2::User',
|
||||||
delete => [ 'PVE::API2::User', 'remove_token', ['userid', 'tokenid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options ],
|
'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' },
|
remove => { alias => 'delete' },
|
||||||
list => [ 'PVE::API2::User', 'token_index', ['userid'], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
|
list => [
|
||||||
permissions => [ __PACKAGE__, 'token_permissions', ['userid', 'tokenid'], {}, $print_perm_result, $PVE::RESTHandler::standard_output_options],
|
'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 => {
|
group => {
|
||||||
add => [ 'PVE::API2::Group', 'create_group', ['groupid'] ],
|
add => ['PVE::API2::Group', 'create_group', ['groupid']],
|
||||||
modify => [ 'PVE::API2::Group', 'update_group', ['groupid'] ],
|
modify => ['PVE::API2::Group', 'update_group', ['groupid']],
|
||||||
delete => [ 'PVE::API2::Group', 'delete_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 => {
|
role => {
|
||||||
add => [ 'PVE::API2::Role', 'create_role', ['roleid'] ],
|
add => ['PVE::API2::Role', 'create_role', ['roleid']],
|
||||||
modify => [ 'PVE::API2::Role', 'update_role', ['roleid'] ],
|
modify => ['PVE::API2::Role', 'update_role', ['roleid']],
|
||||||
delete => [ 'PVE::API2::Role', 'delete_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 => {
|
acl => {
|
||||||
modify => [ 'PVE::API2::ACL', 'update_acl', ['path'], { delete => 0 }],
|
modify => ['PVE::API2::ACL', 'update_acl', ['path'], { delete => 0 }],
|
||||||
delete => [ 'PVE::API2::ACL', 'update_acl', ['path'], { delete => 1 }],
|
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 => {
|
realm => {
|
||||||
add => [ 'PVE::API2::Domains', 'create', ['realm'] ],
|
add => ['PVE::API2::Domains', 'create', ['realm']],
|
||||||
modify => [ 'PVE::API2::Domains', 'update', ['realm'] ],
|
modify => ['PVE::API2::Domains', 'update', ['realm']],
|
||||||
delete => [ 'PVE::API2::Domains', 'delete', ['realm'] ],
|
delete => ['PVE::API2::Domains', 'delete', ['realm']],
|
||||||
list => [ 'PVE::API2::Domains', 'index', [], {}, $print_api_result, $PVE::RESTHandler::standard_output_options],
|
list => [
|
||||||
sync => [ 'PVE::API2::Domains', 'sync', ['realm'], ],
|
'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) = @_;
|
my ($res) = @_;
|
||||||
print "$res->{ticket}\n";
|
print "$res->{ticket}\n";
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
|
|
||||||
passwd => [ 'PVE::API2::AccessControl', 'change_password', ['userid'] ],
|
passwd => ['PVE::API2::AccessControl', 'change_password', ['userid']],
|
||||||
|
|
||||||
useradd => { alias => 'user add' },
|
useradd => { alias => 'user add' },
|
||||||
usermod => { alias => 'user modify' },
|
usermod => { alias => 'user modify' },
|
||||||
@ -272,10 +364,17 @@ eval {
|
|||||||
|
|
||||||
if ($have_pool_api) {
|
if ($have_pool_api) {
|
||||||
$cmddef->{pool} = {
|
$cmddef->{pool} = {
|
||||||
add => [ 'PVE::API2::Pool', 'create_pool', ['poolid'] ],
|
add => ['PVE::API2::Pool', 'create_pool', ['poolid']],
|
||||||
modify => [ 'PVE::API2::Pool', 'update_pool', ['poolid'] ],
|
modify => ['PVE::API2::Pool', 'update_pool', ['poolid']],
|
||||||
delete => [ 'PVE::API2::Pool', 'delete_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,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,10 @@ sub run {
|
|||||||
my $nodename = PVE::INotify::nodename();
|
my $nodename = PVE::INotify::nodename();
|
||||||
|
|
||||||
# check statefile in pmxcfs if we should start
|
# 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 $members = PVE::Cluster::get_members();
|
||||||
|
|
||||||
my $state = get_state($id);
|
my $state = get_state($id);
|
||||||
@ -141,13 +144,15 @@ sub run {
|
|||||||
my $last_upid = $state->{upid};
|
my $last_upid = $state->{upid};
|
||||||
my $last_time = $state->{time};
|
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)) {
|
if (defined($last_upid)) {
|
||||||
# first check if the next run is scheduled
|
# first check if the next run is scheduled
|
||||||
if (my $parsed = PVE::Tools::upid_decode($last_upid, 1)) {
|
if (my $parsed = PVE::Tools::upid_decode($last_upid, 1)) {
|
||||||
my $cal_spec = PVE::CalendarEvent::parse_calendar_event($schedule);
|
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
|
return 0 if !defined($next_sync) || $now < $next_sync; # not yet its (next) turn
|
||||||
}
|
}
|
||||||
# check if still running and node is online
|
# check if still running and node is online
|
||||||
@ -158,7 +163,7 @@ sub run {
|
|||||||
last if !$last_node_online; # it's not finished and the node is offline
|
last if !$last_node_online; # it's not finished and the node is offline
|
||||||
return 0; # not finished and online
|
return 0; # not finished and online
|
||||||
}
|
}
|
||||||
} elsif (defined($last_time) && ($last_time+60) > $now && $last_node_online) {
|
} elsif (defined($last_time) && ($last_time + 60) > $now && $last_node_online) {
|
||||||
# another node started this job in the last 60 seconds and is still online
|
# another node started this job in the last 60 seconds and is still online
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -168,32 +173,46 @@ sub run {
|
|||||||
# * it was started but either too long ago, or with an error
|
# * it was started but either too long ago, or with an error
|
||||||
# * the started task finished
|
# * the started task finished
|
||||||
|
|
||||||
save_state($id, {
|
save_state(
|
||||||
|
$id,
|
||||||
|
{
|
||||||
node => $nodename,
|
node => $nodename,
|
||||||
time => $now,
|
time => $now,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
return 1;
|
return 1;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
die $@ if $@;
|
die $@ if $@;
|
||||||
|
|
||||||
if ($shouldrun) {
|
if ($shouldrun) {
|
||||||
my $upid = eval { PVE::API2::Domains->sync($conf) };
|
my $upid = eval { PVE::API2::Domains->sync($conf) };
|
||||||
my $err = $@;
|
my $err = $@;
|
||||||
PVE::Cluster::cfs_lock_domain('realm-sync', undef, sub {
|
PVE::Cluster::cfs_lock_domain(
|
||||||
|
'realm-sync',
|
||||||
|
undef,
|
||||||
|
sub {
|
||||||
if ($err && !$upid) {
|
if ($err && !$upid) {
|
||||||
save_state($id, {
|
save_state(
|
||||||
|
$id,
|
||||||
|
{
|
||||||
node => $nodename,
|
node => $nodename,
|
||||||
time => $now,
|
time => $now,
|
||||||
error => $err,
|
error => $err,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
die "$err\n";
|
die "$err\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
save_state($id, {
|
save_state(
|
||||||
|
$id,
|
||||||
|
{
|
||||||
node => $nodename,
|
node => $nodename,
|
||||||
upid => $upid,
|
upid => $upid,
|
||||||
});
|
},
|
||||||
});
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
die $@ if $@;
|
die $@ if $@;
|
||||||
return $upid;
|
return $upid;
|
||||||
}
|
}
|
||||||
|
@ -32,22 +32,23 @@ my $compile_acl_path = sub {
|
|||||||
|
|
||||||
# permissions() will always prime the cache for the owning user
|
# permissions() will always prime the cache for the owning user
|
||||||
my ($username, undef) = PVE::AccessControl::split_tokenid($user, 1);
|
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
|
# resolve and cache roles of the current user/token for all pool ACL paths
|
||||||
if (!$data->{poolroles}) {
|
if (!$data->{poolroles}) {
|
||||||
$data->{poolroles} = {};
|
$data->{poolroles} = {};
|
||||||
|
|
||||||
foreach my $pool (keys %{$cfg->{pools}}) {
|
foreach my $pool (keys %{ $cfg->{pools} }) {
|
||||||
my $d = $cfg->{pools}->{$pool};
|
my $d = $cfg->{pools}->{$pool};
|
||||||
my $pool_roles = PVE::AccessControl::roles($cfg, $user, "/pool/$pool"); # pool roles
|
my $pool_roles = PVE::AccessControl::roles($cfg, $user, "/pool/$pool"); # pool roles
|
||||||
next if !scalar(keys %$pool_roles);
|
next if !scalar(keys %$pool_roles);
|
||||||
foreach my $vmid (keys %{$d->{vms}}) {
|
foreach my $vmid (keys %{ $d->{vms} }) {
|
||||||
for my $role (keys %$pool_roles) {
|
for my $role (keys %$pool_roles) {
|
||||||
$data->{poolroles}->{"/vms/$vmid"}->{$role} = 1;
|
$data->{poolroles}->{"/vms/$vmid"}->{$role} = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach my $storeid (keys %{$d->{storage}}) {
|
foreach my $storeid (keys %{ $d->{storage} }) {
|
||||||
for my $role (keys %$pool_roles) {
|
for my $role (keys %$pool_roles) {
|
||||||
$data->{poolroles}->{"/storage/$storeid"}->{$role} = 1;
|
$data->{poolroles}->{"/storage/$storeid"}->{$role} = 1;
|
||||||
}
|
}
|
||||||
@ -70,7 +71,7 @@ my $compile_acl_path = sub {
|
|||||||
# but pool ACL NoAccess trumps regular ACL
|
# but pool ACL NoAccess trumps regular ACL
|
||||||
$roles = { 'NoAccess' => 0 };
|
$roles = { 'NoAccess' => 0 };
|
||||||
} else {
|
} else {
|
||||||
foreach my $role (keys %{$data->{poolroles}->{$path}}) {
|
foreach my $role (keys %{ $data->{poolroles}->{$path} }) {
|
||||||
# only use role from pool ACL if regular ACL didn't already
|
# only use role from pool ACL if regular ACL didn't already
|
||||||
# set it, and never set propagation for pool-derived ACLs
|
# set it, and never set propagation for pool-derived ACLs
|
||||||
$roles->{$role} = 0 if !defined($roles->{$role});
|
$roles->{$role} = 0 if !defined($roles->{$role});
|
||||||
@ -100,7 +101,7 @@ my $compile_acl_path = sub {
|
|||||||
# map of set privs to their propagation flag value, for the owning user
|
# map of set privs to their propagation flag value, for the owning user
|
||||||
my $user_privs = $cache->{$username}->{privs}->{$path};
|
my $user_privs = $cache->{$username}->{privs}->{$path};
|
||||||
# list of privs set both for token and owning user
|
# list of privs set both for token and owning user
|
||||||
my $filtered_privs = [ grep { defined($user_privs->{$_}) } keys %$privs ];
|
my $filtered_privs = [grep { defined($user_privs->{$_}) } keys %$privs];
|
||||||
# intersection of privs using filtered list, combining both propagation
|
# intersection of privs using filtered list, combining both propagation
|
||||||
# flags
|
# flags
|
||||||
$privs = { map { $_ => $user_privs->{$_} && $privs->{$_} } @$filtered_privs };
|
$privs = { map { $_ => $user_privs->{$_} && $privs->{$_} } @$filtered_privs };
|
||||||
@ -137,7 +138,7 @@ sub permissions {
|
|||||||
|
|
||||||
if ($user eq 'root@pam') { # root can do anything
|
if ($user eq 'root@pam') { # root can do anything
|
||||||
my $cfg = $self->{user_cfg};
|
my $cfg = $self->{user_cfg};
|
||||||
return { map { $_ => 1 } keys %{$cfg->{roles}->{'Administrator'}} };
|
return { map { $_ => 1 } keys %{ $cfg->{roles}->{'Administrator'} } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!defined($path)) {
|
if (!defined($path)) {
|
||||||
@ -193,10 +194,14 @@ sub compute_api_permission {
|
|||||||
|
|
||||||
my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn', '/mapping'];
|
my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn', '/mapping'];
|
||||||
my $defined_paths = [];
|
my $defined_paths = [];
|
||||||
PVE::AccessControl::iterate_acl_tree("/", $usercfg->{acl_root}, sub {
|
PVE::AccessControl::iterate_acl_tree(
|
||||||
|
"/",
|
||||||
|
$usercfg->{acl_root},
|
||||||
|
sub {
|
||||||
my ($path, $node) = @_;
|
my ($path, $node) = @_;
|
||||||
push @$defined_paths, $path;
|
push @$defined_paths, $path;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
my $checked_paths = {};
|
my $checked_paths = {};
|
||||||
foreach my $path (@$required_paths, @$defined_paths) {
|
foreach my $path (@$required_paths, @$defined_paths) {
|
||||||
@ -251,18 +256,22 @@ sub get_effective_permissions {
|
|||||||
my $cfg = $self->{user_cfg};
|
my $cfg = $self->{user_cfg};
|
||||||
|
|
||||||
# paths explicitly listed in ACLs
|
# 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) = @_;
|
my ($path, $node) = @_;
|
||||||
$paths->{$path} = 1;
|
$paths->{$path} = 1;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
# paths referenced by pool definitions
|
# paths referenced by pool definitions
|
||||||
foreach my $pool (keys %{$cfg->{pools}}) {
|
foreach my $pool (keys %{ $cfg->{pools} }) {
|
||||||
my $d = $cfg->{pools}->{$pool};
|
my $d = $cfg->{pools}->{$pool};
|
||||||
foreach my $vmid (keys %{$d->{vms}}) {
|
foreach my $vmid (keys %{ $d->{vms} }) {
|
||||||
$paths->{"/vms/$vmid"} = 1;
|
$paths->{"/vms/$vmid"} = 1;
|
||||||
}
|
}
|
||||||
foreach my $storeid (keys %{$d->{storage}}) {
|
foreach my $storeid (keys %{ $d->{storage} }) {
|
||||||
$paths->{"/storage/$storeid"} = 1;
|
$paths->{"/storage/$storeid"} = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,10 +299,10 @@ sub check {
|
|||||||
return undef if $noerr;
|
return undef if $noerr;
|
||||||
raise_perm_exc("$path, $priv");
|
raise_perm_exc("$path, $priv");
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
};
|
}
|
||||||
|
|
||||||
sub check_any {
|
sub check_any {
|
||||||
my ($self, $user, $path, $privs, $noerr) = @_;
|
my ($self, $user, $path, $privs, $noerr) = @_;
|
||||||
@ -307,14 +316,14 @@ sub check_any {
|
|||||||
$found = 1;
|
$found = 1;
|
||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
return 1 if $found;
|
return 1 if $found;
|
||||||
|
|
||||||
return undef if $noerr;
|
return undef if $noerr;
|
||||||
|
|
||||||
raise_perm_exc("$path, " . join("|", @$privs));
|
raise_perm_exc("$path, " . join("|", @$privs));
|
||||||
};
|
}
|
||||||
|
|
||||||
sub check_full {
|
sub check_full {
|
||||||
my ($self, $username, $path, $privs, $any, $noerr) = @_;
|
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 if $self->check_full($user, "/pool/$pool", $privs, $any, 1);
|
||||||
}
|
}
|
||||||
return $self->check_full($user, "/vms/$vmid", $privs, $any, $noerr);
|
return $self->check_full($user, "/vms/$vmid", $privs, $any, $noerr);
|
||||||
};
|
}
|
||||||
|
|
||||||
sub is_group_member {
|
sub is_group_member {
|
||||||
my ($self, $group, $user) = @_;
|
my ($self, $group, $user) = @_;
|
||||||
@ -403,7 +412,7 @@ sub filter_groups {
|
|||||||
my $cfg = $self->{user_cfg};
|
my $cfg = $self->{user_cfg};
|
||||||
|
|
||||||
my $groups = {};
|
my $groups = {};
|
||||||
foreach my $group (keys %{$cfg->{groups}}) {
|
foreach my $group (keys %{ $cfg->{groups} }) {
|
||||||
my $path = "/access/groups/$group";
|
my $path = "/access/groups/$group";
|
||||||
if ($self->check_full($user, $path, $privs, $any, 1)) {
|
if ($self->check_full($user, $path, $privs, $any, 1)) {
|
||||||
$groups->{$group} = $cfg->{groups}->{$group};
|
$groups->{$group} = $cfg->{groups}->{$group};
|
||||||
@ -422,7 +431,7 @@ sub group_member_join {
|
|||||||
foreach my $group (@$grouplist) {
|
foreach my $group (@$grouplist) {
|
||||||
my $data = $cfg->{groups}->{$group};
|
my $data = $cfg->{groups}->{$group};
|
||||||
next if !$data;
|
next if !$data;
|
||||||
foreach my $user (keys %{$data->{users}}) {
|
foreach my $user (keys %{ $data->{users} }) {
|
||||||
$users->{$user} = 1;
|
$users->{$user} = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,9 +442,9 @@ sub group_member_join {
|
|||||||
sub check_perm_modify {
|
sub check_perm_modify {
|
||||||
my ($self, $username, $path, $noerr) = @_;
|
my ($self, $username, $path, $noerr) = @_;
|
||||||
|
|
||||||
return $self->check($username, '/access', [ 'Permissions.Modify' ], $noerr) if !$path;
|
return $self->check($username, '/access', ['Permissions.Modify'], $noerr) if !$path;
|
||||||
|
|
||||||
my $testperms = [ 'Permissions.Modify' ];
|
my $testperms = ['Permissions.Modify'];
|
||||||
if ($path =~ m|^/storage/.+$|) {
|
if ($path =~ m|^/storage/.+$|) {
|
||||||
push @$testperms, 'Datastore.Allocate';
|
push @$testperms, 'Datastore.Allocate';
|
||||||
} elsif ($path =~ m|^/vms/.+$|) {
|
} elsif ($path =~ m|^/vms/.+$|) {
|
||||||
@ -535,7 +544,7 @@ sub exec_api2_perm_check {
|
|||||||
} else {
|
} else {
|
||||||
die "unknown permission test";
|
die "unknown permission test";
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
sub check_api2_permissions {
|
sub check_api2_permissions {
|
||||||
my ($self, $perm, $username, $param) = @_;
|
my ($self, $perm, $username, $param) = @_;
|
||||||
@ -581,8 +590,7 @@ sub init {
|
|||||||
$self->{aclversion} = undef;
|
$self->{aclversion} = undef;
|
||||||
|
|
||||||
return $self;
|
return $self;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
# init_request - must be called before each RPC request
|
# init_request - must be called before each RPC request
|
||||||
sub init_request {
|
sub init_request {
|
||||||
@ -609,8 +617,12 @@ sub init_request {
|
|||||||
$self->{user_cfg} = $cfg;
|
$self->{user_cfg} = $cfg;
|
||||||
} else {
|
} else {
|
||||||
my $ucvers = PVE::Cluster::cfs_file_version('user.cfg');
|
my $ucvers = PVE::Cluster::cfs_file_version('user.cfg');
|
||||||
if (!$self->{aclcache} || !defined($self->{aclversion}) ||
|
if (
|
||||||
!defined($ucvers) || ($ucvers ne $self->{aclversion})) {
|
!$self->{aclcache}
|
||||||
|
|| !defined($self->{aclversion})
|
||||||
|
|| !defined($ucvers)
|
||||||
|
|| ($ucvers ne $self->{aclversion})
|
||||||
|
) {
|
||||||
$self->{aclversion} = $ucvers;
|
$self->{aclversion} = $ucvers;
|
||||||
my $cfg = PVE::Cluster::cfs_read_file('user.cfg');
|
my $cfg = PVE::Cluster::cfs_read_file('user.cfg');
|
||||||
$self->{user_cfg} = $cfg;
|
$self->{user_cfg} = $cfg;
|
||||||
|
@ -49,7 +49,10 @@ sub generate_token {
|
|||||||
|
|
||||||
PVE::AccessControl::pve_verify_tokenid($tokenid);
|
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 $uuid = UUID::uuid();
|
||||||
my $token_cfg = PVE::Cluster::cfs_read_file('priv/token.cfg');
|
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);
|
PVE::Cluster::cfs_write_file('priv/token.cfg', $token_cfg);
|
||||||
|
|
||||||
return $uuid;
|
return $uuid;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
die "$@\n" if defined($@);
|
die "$@\n" if defined($@);
|
||||||
|
|
||||||
@ -68,13 +72,17 @@ sub generate_token {
|
|||||||
sub delete_token {
|
sub delete_token {
|
||||||
my ($tokenid) = @_;
|
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');
|
my $token_cfg = PVE::Cluster::cfs_read_file('priv/token.cfg');
|
||||||
|
|
||||||
delete $token_cfg->{$tokenid};
|
delete $token_cfg->{$tokenid};
|
||||||
|
|
||||||
PVE::Cluster::cfs_write_file('priv/token.cfg', $token_cfg);
|
PVE::Cluster::cfs_write_file('priv/token.cfg', $token_cfg);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
die "$@\n" if defined($@);
|
die "$@\n" if defined($@);
|
||||||
}
|
}
|
||||||
|
@ -79,13 +79,15 @@ my $stranger_user_tests = [
|
|||||||
|
|
||||||
my $stranger_nonprivsep_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',
|
rpcuser => 'stranger@pve!noprivsep',
|
||||||
params => {},
|
params => {},
|
||||||
result => $stranger_perms,
|
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',
|
rpcuser => 'stranger@pve!noprivsep',
|
||||||
params => {
|
params => {
|
||||||
userid => 'stranger@pve!noprivsep',
|
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,
|
should_fail => 1,
|
||||||
rpcuser => 'stranger@pve!noprivsep',
|
rpcuser => 'stranger@pve!noprivsep',
|
||||||
params => {
|
params => {
|
||||||
userid => 'auditor@pam!noprivsep',
|
userid => 'auditor@pam!noprivsep',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
my $stranger_privsep_tests = [
|
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',
|
rpcuser => 'stranger@pve!privsep',
|
||||||
params => {},
|
params => {},
|
||||||
result => $stranger_privsep_perms,
|
result => $stranger_privsep_perms,
|
||||||
@ -157,7 +161,7 @@ my $stranger_privsep_tests = [
|
|||||||
rpcuser => 'stranger@pve!privsep',
|
rpcuser => 'stranger@pve!privsep',
|
||||||
params => {
|
params => {
|
||||||
userid => 'auditor@pam!noprivsep',
|
userid => 'auditor@pam!noprivsep',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -216,13 +220,15 @@ my $auditor_user_tests = [
|
|||||||
|
|
||||||
my $auditor_nonprivsep_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',
|
rpcuser => 'auditor@pam!noprivsep',
|
||||||
params => {},
|
params => {},
|
||||||
result => $auditor_perms,
|
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',
|
rpcuser => 'auditor@pam!noprivsep',
|
||||||
params => {
|
params => {
|
||||||
userid => 'auditor@pam!noprivsep',
|
userid => 'auditor@pam!noprivsep',
|
||||||
@ -247,7 +253,8 @@ my $auditor_nonprivsep_tests = [
|
|||||||
result => $auditor_privsep_perms,
|
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',
|
rpcuser => 'auditor@pam!noprivsep',
|
||||||
params => {
|
params => {
|
||||||
userid => 'stranger@pve!noprivsep',
|
userid => 'stranger@pve!noprivsep',
|
||||||
|
@ -11,8 +11,8 @@ my $username = shift;
|
|||||||
die "Username missing" if !$username;
|
die "Username missing" if !$username;
|
||||||
|
|
||||||
my $password = PVE::PTY::read_password('password: ');
|
my $password = PVE::PTY::read_password('password: ');
|
||||||
PVE::AccessControl::authenticate_user($username,$password);
|
PVE::AccessControl::authenticate_user($username, $password);
|
||||||
|
|
||||||
print "Authentication Successful!!\n";
|
print "Authentication Successful!!\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -12,8 +12,8 @@ use PVE::RPCEnvironment;
|
|||||||
# dump-perm.pl -f myuser.cfg root /
|
# dump-perm.pl -f myuser.cfg root /
|
||||||
|
|
||||||
my $opt_file;
|
my $opt_file;
|
||||||
if (!GetOptions ("file=s" => \$opt_file)) {
|
if (!GetOptions("file=s" => \$opt_file)) {
|
||||||
exit (-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $username = shift;
|
my $username = shift;
|
||||||
@ -21,7 +21,7 @@ my $path = shift;
|
|||||||
|
|
||||||
if (!($username && $path)) {
|
if (!($username && $path)) {
|
||||||
print "usage: $0 <username> <path>\n";
|
print "usage: $0 <username> <path>\n";
|
||||||
exit (-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
my $cfg;
|
my $cfg;
|
||||||
@ -38,4 +38,4 @@ my $perm = $rpcenv->permissions($username, $path);
|
|||||||
print "permission for user '$username' on '$path':\n";
|
print "permission for user '$username' on '$path':\n";
|
||||||
print join(',', keys %$perm) . "\n";
|
print join(',', keys %$perm) . "\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -13,4 +13,4 @@ $cfg = PVE::AccessControl::load_user_config();
|
|||||||
|
|
||||||
print Dumper($cfg) . "\n";
|
print Dumper($cfg) . "\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -43,7 +43,7 @@ sub default_roles_with {
|
|||||||
|
|
||||||
sub default_users {
|
sub default_users {
|
||||||
my $users = dclone($default_user_cfg->{users});
|
my $users = dclone($default_user_cfg->{users});
|
||||||
return { map { $_ => $add_default_user_properties->($users->{$_}); } keys %$users};
|
return { map { $_ => $add_default_user_properties->($users->{$_}); } keys %$users };
|
||||||
}
|
}
|
||||||
|
|
||||||
sub default_users_with {
|
sub default_users_with {
|
||||||
@ -101,7 +101,7 @@ sub default_pool_vms_with {
|
|||||||
|
|
||||||
my $vms = {};
|
my $vms = {};
|
||||||
foreach my $pool (@$extra_pools) {
|
foreach my $pool (@$extra_pools) {
|
||||||
foreach my $vmid (keys %{$pool->{vms}}) {
|
foreach my $vmid (keys %{ $pool->{vms} }) {
|
||||||
$vms->{$vmid} = $pool->{id};
|
$vms->{$vmid} = $pool->{id};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ sub default_acls_with {
|
|||||||
foreach my $a (@$extra_acls) {
|
foreach my $a (@$extra_acls) {
|
||||||
my $acl = dclone($a);
|
my $acl = dclone($a);
|
||||||
my $path = delete $acl->{path};
|
my $path = delete $acl->{path};
|
||||||
my $split_path = [ split("/", $path) ];
|
my $split_path = [split("/", $path)];
|
||||||
my $node = $acls;
|
my $node = $acls;
|
||||||
for my $p (@$split_path) {
|
for my $p (@$split_path) {
|
||||||
next if !$p;
|
next if !$p;
|
||||||
@ -129,7 +129,7 @@ sub default_acls_with {
|
|||||||
$node->{children}->{$p} = {} if !$node->{children}->{$p};
|
$node->{children}->{$p} = {} if !$node->{children}->{$p};
|
||||||
$node = $node->{children}->{$p};
|
$node = $node->{children}->{$p};
|
||||||
}
|
}
|
||||||
%$node = ( %$acl );
|
%$node = (%$acl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $acls;
|
return $acls;
|
||||||
@ -241,8 +241,8 @@ my $default_cfg = {
|
|||||||
},
|
},
|
||||||
test_pool_members => {
|
test_pool_members => {
|
||||||
'id' => 'testpool',
|
'id' => 'testpool',
|
||||||
vms => { 123 => 1, 1234 => 1},
|
vms => { 123 => 1, 1234 => 1 },
|
||||||
storage => { 'local' => 1, 'local-zfs' => 1},
|
storage => { 'local' => 1, 'local-zfs' => 1 },
|
||||||
pools => {},
|
pools => {},
|
||||||
},
|
},
|
||||||
test_pool_duplicate_vms => {
|
test_pool_duplicate_vms => {
|
||||||
@ -254,7 +254,7 @@ my $default_cfg = {
|
|||||||
test_pool_duplicate_storages => {
|
test_pool_duplicate_storages => {
|
||||||
'id' => 'test_duplicate_storages',
|
'id' => 'test_duplicate_storages',
|
||||||
vms => {},
|
vms => {},
|
||||||
storage => { 'local' => 1, 'local-zfs' => 1},
|
storage => { 'local' => 1, 'local-zfs' => 1 },
|
||||||
pools => {},
|
pools => {},
|
||||||
},
|
},
|
||||||
acl_simple_user => {
|
acl_simple_user => {
|
||||||
@ -443,7 +443,8 @@ my $default_raw = {
|
|||||||
'acl_complex_users_1' => 'acl:1:/storage:test@pam:PVEDatastoreAdmin:',
|
'acl_complex_users_1' => 'acl:1:/storage:test@pam:PVEDatastoreAdmin:',
|
||||||
'acl_complex_users_2' => 'acl:1:/storage:test2@pam:PVEDatastoreUser:',
|
'acl_complex_users_2' => 'acl:1:/storage:test2@pam:PVEDatastoreUser:',
|
||||||
'acl_simple_token' => 'acl:1:/:test@pam!full:PVEVMAdmin:',
|
'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_2' => 'acl:1:/storage:test2@pam!privsep:PVEDatastoreUser:',
|
||||||
'acl_complex_tokens_1_missing' => 'acl:1:/storage:test2@pam!expired:PVEDatastoreAdmin:',
|
'acl_complex_tokens_1_missing' => 'acl:1:/storage:test2@pam!expired:PVEDatastoreAdmin:',
|
||||||
'acl_simple_group' => 'acl:1:/:@testgroup:PVEVMAdmin:',
|
'acl_simple_group' => 'acl:1:/:@testgroup:PVEVMAdmin:',
|
||||||
@ -478,7 +479,7 @@ my $tests = [
|
|||||||
users => default_users(),
|
users => default_users(),
|
||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
},
|
},
|
||||||
raw => $default_raw->{users}->{'root@pam'}."\n\n\n\n\n",
|
raw => $default_raw->{users}->{'root@pam'} . "\n\n\n\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "group_empty",
|
name => "group_empty",
|
||||||
@ -488,10 +489,9 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
groups => default_groups_with([$default_cfg->{'test_group_empty'}]),
|
groups => default_groups_with([$default_cfg->{'test_group_empty'}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n".
|
. $default_raw->{users}->{'root@pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_empty'}."\n\n".
|
. $default_raw->{groups}->{'test_group_empty'} . "\n\n" . "\n\n",
|
||||||
"\n\n",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "group_inexisting_member",
|
name => "group_inexisting_member",
|
||||||
@ -501,14 +501,13 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
groups => default_groups_with([$default_cfg->{'test_group_empty'}]),
|
groups => default_groups_with([$default_cfg->{'test_group_empty'}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n".
|
. $default_raw->{users}->{'root@pam'} . "\n\n"
|
||||||
"group:testgroup:does_not_exist::".
|
. "group:testgroup:does_not_exist::"
|
||||||
"\n\n\n\n",
|
. "\n\n\n\n",
|
||||||
expected_raw => "".
|
expected_raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n".
|
. $default_raw->{users}->{'root@pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_empty'}."\n\n".
|
. $default_raw->{groups}->{'test_group_empty'} . "\n\n" . "\n\n",
|
||||||
"\n\n",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "group_invalid_member",
|
name => "group_invalid_member",
|
||||||
@ -517,10 +516,9 @@ my $tests = [
|
|||||||
users => default_users(),
|
users => default_users(),
|
||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n".
|
. $default_raw->{users}->{'root@pam'} . "\n\n"
|
||||||
'group:inval!d:root@pam:'.
|
. 'group:inval!d:root@pam:' . "\n\n",
|
||||||
"\n\n",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "group_with_one_member",
|
name => "group_with_one_member",
|
||||||
@ -530,26 +528,26 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
groups => default_groups_with([$default_cfg->{'test_group_single_member'}]),
|
groups => default_groups_with([$default_cfg->{'test_group_single_member'}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_single_member'}."\n\n".
|
. $default_raw->{groups}->{'test_group_single_member'} . "\n\n" . "\n\n",
|
||||||
"\n\n",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "group_with_members",
|
name => "group_with_members",
|
||||||
config => {
|
config => {
|
||||||
acl_root => default_acls(),
|
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(),
|
roles => default_roles(),
|
||||||
groups => default_groups_with([$default_cfg->{'test_group_members'}]),
|
groups => default_groups_with([$default_cfg->{'test_group_members'}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_members'}."\n\n".
|
. $default_raw->{groups}->{'test_group_members'} . "\n\n" . "\n\n",
|
||||||
"\n\n",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "token_simple",
|
name => "token_simple",
|
||||||
@ -558,27 +556,30 @@ my $tests = [
|
|||||||
users => default_users_with([$default_cfg->{test_pam_with_token}]),
|
users => default_users_with([$default_cfg->{test_pam_with_token}]),
|
||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_simple'}."\n\n\n\n\n",
|
. $default_raw->{tokens}->{'test_token_simple'}
|
||||||
|
. "\n\n\n\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "token_multi",
|
name => "token_multi",
|
||||||
config => {
|
config => {
|
||||||
acl_root => default_acls(),
|
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(),
|
roles => default_roles(),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_simple'}."\n".
|
. $default_raw->{tokens}->{'test_token_simple'} . "\n"
|
||||||
"\n\n\n\n",
|
. "\n\n\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "custom_role_with_single_priv",
|
name => "custom_role_with_single_priv",
|
||||||
@ -587,9 +588,10 @@ my $tests = [
|
|||||||
users => default_users(),
|
users => default_users(),
|
||||||
roles => default_roles_with([$default_cfg->{test_role_single_priv}]),
|
roles => default_roles_with([$default_cfg->{test_role_single_priv}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{roles}->{'test_role_single_priv'}."\n\n",
|
. "\n\n\n\n"
|
||||||
|
. $default_raw->{roles}->{'test_role_single_priv'} . "\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "custom_role_with_privs",
|
name => "custom_role_with_privs",
|
||||||
@ -598,9 +600,10 @@ my $tests = [
|
|||||||
users => default_users(),
|
users => default_users(),
|
||||||
roles => default_roles_with([$default_cfg->{test_role_privs}]),
|
roles => default_roles_with([$default_cfg->{test_role_privs}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{roles}->{'test_role_privs'}."\n\n",
|
. "\n\n\n\n"
|
||||||
|
. $default_raw->{roles}->{'test_role_privs'} . "\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "custom_role_with_duplicate_privs",
|
name => "custom_role_with_duplicate_privs",
|
||||||
@ -609,12 +612,14 @@ my $tests = [
|
|||||||
users => default_users(),
|
users => default_users(),
|
||||||
roles => default_roles_with([$default_cfg->{test_role_privs}]),
|
roles => default_roles_with([$default_cfg->{test_role_privs}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{roles}->{'test_role_privs_duplicate'}."\n\n",
|
. "\n\n\n\n"
|
||||||
expected_raw => "".
|
. $default_raw->{roles}->{'test_role_privs_duplicate'} . "\n\n",
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
|
expected_raw => ""
|
||||||
$default_raw->{roles}->{'test_role_privs'}."\n\n",
|
. $default_raw->{users}->{'root@pam'}
|
||||||
|
. "\n\n\n\n"
|
||||||
|
. $default_raw->{roles}->{'test_role_privs'} . "\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "custom_role_with_invalid_priv",
|
name => "custom_role_with_invalid_priv",
|
||||||
@ -623,12 +628,14 @@ my $tests = [
|
|||||||
users => default_users(),
|
users => default_users(),
|
||||||
roles => default_roles_with([$default_cfg->{test_role_privs}]),
|
roles => default_roles_with([$default_cfg->{test_role_privs}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{roles}->{'test_role_privs_invalid'}."\n\n",
|
. "\n\n\n\n"
|
||||||
expected_raw => "".
|
. $default_raw->{roles}->{'test_role_privs_invalid'} . "\n\n",
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
|
expected_raw => ""
|
||||||
$default_raw->{roles}->{'test_role_privs'}."\n\n",
|
. $default_raw->{users}->{'root@pam'}
|
||||||
|
. "\n\n\n\n"
|
||||||
|
. $default_raw->{roles}->{'test_role_privs'} . "\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "pool_empty",
|
name => "pool_empty",
|
||||||
@ -638,9 +645,11 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
|
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{pools}->{'test_pool_empty'}."\n\n\n",
|
. "\n\n\n"
|
||||||
|
. $default_raw->{pools}->{'test_pool_empty'}
|
||||||
|
. "\n\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "pool_invalid",
|
name => "pool_invalid",
|
||||||
@ -650,12 +659,16 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
|
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{pools}->{'test_pool_invalid'}."\n\n\n",
|
. "\n\n\n"
|
||||||
expected_raw => "".
|
. $default_raw->{pools}->{'test_pool_invalid'}
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n".
|
. "\n\n\n",
|
||||||
$default_raw->{pools}->{'test_pool_empty'}."\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",
|
name => "pool_members",
|
||||||
@ -666,9 +679,11 @@ my $tests = [
|
|||||||
pools => default_pools_with([$default_cfg->{test_pool_members}]),
|
pools => default_pools_with([$default_cfg->{test_pool_members}]),
|
||||||
vms => default_pool_vms_with([$default_cfg->{test_pool_members}]),
|
vms => default_pool_vms_with([$default_cfg->{test_pool_members}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{pools}->{'test_pool_members'}."\n\n\n",
|
. "\n\n\n"
|
||||||
|
. $default_raw->{pools}->{'test_pool_members'}
|
||||||
|
. "\n\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "pool_duplicate_members",
|
name => "pool_duplicate_members",
|
||||||
@ -676,19 +691,28 @@ my $tests = [
|
|||||||
acl_root => default_acls(),
|
acl_root => default_acls(),
|
||||||
users => default_users(),
|
users => default_users(),
|
||||||
roles => default_roles(),
|
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}]),
|
vms => default_pool_vms_with([$default_cfg->{test_pool_members}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{pools}->{'test_pool_members'}."\n".
|
. "\n\n\n"
|
||||||
$default_raw->{pools}->{'test_pool_duplicate_vms'}."\n".
|
. $default_raw->{pools}->{'test_pool_members'} . "\n"
|
||||||
$default_raw->{pools}->{'test_pool_duplicate_storages'}."\n",
|
. $default_raw->{pools}->{'test_pool_duplicate_vms'} . "\n"
|
||||||
expected_raw => "".
|
. $default_raw->{pools}->{'test_pool_duplicate_storages'} . "\n",
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n".
|
expected_raw => ""
|
||||||
$default_raw->{pools}->{'test_pool_duplicate_storages'}."\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{pools}->{'test_pool_duplicate_vms_expected'}."\n".
|
. "\n\n\n"
|
||||||
$default_raw->{pools}->{'test_pool_members'}."\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",
|
name => "acl_simple_user",
|
||||||
@ -697,39 +721,47 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
acl_root => default_acls_with([$default_cfg->{acl_simple_user}]),
|
acl_root => default_acls_with([$default_cfg->{acl_simple_user}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
|
. $default_raw->{users}->{'test_pam'}
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n",
|
. "\n\n\n\n\n"
|
||||||
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_complex_users",
|
name => "acl_complex_users",
|
||||||
config => {
|
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(),
|
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 => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
|
. $default_raw->{users}->{'test_pam'}
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n".
|
. "\n\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
|
||||||
|
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_complex_missing_user",
|
name => "acl_complex_missing_user",
|
||||||
config => {
|
config => {
|
||||||
users => default_users_with([$default_cfg->{test2_pam}]),
|
users => default_users_with([$default_cfg->{test2_pam}]),
|
||||||
roles => default_roles(),
|
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 => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n\n\n\n\n".
|
. $default_raw->{users}->{'test2_pam'}
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n".
|
. "\n\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
|
||||||
|
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_simple_group",
|
name => "acl_simple_group",
|
||||||
@ -739,57 +771,78 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
acl_root => default_acls_with([$default_cfg->{acl_simple_group}]),
|
acl_root => default_acls_with([$default_cfg->{acl_simple_group}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_single_member'}."\n\n\n\n".
|
. $default_raw->{groups}->{'test_group_single_member'}
|
||||||
$default_raw->{acl}->{'acl_simple_group'}."\n",
|
. "\n\n\n\n"
|
||||||
|
. $default_raw->{acl}->{'acl_simple_group'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_complex_groups",
|
name => "acl_complex_groups",
|
||||||
config => {
|
config => {
|
||||||
users => default_users_with([$default_cfg->{test_pam_with_group}, $default_cfg->{'test2_pam_with_group'}, $default_cfg->{'test3_pam'}]),
|
users => default_users_with(
|
||||||
groups => default_groups_with([$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]),
|
[
|
||||||
|
$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(),
|
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 => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\n".
|
. $default_raw->{users}->{'test3_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n".
|
. $default_raw->{groups}->{'test_group_second'} . "\n"
|
||||||
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
|
. $default_raw->{groups}->{'test_group_members'}
|
||||||
$default_raw->{acl}->{'acl_simple_group'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
|
||||||
|
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_complex_missing_group",
|
name => "acl_complex_missing_group",
|
||||||
config => {
|
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'}]),
|
groups => default_groups_with([$default_cfg->{'test_group_second'}]),
|
||||||
roles => default_roles(),
|
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 => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\n".
|
. $default_raw->{users}->{'test3_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n".
|
. $default_raw->{groups}->{'test_group_second'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_simple_group'}."\n".
|
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
|
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
|
||||||
expected_raw => "".
|
expected_raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\n".
|
. $default_raw->{users}->{'test3_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n\n\n\n".
|
. $default_raw->{groups}->{'test_group_second'}
|
||||||
$default_raw->{acl}->{'acl_simple_group'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
|
||||||
|
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_simple_token",
|
name => "acl_simple_token",
|
||||||
@ -798,57 +851,66 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
acl_root => default_acls_with([$default_cfg->{acl_simple_token}]),
|
acl_root => default_acls_with([$default_cfg->{acl_simple_token}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_simple'}."\n\n\n\n\n".
|
. $default_raw->{tokens}->{'test_token_simple'}
|
||||||
$default_raw->{acl}->{'acl_simple_token'}."\n",
|
. "\n\n\n\n\n"
|
||||||
|
. $default_raw->{acl}->{'acl_simple_token'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_complex_tokens",
|
name => "acl_complex_tokens",
|
||||||
config => {
|
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(),
|
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 => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_simple'}."\n\n\n\n\n".
|
. $default_raw->{tokens}->{'test_token_simple'}
|
||||||
$default_raw->{acl}->{'acl_simple_token'}."\n".
|
. "\n\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_tokens_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_token'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_tokens_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_tokens_1'} . "\n"
|
||||||
|
. $default_raw->{acl}->{'acl_complex_tokens_2'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_complex_missing_token",
|
name => "acl_complex_missing_token",
|
||||||
config => {
|
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(),
|
roles => default_roles(),
|
||||||
acl_root => default_acls_with([$default_cfg->{acl_complex_missing_token}]),
|
acl_root => default_acls_with([$default_cfg->{acl_complex_missing_token}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_simple_token'}."\n".
|
. $default_raw->{acl}->{'acl_simple_token'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_tokens_1'}."\n".
|
. $default_raw->{acl}->{'acl_complex_tokens_1'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_tokens_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_tokens_2'} . "\n",
|
||||||
expected_raw => "".
|
expected_raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_expired'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_expired'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_full'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_full'} . "\n"
|
||||||
$default_raw->{tokens}->{'test_token_multi_privsep'}."\n".
|
. $default_raw->{tokens}->{'test_token_multi_privsep'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
|
. $default_raw->{users}->{'test_pam'}
|
||||||
$default_raw->{acl}->{'acl_complex_tokens_1_missing'}."\n".
|
. "\n\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_tokens_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_tokens_1_missing'} . "\n"
|
||||||
|
. $default_raw->{acl}->{'acl_complex_tokens_2'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_missing_role",
|
name => "acl_missing_role",
|
||||||
@ -857,127 +919,149 @@ my $tests = [
|
|||||||
roles => default_roles(),
|
roles => default_roles(),
|
||||||
acl_root => default_acls_with([$default_cfg->{acl_simple_user}]),
|
acl_root => default_acls_with([$default_cfg->{acl_simple_user}]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
|
. $default_raw->{users}->{'test_pam'}
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n".
|
. "\n\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_missing_role'}."\n",
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
|
||||||
expected_raw => "".
|
. $default_raw->{acl}->{'acl_missing_role'} . "\n",
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
expected_raw => ""
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n\n\n\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n",
|
. $default_raw->{users}->{'test_pam'}
|
||||||
|
. "\n\n\n\n\n"
|
||||||
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "acl_complex_mixed",
|
name => "acl_complex_mixed",
|
||||||
config => {
|
config => {
|
||||||
users => default_users_with([$default_cfg->{test_pam_with_group}, $default_cfg->{'test2_pam_with_group'}, $default_cfg->{'test3_pam'}]),
|
users => default_users_with(
|
||||||
groups => default_groups_with([$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]),
|
[
|
||||||
|
$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(),
|
roles => default_roles(),
|
||||||
acl_root => default_acls_with([
|
acl_root => default_acls_with([
|
||||||
$default_cfg->{acl_complex_mixed_root},
|
$default_cfg->{acl_complex_mixed_root},
|
||||||
$default_cfg->{acl_complex_mixed_storage},
|
$default_cfg->{acl_complex_mixed_storage},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\n".
|
. $default_raw->{users}->{'test3_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n".
|
. $default_raw->{groups}->{'test_group_second'} . "\n"
|
||||||
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
|
. $default_raw->{groups}->{'test_group_members'}
|
||||||
$default_raw->{acl}->{'acl_simple_group'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_2'}."\n".
|
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n".
|
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
|
||||||
expected_raw => "".
|
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
expected_raw => ""
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test3_pam'} . "\n"
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
|
. $default_raw->{groups}->{'test_group_second'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_mixed_1'}."\n".
|
. $default_raw->{groups}->{'test_group_members'}
|
||||||
$default_raw->{acl}->{'acl_complex_mixed_2'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_mixed_3'}."\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",
|
name => "acl_complex_mixed_prop_noprop_no_merge_sort_by_path",
|
||||||
config => {
|
config => {
|
||||||
users => default_users_with([$default_cfg->{test_pam_with_group}, $default_cfg->{'test2_pam_with_group'}, $default_cfg->{'test3_pam'}]),
|
users => default_users_with(
|
||||||
groups => default_groups_with([$default_cfg->{'test_group_members'}, $default_cfg->{'test_group_second'}]),
|
[
|
||||||
|
$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(),
|
roles => default_roles(),
|
||||||
acl_root => default_acls_with([
|
acl_root => default_acls_with([
|
||||||
$default_cfg->{acl_complex_mixed_root_noprop},
|
$default_cfg->{acl_complex_mixed_root_noprop},
|
||||||
$default_cfg->{acl_complex_mixed_storage_noprop},
|
$default_cfg->{acl_complex_mixed_storage_noprop},
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\n".
|
. $default_raw->{users}->{'test3_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n".
|
. $default_raw->{groups}->{'test_group_second'} . "\n"
|
||||||
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
|
. $default_raw->{groups}->{'test_group_members'}
|
||||||
$default_raw->{acl}->{'acl_simple_group_noprop'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n".
|
. $default_raw->{acl}->{'acl_simple_group_noprop'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_1_noprop'}."\n".
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_2_noprop'}."\n".
|
. $default_raw->{acl}->{'acl_complex_groups_1_noprop'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
|
. $default_raw->{acl}->{'acl_complex_groups_2_noprop'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
|
||||||
|
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "sort_roles_and_privs",
|
name => "sort_roles_and_privs",
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{roles}->{'test_role_single_priv'}."\n\n".
|
. $default_raw->{roles}->{'test_role_single_priv'} . "\n\n"
|
||||||
$default_raw->{roles}->{'test_role_privs_out_of_order'}."\n\n",
|
. $default_raw->{roles}->{'test_role_privs_out_of_order'} . "\n\n",
|
||||||
expected_raw => "".
|
expected_raw => ""
|
||||||
$default_raw->{users}->{'root@pam'}."\n\n\n\n".
|
. $default_raw->{users}->{'root@pam'}
|
||||||
$default_raw->{roles}->{'test_role_privs'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{roles}->{'test_role_single_priv'}."\n\n",
|
. $default_raw->{roles}->{'test_role_privs'} . "\n"
|
||||||
|
. $default_raw->{roles}->{'test_role_single_priv'} . "\n\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "sort_users_and_group_members",
|
name => "sort_users_and_group_members",
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_members_out_of_order'}."\n\n".
|
. $default_raw->{groups}->{'test_group_members_out_of_order'} . "\n\n" . "\n\n",
|
||||||
"\n\n",
|
expected_raw => ""
|
||||||
expected_raw => "".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{groups}->{'test_group_members'} . "\n\n" . "\n\n",
|
||||||
$default_raw->{groups}->{'test_group_members'}."\n\n".
|
|
||||||
"\n\n",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name => "sort_user_groups_and_acls",
|
name => "sort_user_groups_and_acls",
|
||||||
raw => "".
|
raw => ""
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\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_members_out_of_order'}
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_simple_user'}."\n".
|
. $default_raw->{groups}->{'test_group_second'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_simple_group'}."\n".
|
. $default_raw->{acl}->{'acl_simple_user'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_1'}."\n".
|
. $default_raw->{acl}->{'acl_simple_group'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_users_2'}."\n".
|
. $default_raw->{acl}->{'acl_complex_users_1'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_1'}."\n".
|
. $default_raw->{acl}->{'acl_complex_users_2'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_groups_2'}."\n",
|
. $default_raw->{acl}->{'acl_complex_groups_1'} . "\n"
|
||||||
expected_raw => "".
|
. $default_raw->{acl}->{'acl_complex_groups_2'} . "\n",
|
||||||
$default_raw->{users}->{'root@pam'}."\n".
|
expected_raw => ""
|
||||||
$default_raw->{users}->{'test2_pam'}."\n".
|
. $default_raw->{users}->{'root@pam'} . "\n"
|
||||||
$default_raw->{users}->{'test3_pam'}."\n".
|
. $default_raw->{users}->{'test2_pam'} . "\n"
|
||||||
$default_raw->{users}->{'test_pam'}."\n\n".
|
. $default_raw->{users}->{'test3_pam'} . "\n"
|
||||||
$default_raw->{groups}->{'test_group_second'}."\n".
|
. $default_raw->{users}->{'test_pam'} . "\n\n"
|
||||||
$default_raw->{groups}->{'test_group_members'}."\n\n\n\n".
|
. $default_raw->{groups}->{'test_group_second'} . "\n"
|
||||||
$default_raw->{acl}->{'acl_complex_mixed_1'}."\n".
|
. $default_raw->{groups}->{'test_group_members'}
|
||||||
$default_raw->{acl}->{'acl_complex_mixed_2'}."\n".
|
. "\n\n\n\n"
|
||||||
$default_raw->{acl}->{'acl_complex_mixed_3'}."\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',
|
name => 'default_values',
|
||||||
@ -1005,25 +1089,24 @@ my $tests = [
|
|||||||
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
|
pools => default_pools_with([$default_cfg->{test_pool_empty}]),
|
||||||
acl_root => {},
|
acl_root => {},
|
||||||
},
|
},
|
||||||
raw => "".
|
raw => ""
|
||||||
'user:root@pam'."\n".
|
. 'user:root@pam' . "\n"
|
||||||
'user:test@pam'."\n".
|
. 'user:test@pam' . "\n"
|
||||||
'token:test@pam!test'."\n\n".
|
. 'token:test@pam!test' . "\n\n"
|
||||||
'group:testgroup'."\n\n".
|
. 'group:testgroup' . "\n\n"
|
||||||
'pool:testpool'."\n\n".
|
. 'pool:testpool' . "\n\n"
|
||||||
'role:testrole'."\n\n".
|
. 'role:testrole' . "\n\n"
|
||||||
'acl::/:',
|
. 'acl::/:',
|
||||||
expected_raw => "".
|
expected_raw => ""
|
||||||
'user:root@pam:0:0::::::'."\n".
|
. 'user:root@pam:0:0::::::' . "\n"
|
||||||
'user:test@pam:0:0::::::'."\n".
|
. 'user:test@pam:0:0::::::' . "\n"
|
||||||
'token:test@pam!test:0:0::'."\n\n".
|
. 'token:test@pam!test:0:0::' . "\n\n"
|
||||||
'group:testgroup:::'."\n\n".
|
. 'group:testgroup:::' . "\n\n"
|
||||||
'pool:testpool::::'."\n\n".
|
. 'pool:testpool::::' . "\n\n"
|
||||||
'role:testrole::'."\n\n",
|
. 'role:testrole::' . "\n\n",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
my $number_of_tests_run = 0;
|
my $number_of_tests_run = 0;
|
||||||
foreach my $t (@$tests) {
|
foreach my $t (@$tests) {
|
||||||
my $expected_config = $t->{expected_config} // $t->{config};
|
my $expected_config = $t->{expected_config} // $t->{config};
|
||||||
@ -1035,7 +1118,11 @@ foreach my $t (@$tests) {
|
|||||||
$number_of_tests_run++;
|
$number_of_tests_run++;
|
||||||
}
|
}
|
||||||
if (defined($t->{expected_raw}) && !defined($t->{config})) {
|
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++;
|
$number_of_tests_run++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1047,10 +1134,14 @@ foreach my $t (@$tests) {
|
|||||||
$number_of_tests_run++;
|
$number_of_tests_run++;
|
||||||
}
|
}
|
||||||
if (defined($t->{expected_config}) && !defined($t->{raw})) {
|
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++;
|
$number_of_tests_run++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
done_testing( $number_of_tests_run);
|
done_testing($number_of_tests_run);
|
||||||
|
@ -65,7 +65,7 @@ check_permission(
|
|||||||
'' # sorted, comma-separated expected privilege string
|
'' # sorted, comma-separated expected privilege string
|
||||||
. 'VM.Allocate,VM.Audit,VM.Backup,VM.Clone,VM.Config.CDROM,VM.Config.CPU,VM.Config.Cloudinit,'
|
. '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.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!
|
# Administrator -> Permissions.Modify!
|
||||||
check_permission(
|
check_permission(
|
||||||
@ -92,4 +92,4 @@ check_roles('sue@pve', '/vms/200', 'NoAccess');
|
|||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -40,4 +40,4 @@ check_roles('User2@pve', '/vms', '');
|
|||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -35,4 +35,4 @@ check_roles('User1@pve', '/vms/200', 'Role2');
|
|||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -27,10 +27,9 @@ sub check_roles {
|
|||||||
print "ROLES:$path:$user:$res\n";
|
print "ROLES:$path:$user:$res\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
check_roles('User1@pve', '/vms/300', 'Role1');
|
check_roles('User1@pve', '/vms/300', 'Role1');
|
||||||
check_roles('User2@pve', '/vms/300', 'NoAccess');
|
check_roles('User2@pve', '/vms/300', 'NoAccess');
|
||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -27,7 +27,6 @@ sub check_roles {
|
|||||||
print "ROLES:$path:$user:$res\n";
|
print "ROLES:$path:$user:$res\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
check_roles('User1@pve', '/vms', 'Role1');
|
check_roles('User1@pve', '/vms', 'Role1');
|
||||||
check_roles('User1@pve', '/vms/100', 'Role1');
|
check_roles('User1@pve', '/vms/100', 'Role1');
|
||||||
check_roles('User1@pve', '/vms/100/a', 'Role1');
|
check_roles('User1@pve', '/vms/100/a', 'Role1');
|
||||||
@ -43,4 +42,4 @@ check_roles('User2@pve', '/kvm/vms/100/a/b', '');
|
|||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -109,4 +109,4 @@ check_permissions('User4@pve', '/storage/store2', '');
|
|||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -54,4 +54,4 @@ check_permissions('User1@pve', '/vms/100', '');
|
|||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
@ -70,5 +70,5 @@ check_permission('max@pve!token2', '/vms/300', 'VM.Allocate,VM.Audit,VM.Console,
|
|||||||
|
|
||||||
print "all tests passed\n";
|
print "all tests passed\n";
|
||||||
|
|
||||||
exit (0);
|
exit(0);
|
||||||
|
|
||||||
|
@ -14,13 +14,13 @@ my $domainscfg = {
|
|||||||
ids => {
|
ids => {
|
||||||
"pam" => { type => 'pam' },
|
"pam" => { type => 'pam' },
|
||||||
"pve" => { type => 'pve' },
|
"pve" => { type => 'pve' },
|
||||||
"syncedrealm" => { type => 'ldap' }
|
"syncedrealm" => { type => 'ldap' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
my $initialusercfg = {
|
my $initialusercfg = {
|
||||||
users => {
|
users => {
|
||||||
'root@pam' => { username => 'root', },
|
'root@pam' => { username => 'root' },
|
||||||
'user1@syncedrealm' => {
|
'user1@syncedrealm' => {
|
||||||
username => 'user1',
|
username => 'user1',
|
||||||
enable => 1,
|
enable => 1,
|
||||||
@ -36,8 +36,8 @@ my $initialusercfg = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
groups => {
|
groups => {
|
||||||
'group1-syncedrealm' => { users => {}, },
|
'group1-syncedrealm' => { users => {} },
|
||||||
'group2-syncedrealm' => { users => {}, },
|
'group2-syncedrealm' => { users => {} },
|
||||||
},
|
},
|
||||||
acl_root => {
|
acl_root => {
|
||||||
users => {
|
users => {
|
||||||
@ -50,15 +50,15 @@ my $initialusercfg = {
|
|||||||
my $sync_response = {
|
my $sync_response = {
|
||||||
user => [
|
user => [
|
||||||
{
|
{
|
||||||
attributes => { 'uid' => ['user1'], },
|
attributes => { 'uid' => ['user1'] },
|
||||||
dn => 'uid=user1,dc=syncedrealm',
|
dn => 'uid=user1,dc=syncedrealm',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes => { 'uid' => ['user2'], },
|
attributes => { 'uid' => ['user2'] },
|
||||||
dn => 'uid=user2,dc=syncedrealm',
|
dn => 'uid=user2,dc=syncedrealm',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes => { 'uid' => ['user4'], },
|
attributes => { 'uid' => ['user4'] },
|
||||||
dn => 'uid=user4,dc=syncedrealm',
|
dn => 'uid=user4,dc=syncedrealm',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -74,7 +74,7 @@ my $sync_response = {
|
|||||||
members => [
|
members => [
|
||||||
'uid=nonexisting,dc=syncedrealm',
|
'uid=nonexisting,dc=syncedrealm',
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ my $returned_user_cfg = {};
|
|||||||
# mocking all cluster and ldap operations
|
# mocking all cluster and ldap operations
|
||||||
my $pve_cluster_module = Test::MockModule->new('PVE::Cluster');
|
my $pve_cluster_module = Test::MockModule->new('PVE::Cluster');
|
||||||
$pve_cluster_module->mock(
|
$pve_cluster_module->mock(
|
||||||
cfs_update => sub {},
|
cfs_update => sub { },
|
||||||
cfs_read_file => sub {
|
cfs_read_file => sub {
|
||||||
my ($filename) = @_;
|
my ($filename) = @_;
|
||||||
if ($filename eq 'domains.cfg') { return dclone($domainscfg); }
|
if ($filename eq 'domains.cfg') { return dclone($domainscfg); }
|
||||||
@ -129,7 +129,7 @@ $pve_rpcenvironment->mock(
|
|||||||
my $pve_ldap_module = Test::MockModule->new('PVE::LDAP');
|
my $pve_ldap_module = Test::MockModule->new('PVE::LDAP');
|
||||||
$pve_ldap_module->mock(
|
$pve_ldap_module->mock(
|
||||||
ldap_connect => sub { return {}; },
|
ldap_connect => sub { return {}; },
|
||||||
ldap_bind => sub {},
|
ldap_bind => sub { },
|
||||||
query_users => sub {
|
query_users => sub {
|
||||||
return $sync_response->{user};
|
return $sync_response->{user};
|
||||||
},
|
},
|
||||||
@ -152,7 +152,7 @@ my $tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
users => {
|
users => {
|
||||||
'root@pam' => { username => 'root', },
|
'root@pam' => { username => 'root' },
|
||||||
'user1@syncedrealm' => {
|
'user1@syncedrealm' => {
|
||||||
username => 'user1',
|
username => 'user1',
|
||||||
enable => 1,
|
enable => 1,
|
||||||
@ -177,8 +177,8 @@ my $tests = [
|
|||||||
'user1@syncedrealm' => 1,
|
'user1@syncedrealm' => 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'group2-syncedrealm' => { users => {}, },
|
'group2-syncedrealm' => { users => {} },
|
||||||
'group3-syncedrealm' => { users => {}, },
|
'group3-syncedrealm' => { users => {} },
|
||||||
},
|
},
|
||||||
acl_root => {
|
acl_root => {
|
||||||
users => {
|
users => {
|
||||||
@ -197,7 +197,7 @@ my $tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
users => {
|
users => {
|
||||||
'root@pam' => { username => 'root', },
|
'root@pam' => { username => 'root' },
|
||||||
'user1@syncedrealm' => {
|
'user1@syncedrealm' => {
|
||||||
username => 'user1',
|
username => 'user1',
|
||||||
enable => 1,
|
enable => 1,
|
||||||
@ -217,7 +217,7 @@ my $tests = [
|
|||||||
'user1@syncedrealm' => 1,
|
'user1@syncedrealm' => 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'group3-syncedrealm' => { users => {}, }
|
'group3-syncedrealm' => { users => {} },
|
||||||
},
|
},
|
||||||
acl_root => {
|
acl_root => {
|
||||||
users => {
|
users => {
|
||||||
@ -236,7 +236,7 @@ my $tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
users => {
|
users => {
|
||||||
'root@pam' => { username => 'root', },
|
'root@pam' => { username => 'root' },
|
||||||
'user1@syncedrealm' => {
|
'user1@syncedrealm' => {
|
||||||
username => 'user1',
|
username => 'user1',
|
||||||
enable => 1,
|
enable => 1,
|
||||||
@ -261,8 +261,8 @@ my $tests = [
|
|||||||
'user1@syncedrealm' => 1,
|
'user1@syncedrealm' => 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'group2-syncedrealm' => { users => {}, },
|
'group2-syncedrealm' => { users => {} },
|
||||||
'group3-syncedrealm' => { users => {}, },
|
'group3-syncedrealm' => { users => {} },
|
||||||
},
|
},
|
||||||
acl_root => {
|
acl_root => {
|
||||||
users => {},
|
users => {},
|
||||||
@ -279,7 +279,7 @@ my $tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
users => {
|
users => {
|
||||||
'root@pam' => { username => 'root', },
|
'root@pam' => { username => 'root' },
|
||||||
'user1@syncedrealm' => {
|
'user1@syncedrealm' => {
|
||||||
username => 'user1',
|
username => 'user1',
|
||||||
enable => 1,
|
enable => 1,
|
||||||
@ -299,7 +299,7 @@ my $tests = [
|
|||||||
'user1@syncedrealm' => 1,
|
'user1@syncedrealm' => 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'group3-syncedrealm' => { users => {}, },
|
'group3-syncedrealm' => { users => {} },
|
||||||
},
|
},
|
||||||
acl_root => {
|
acl_root => {
|
||||||
users => {},
|
users => {},
|
||||||
@ -316,7 +316,7 @@ my $tests = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
users => {
|
users => {
|
||||||
'root@pam' => { username => 'root', },
|
'root@pam' => { username => 'root' },
|
||||||
'user1@syncedrealm' => {
|
'user1@syncedrealm' => {
|
||||||
username => 'user1',
|
username => 'user1',
|
||||||
enable => 1,
|
enable => 1,
|
||||||
@ -337,7 +337,7 @@ my $tests = [
|
|||||||
'user1@syncedrealm' => 1,
|
'user1@syncedrealm' => 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'group3-syncedrealm' => { users => {}, },
|
'group3-syncedrealm' => { users => {} },
|
||||||
},
|
},
|
||||||
acl_root => {
|
acl_root => {
|
||||||
users => {},
|
users => {},
|
||||||
|
Loading…
Reference in New Issue
Block a user