my $verify_auth = sub {
- my ($rpcenv, $username, $pw_or_ticket, $path, $privs) = @_;
+ my ($rpcenv, $username, $pw_or_ticket, $otp, $path, $privs) = @_;
my $normpath = PVE::AccessControl::normalize_path($path);
} elsif (PVE::AccessControl::verify_vnc_ticket($pw_or_ticket, $username, $normpath, 1)) {
# valid vnc ticket
} else {
- $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket);
+ $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp);
}
my $privlist = [ PVE::Tools::split_list($privs) ];
};
my $create_ticket = sub {
- my ($rpcenv, $username, $pw_or_ticket) = @_;
+ my ($rpcenv, $username, $pw_or_ticket, $otp) = @_;
my $ticketuser;
if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) &&
($ticketuser eq 'root@pam' || $ticketuser eq $username)) {
# valid ticket. Note: root@pam can create tickets for other users
} else {
- $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket);
+ $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp);
}
my $ticket = PVE::AccessControl::assemble_ticket($username);
my $usercfg = $rpcenv->{user_cfg};
- my $nodelist = PVE::Cluster::get_nodelist();
- my $vmlist = PVE::Cluster::get_vmlist() || {};
- my $idlist = $vmlist->{ids} || {};
-
- my $cfg = PVE::Storage::config();
- my @sids = PVE::Storage::storage_ids ($cfg);
-
- my $res = {
- vms => {},
- storage => {},
- access => {},
- nodes => {},
- dc => {},
+ my $res = {};
+ my $priv_re_map = {
+ vms => qr/VM\.|Permissions\.Modify/,
+ access => qr/(User|Group)\.|Permissions\.Modify/,
+ storage => qr/Datastore\./,
+ nodes => qr/Sys\.|Permissions\.Modify/,
+ dc => qr/Sys\.Audit/,
};
-
- my $extract_vm_caps = sub {
- my ($path) = @_;
-
- my $perm = $rpcenv->permissions($authuser, $path);
- foreach my $priv (keys %$perm) {
- next if !($priv eq 'Permissions.Modify' || $priv =~ m/^VM\./);
- $res->{vms}->{$priv} = 1;
- }
- };
-
- foreach my $pool (keys %{$usercfg->{pools}}) {
- &$extract_vm_caps("/pool/$pool");
- }
-
- foreach my $vmid (keys %$idlist, '__phantom__') {
- &$extract_vm_caps("/vms/$vmid");
- }
-
- foreach my $storeid (@sids, '__phantom__') {
- my $perm = $rpcenv->permissions($authuser, "/storage/$storeid");
- foreach my $priv (keys %$perm) {
- next if !($priv eq 'Permissions.Modify' || $priv =~ m/^Datastore\./);
- $res->{storage}->{$priv} = 1;
- }
- }
-
- foreach my $path (('/access/groups')) {
- my $perm = $rpcenv->permissions($authuser, $path);
- foreach my $priv (keys %$perm) {
- next if $priv !~ m/^(User|Group)\./;
- $res->{access}->{$priv} = 1;
- }
- }
-
- foreach my $group (keys %{$usercfg->{users}->{$authuser}->{groups}}, '__phantom__') {
- my $perm = $rpcenv->permissions($authuser, "/access/groups/$group");
- if ($perm->{'User.Modify'}) {
- $res->{access}->{'User.Modify'} = 1;
- }
- }
-
- foreach my $node (@$nodelist) {
- my $perm = $rpcenv->permissions($authuser, "/nodes/$node");
- foreach my $priv (keys %$perm) {
- next if $priv !~ m/^Sys\./;
- $res->{nodes}->{$priv} = 1;
+ map { $res->{$_} = {} } keys %$priv_re_map;
+
+ my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage'];
+
+ my $checked_paths = {};
+ foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) {
+ next if $checked_paths->{$path};
+ $checked_paths->{$path} = 1;
+
+ my $path_perm = $rpcenv->permissions($authuser, $path);
+
+ my $toplevel = ($path =~ /^\/(\w+)/) ? $1 : 'dc';
+ if ($toplevel eq 'pool') {
+ foreach my $priv (keys %$path_perm) {
+ if ($priv =~ m/^VM\./) {
+ $res->{vms}->{$priv} = 1;
+ } elsif ($priv =~ m/^Datastore\./) {
+ $res->{storage}->{$priv} = 1;
+ } elsif ($priv eq 'Permissions.Modify') {
+ $res->{storage}->{$priv} = 1;
+ $res->{vms}->{$priv} = 1;
+ }
+ }
+ } else {
+ my $priv_regex = $priv_re_map->{$toplevel} // next;
+ foreach my $priv (keys %$path_perm) {
+ next if $priv !~ m/^($priv_regex)/;
+ $res->{$toplevel}->{$priv} = 1;
+ }
}
}
- my $perm = $rpcenv->permissions($authuser, "/");
- $res->{dc}->{'Sys.Audit'} = 1 if $perm->{'Sys.Audit'};
-
return $res;
};
path => 'ticket',
method => 'GET',
permissions => { user => 'world' },
- description => "Dummy. Useful for formaters which want to priovde a login page.",
+ description => "Dummy. Useful for formatters which want to provide a login page.",
parameters => {
additionalProperties => 0,
},
description => "User name",
type => 'string',
maxLength => 64,
+ completion => \&PVE::AccessControl::complete_username,
},
realm => get_standard_option('realm', {
description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.",
- optional => 1}),
+ optional => 1,
+ completion => \&PVE::AccessControl::complete_realm,
+ }),
password => {
description => "The secret password. This can also be a valid ticket.",
type => 'string',
},
+ otp => {
+ description => "One-time password for Two-factor authentication.",
+ type => 'string',
+ optional => 1,
+ },
path => {
description => "Verify ticket, and check if user have access 'privs' on 'path'",
type => 'string',
$rpcenv->check_user_enabled($username);
if ($param->{path} && $param->{privs}) {
- $res = &$verify_auth($rpcenv, $username, $param->{password},
+ $res = &$verify_auth($rpcenv, $username, $param->{password}, $param->{otp},
$param->{path}, $param->{privs});
} else {
- $res = &$create_ticket($rpcenv, $username, $param->{password});
+ $res = &$create_ticket($rpcenv, $username, $param->{password}, $param->{otp});
}
};
if (my $err = $@) {
parameters => {
additionalProperties => 0,
properties => {
- userid => get_standard_option('userid'),
+ userid => get_standard_option('userid', {
+ completion => \&PVE::AccessControl::complete_username,
+ }),
password => {
description => "The new password.",
type => 'string',