]> git.proxmox.com Git - pve-access-control.git/blobdiff - PVE/API2/AccessControl.pm
fix typo in change_passsword
[pve-access-control.git] / PVE / API2 / AccessControl.pm
index 1679ed45eb4c0d6ff81682eb967fbce3c5521cfd..afcd2fa62e5092f6abf03a754b61b993d6b849c7 100644 (file)
@@ -88,7 +88,7 @@ __PACKAGE__->register_method ({
 
 
 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);
 
@@ -99,7 +99,7 @@ my $verify_auth = sub {
     } 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) ];
@@ -111,14 +111,14 @@ my $verify_auth = sub {
 };
 
 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);
@@ -136,76 +136,61 @@ my $compute_api_permission = sub {
 
     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 $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;   
-       }
+    my $res = {};
+    my $priv_re_map = {
+       vms => qr/VM\.|Permissions\.Modify/,
+       access => qr/(User|Group)\.|Permissions\.Modify/,
+       storage => qr/Datastore\.|Permissions\.Modify/,
+       nodes => qr/Sys\.|Permissions\.Modify/,
+       dc => qr/Sys\.Audit/,
     };
-
-    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;
 };
 
+__PACKAGE__->register_method ({
+    name => 'get_ticket', 
+    path => 'ticket', 
+    method => 'GET',
+    permissions => { user => 'world' },
+    description => "Dummy. Useful for formatters which want to provide a login page.",
+    parameters => {
+       additionalProperties => 0,
+    },
+    returns => { type => "null" },
+    code => sub { return undef; }});
+  
 __PACKAGE__->register_method ({
     name => 'create_ticket', 
     path => 'ticket', 
@@ -223,14 +208,22 @@ __PACKAGE__->register_method ({
                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',
@@ -264,22 +257,22 @@ __PACKAGE__->register_method ({
        my $rpcenv = PVE::RPCEnvironment::get();
 
        my $res;
-
        eval {
            # test if user exists and is enabled
            $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 = $@) {
            my $clientip = $rpcenv->get_client_ip() || '';
            syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
-           die $err;
+           # do not return any info to prevent user enumeration attacks
+           die PVE::Exception->new("authentication failure\n", code => 401);
        }
 
        $res->{cap} = &$compute_api_permission($rpcenv, $username);
@@ -290,7 +283,7 @@ __PACKAGE__->register_method ({
     }});
 
 __PACKAGE__->register_method ({
-    name => 'change_passsword', 
+    name => 'change_password',
     path => 'password', 
     method => 'PUT',
     permissions => { 
@@ -308,7 +301,9 @@ __PACKAGE__->register_method ({
     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',