]> git.proxmox.com Git - pve-access-control.git/blobdiff - src/PVE/RPCEnvironment.pm
permissions: fix token/user priv intersection
[pve-access-control.git] / src / PVE / RPCEnvironment.pm
index e66107be7c1567cdc045c303ae0fd89980144a87..a0c755587638191e6e45a282fe2e76dc3636151c 100644 (file)
@@ -3,15 +3,14 @@ package PVE::RPCEnvironment;
 use strict;
 use warnings;
 
-use PVE::RESTEnvironment;
-
+use PVE::AccessControl;
+use PVE::Cluster;
 use PVE::Exception qw(raise raise_perm_exc);
-use PVE::SafeSyslog;
-use PVE::Tools;
 use PVE::INotify;
-use PVE::Cluster;
 use PVE::ProcFSTools;
-use PVE::AccessControl;
+use PVE::RESTEnvironment;
+use PVE::SafeSyslog;
+use PVE::Tools;
 
 use base qw(PVE::RESTEnvironment);
 
@@ -75,7 +74,7 @@ my $compile_acl_path = sub {
     foreach my $role (keys %$roles) {
        if (my $privset = $cfg->{roles}->{$role}) {
            foreach my $p (keys %$privset) {
-               $privs->{$p} = $roles->{$role};
+               $privs->{$p} ||= $roles->{$role};
            }
        }
     }
@@ -83,7 +82,13 @@ my $compile_acl_path = sub {
     if ($username && $username ne 'root@pam') {
        # intersect user and token permissions
        my $user_privs = $cache->{$username}->{privs}->{$path};
-       $privs = { map { $_ => $user_privs->{$_} && $privs->{$_} } keys %$privs };
+       my $filtered_privs = [ grep { defined($user_privs->{$_}) } keys %$privs ];
+       $privs = { map { $_ => $user_privs->{$_} && $privs->{$_} } @$filtered_privs };
+    }
+
+    foreach my $priv (keys %$privs) {
+       # safeguard, this should never happen anyway
+       delete $privs->{$priv} if !defined($privs->{$priv});
     }
 
     $data->{privs}->{$path} = $privs;
@@ -127,6 +132,59 @@ sub permissions {
     return &$compile_acl_path($self, $user, $path);
 }
 
+sub compute_api_permission {
+    my ($self, $authuser) = @_;
+
+    my $usercfg = $self->{user_cfg};
+
+    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/,
+       sdn => qr/SDN\.|Permissions\.Modify/,
+       dc => qr/Sys\.Audit|SDN\./,
+    };
+    map { $res->{$_} = {} } keys %$priv_re_map;
+
+    my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn'];
+
+    my $checked_paths = {};
+    foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) {
+       next if $checked_paths->{$path};
+       $checked_paths->{$path} = 1;
+
+       my $path_perm = $self->permissions($authuser, $path);
+
+       my $toplevel = ($path =~ /^\/(\w+)/) ? $1 : 'dc';
+       if ($toplevel eq 'pool') {
+           foreach my $priv (keys %$path_perm) {
+               next if !defined($path_perm->{$priv});
+
+               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 !defined($path_perm->{$priv});
+
+               next if $priv !~ m/^($priv_regex)/;
+               $res->{$toplevel}->{$priv} = 1;
+           }
+       }
+    }
+
+    return $res;
+}
+
 sub get_effective_permissions {
     my ($self, $user) = @_;
 
@@ -137,6 +195,7 @@ sub get_effective_permissions {
        '/access/groups' => 1,
        '/nodes' => 1,
        '/pools' => 1,
+       '/sdn' => 1,
        '/storage' => 1,
        '/vms' => 1,
     };
@@ -162,6 +221,9 @@ sub get_effective_permissions {
     my $perms = {};
     foreach my $path (keys %$paths) {
        my $path_perms = $self->permissions($user, $path);
+       foreach my $priv (keys %$path_perms) {
+           delete $path_perms->{$priv} if !defined($path_perms->{$priv});
+       }
        # filter paths where user has NO permissions
        $perms->{$path} = $path_perms if %$path_perms;
     }
@@ -346,17 +408,25 @@ sub exec_api2_perm_check {
     } elsif ($test eq 'userid-group') {
        my $userid = $param->{userid};
        my ($t, $privs, %options) = @$check;
-       return 0 if !$options{groups_param} && !$self->check_user_exist($userid, $noerr);
+
+       my $check_existing_user = !$options{groups_param} || $options{groups_param} ne 'create';
+       return 0 if $check_existing_user && !$self->check_user_exist($userid, $noerr);
+
+       # check permission for ALL groups (and thus ALL users)
        if (!$self->check_any($username, "/access/groups", $privs, 1)) {
+           # list of groups $username has any of $privs on
            my $groups = $self->filter_groups($username, $privs, 1);
            if ($options{groups_param}) {
+               # does $username have any of $privs on all new/updated/.. groups?
                my @group_param = PVE::Tools::split_list($param->{groups});
                raise_perm_exc("/access/groups, " . join("|", @$privs)) if !scalar(@group_param);
                foreach my $pg (@group_param) {
                    raise_perm_exc("/access/groups/$pg, " . join("|", @$privs))
                        if !$groups->{$pg};
                }
-           } else {
+           }
+           if ($check_existing_user) {
+               # does $username have any of $privs on any existing group of $userid
                my $allowed_users = $self->group_member_join([keys %$groups]);
                if (!$allowed_users->{$userid}) {
                    return 0 if $noerr;
@@ -380,12 +450,12 @@ sub exec_api2_perm_check {
        } else {
            die "unknown userid-param test";
        }
-     } elsif ($test eq 'perm-modify') {
+    } elsif ($test eq 'perm-modify') {
        my ($t, $tmplpath) = @$check;
        my $path = PVE::Tools::template_replace($tmplpath, $param);
        $path = PVE::AccessControl::normalize_path($path);
        return $self->check_perm_modify($username, $path, $noerr);
-   } else {
+    } else {
        die "unknown permission test";
     }
 };