]> git.proxmox.com Git - pve-access-control.git/blobdiff - src/PVE/RPCEnvironment.pm
bump version to 8.1.4
[pve-access-control.git] / src / PVE / RPCEnvironment.pm
index e03974a4afb6a89abf328bd2f027d8f385018da8..e6683532c1f506aaaf688665fcdef50ba80e319a 100644 (file)
@@ -5,7 +5,7 @@ use warnings;
 
 use PVE::AccessControl;
 use PVE::Cluster;
-use PVE::Exception qw(raise raise_perm_exc);
+use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
 use PVE::INotify;
 use PVE::ProcFSTools;
 use PVE::RESTEnvironment;
@@ -140,6 +140,12 @@ sub permissions {
        return { map { $_ => 1 } keys %{$cfg->{roles}->{'Administrator'}} };
     }
 
+    if (!defined($path)) {
+       # this shouldn't happen!
+       warn "internal error: ACL check called for undefined ACL path!\n";
+       return {};
+    }
+
     if (PVE::AccessControl::pve_verify_tokenid($user, 1)) {
        my ($username, $token) = PVE::AccessControl::split_tokenid($user);
        my $cfg = $self->{user_cfg};
@@ -180,14 +186,20 @@ sub compute_api_permission {
        storage => qr/Datastore\.|Permissions\.Modify/,
        nodes => qr/Sys\.|Permissions\.Modify/,
        sdn => qr/SDN\.|Permissions\.Modify/,
-       dc => qr/Sys\.Audit|SDN\./,
+       dc => qr/Sys\.Audit|Sys\.Modify|SDN\./,
+       mapping => qr/Mapping\.|Permissions.Modify/,
     };
     map { $res->{$_} = {} } keys %$priv_re_map;
 
-    my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn'];
+    my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn', '/mapping'];
+    my $defined_paths = [];
+    PVE::AccessControl::iterate_acl_tree("/", $usercfg->{acl_root}, sub {
+       my ($path, $node) = @_;
+       push @$defined_paths, $path;
+    });
 
     my $checked_paths = {};
-    foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) {
+    foreach my $path (@$required_paths, @$defined_paths) {
        next if $checked_paths->{$path};
        $checked_paths->{$path} = 1;
 
@@ -230,7 +242,7 @@ sub get_effective_permissions {
        '/access' => 1,
        '/access/groups' => 1,
        '/nodes' => 1,
-       '/pools' => 1,
+       '/pool' => 1,
        '/sdn' => 1,
        '/storage' => 1,
        '/vms' => 1,
@@ -239,9 +251,10 @@ sub get_effective_permissions {
     my $cfg = $self->{user_cfg};
 
     # paths explicitly listed in ACLs
-    foreach my $acl_path (keys %{$cfg->{acl}}) {
-       $paths->{$acl_path} = 1;
-    }
+    PVE::AccessControl::iterate_acl_tree("/", $cfg->{acl_root}, sub {
+       my ($path, $node) = @_;
+       $paths->{$path} = 1;
+    });
 
     # paths referenced by pool definitions
     foreach my $pool (keys %{$cfg->{pools}}) {
@@ -312,6 +325,31 @@ sub check_full {
     }
 }
 
+# check for any fashion of access to vnet/bridge
+sub check_sdn_bridge {
+    my ($self, $username, $zone, $bridge, $privs, $noerr) = @_;
+
+    my $path = "/sdn/zones/$zone/$bridge";
+    # check access to bridge itself
+    return 1 if $self->check_any($username, $path, $privs, 1);
+
+    my $cfg = $self->{user_cfg};
+    my $bridge_acl = PVE::AccessControl::find_acl_tree_node($cfg->{acl_root}, $path);
+    if ($bridge_acl) {
+       # check access to VLANs
+       my $vlans = $bridge_acl->{children};
+       for my $vlan (keys %$vlans) {
+           my $vlanpath = "$path/$vlan";
+           return 1 if $self->check_any($username, $vlanpath, $privs, 1);
+       }
+    }
+
+    # repeat check, but fatal
+    $self->check_any($username, $path, $privs, 0) if !$noerr;
+
+    return;
+}
+
 sub check_user_enabled {
     my ($self, $user, $noerr) = @_;
 
@@ -439,8 +477,10 @@ sub exec_api2_perm_check {
            raise_perm_exc();
        }
        my $path = PVE::Tools::template_replace($tmplpath, $param);
-       $path = PVE::AccessControl::normalize_path($path);
-       return $self->check_full($username, $path, $privs, $any, $noerr);
+       my $normpath = PVE::AccessControl::normalize_path($path);
+       warn "Failed to normalize '$path'\n" if !defined($normpath) && defined($path);
+
+       return $self->check_full($username, $normpath, $privs, $any, $noerr);
     } elsif ($test eq 'userid-group') {
        my $userid = $param->{userid};
        my ($t, $privs, %options) = @$check;
@@ -490,6 +530,7 @@ sub exec_api2_perm_check {
        my ($t, $tmplpath) = @$check;
        my $path = PVE::Tools::template_replace($tmplpath, $param);
        $path = PVE::AccessControl::normalize_path($path);
+       return 0 if !defined($path); # should already die in API2::ACL
        return $self->check_perm_modify($username, $path, $noerr);
     } else {
        die "unknown permission test";
@@ -596,4 +637,37 @@ sub is_worker {
     return PVE::RESTEnvironment->is_worker();
 }
 
+# Permission helper for TFA and password API endpoints modifying users.
+# Only root may modify root, regular users need to specify their password.
+#
+# Returns the same as `verify_username` in list context (userid, ruid, realm),
+# or just the userid in scalar context.
+sub reauth_user_for_user_modification : prototype($$$$;$) {
+    my ($rpcenv, $authuser, $userid, $password, $param_name) = @_;
+
+    $param_name //= 'password';
+
+    ($userid, my $ruid, my $realm) = PVE::AccessControl::verify_username($userid);
+    $rpcenv->check_user_exist($userid);
+
+    raise_perm_exc() if $userid eq 'root@pam' && $authuser ne 'root@pam';
+
+    # Regular users need to confirm their password to change TFA settings.
+    if ($authuser ne 'root@pam') {
+       raise_param_exc({ $param_name => 'password is required to modify user' })
+           if !defined($password);
+
+       ($authuser, my $auth_username, my $auth_realm) =
+           PVE::AccessControl::verify_username($authuser);
+
+       my $domain_cfg = PVE::Cluster::cfs_read_file('domains.cfg');
+       my $cfg = $domain_cfg->{ids}->{$auth_realm};
+       die "auth domain '$auth_realm' does not exist\n" if !$cfg;
+       my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
+       $plugin->authenticate_user($cfg, $auth_realm, $auth_username, $password);
+    }
+
+    return wantarray ? ($userid, $ruid, $realm) : $userid;
+}
+
 1;