]> git.proxmox.com Git - pve-access-control.git/blobdiff - PVE/API2/AccessControl.pm
d/control: bump debhelper compat to >= 12
[pve-access-control.git] / PVE / API2 / AccessControl.pm
index c2324e8f7fa908da09633400bef350ccaf05387b..a77694b7561d64f080db9c4c26e22d6ffd7b0f1d 100644 (file)
@@ -6,10 +6,11 @@ use warnings;
 use JSON;
 use MIME::Base64;
 
-use PVE::Exception qw(raise raise_perm_exc);
+use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
 use PVE::SafeSyslog;
 use PVE::RPCEnvironment;
 use PVE::Cluster qw(cfs_read_file);
+use PVE::DataCenterConfig;
 use PVE::RESTHandler;
 use PVE::AccessControl;
 use PVE::JSONSchema qw(get_standard_option);
@@ -31,36 +32,36 @@ eval {
 use base qw(PVE::RESTHandler);
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::User",  
+    subclass => "PVE::API2::User",
     path => 'users',
 });
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::Group",  
+    subclass => "PVE::API2::Group",
     path => 'groups',
 });
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::Role",  
+    subclass => "PVE::API2::Role",
     path => 'roles',
 });
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::ACL",  
+    subclass => "PVE::API2::ACL",
     path => 'acl',
 });
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::Domains",  
+    subclass => "PVE::API2::Domains",
     path => 'domains',
 });
 
 __PACKAGE__->register_method ({
-    name => 'index', 
-    path => '', 
+    name => 'index',
+    path => '',
     method => 'GET',
     description => "Directory index.",
-    permissions => { 
+    permissions => {
        user => 'all',
     },
     parameters => {
@@ -79,7 +80,7 @@ __PACKAGE__->register_method ({
     },
     code => sub {
        my ($param) = @_;
-    
+
        my $res = [];
 
        my $ma = __PACKAGE__->method_attributes();
@@ -175,11 +176,12 @@ my $compute_api_permission = sub {
        access => qr/(User|Group)\.|Permissions\.Modify/,
        storage => qr/Datastore\.|Permissions\.Modify/,
        nodes => qr/Sys\.|Permissions\.Modify/,
-       dc => qr/Sys\.Audit/,
+       sdn => qr/SDN\.|Permissions\.Modify/,
+       dc => qr/Sys\.Audit|SDN\./,
     };
     map { $res->{$_} = {} } keys %$priv_re_map;
 
-    my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage'];
+    my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn'];
 
     my $checked_paths = {};
     foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) {
@@ -213,8 +215,8 @@ my $compute_api_permission = sub {
 };
 
 __PACKAGE__->register_method ({
-    name => 'get_ticket', 
-    path => 'ticket', 
+    name => 'get_ticket',
+    path => 'ticket',
     method => 'GET',
     permissions => { user => 'world' },
     description => "Dummy. Useful for formatters which want to provide a login page.",
@@ -223,16 +225,17 @@ __PACKAGE__->register_method ({
     },
     returns => { type => "null" },
     code => sub { return undef; }});
-  
+
 __PACKAGE__->register_method ({
-    name => 'create_ticket', 
-    path => 'ticket', 
+    name => 'create_ticket',
+    path => 'ticket',
     method => 'POST',
-    permissions => { 
+    permissions => {
        description => "You need to pass valid credientials.",
-       user => 'world' 
+       user => 'world'
     },
     protected => 1, # else we can't access shadow files
+    allowtoken => 0, # we don't want tokens to create tickets
     description => "Create or verify authentication ticket.",
     parameters => {
        additionalProperties => 0,
@@ -248,7 +251,7 @@ __PACKAGE__->register_method ({
                optional => 1,
                completion => \&PVE::AccessControl::complete_realm,
            }),
-           password => { 
+           password => {
                description => "The secret password. This can also be a valid ticket.",
                type => 'string',
            },
@@ -264,7 +267,7 @@ __PACKAGE__->register_method ({
                optional => 1,
                maxLength => 64,
            },
-           privs => { 
+           privs => {
                description => "Verify ticket, and check if user have access 'privs' on 'path'",
                type => 'string' , format => 'pve-priv-list',
                requires => 'path',
@@ -285,10 +288,11 @@ __PACKAGE__->register_method ({
     },
     code => sub {
        my ($param) = @_;
-    
+
        my $username = $param->{username};
        $username .= "\@$param->{realm}" if $param->{realm};
 
+       $username = PVE::AccessControl::lookup_username($username);
        my $rpcenv = PVE::RPCEnvironment::get();
 
        my $res;
@@ -325,11 +329,11 @@ __PACKAGE__->register_method ({
 
 __PACKAGE__->register_method ({
     name => 'change_password',
-    path => 'password', 
+    path => 'password',
     method => 'PUT',
-    permissions => { 
+    permissions => {
        description => "Each user is allowed to change his own password. A user can change the password of another user if he has 'Realm.AllocateUser' (on the realm of user <userid>) and 'User.Modify' permission on /access/groups/<group> on a group where user <userid> is member of.",
-       check => [ 'or', 
+       check => [ 'or',
                   ['userid-param', 'self'],
                   [ 'and',
                     [ 'userid-param', 'Realm.AllocateUser'],
@@ -338,15 +342,16 @@ __PACKAGE__->register_method ({
            ],
     },
     protected => 1, # else we can't access shadow files
+    allowtoken => 0, # we don't want tokens to change the regular user password
     description => "Change user password.",
     parameters => {
        additionalProperties => 0,
        properties => {
            userid => get_standard_option('userid-completed'),
-           password => { 
+           password => {
                description => "The new password.",
                type => 'string',
-               minLength => 5, 
+               minLength => 5,
                maxLength => 64,
            },
        }
@@ -389,7 +394,6 @@ sub get_u2f_config() {
     my $dc = cfs_read_file('datacenter.cfg');
     my $u2f = $dc->{u2f};
     die "u2f not configured in datacenter.cfg\n" if !$u2f;
-    $u2f = PVE::JSONSchema::parse_property_string($PVE::Cluster::u2f_format, $u2f);
     return $u2f;
 }
 
@@ -470,6 +474,7 @@ __PACKAGE__->register_method ({
            ],
     },
     protected => 1, # else we can't access shadow files
+    allowtoken => 0, # we don't want tokens to change the regular user's TFA settings
     description => "Change user u2f authentication.",
     parameters => {
        additionalProperties => 0,
@@ -532,11 +537,11 @@ __PACKAGE__->register_method ({
 
        # Regular users need to confirm their password to change u2f settings.
        if ($authuser ne 'root@pam') {
-           raise_param_exc('password' => 'password is required to modify u2f data')
+           raise_param_exc({ 'password' => 'password is required to modify u2f data' })
                if !defined($password);
            my $domain_cfg = cfs_read_file('domains.cfg');
            my $cfg = $domain_cfg->{ids}->{$realm};
-           die "auth domain '$realm' does not exists\n" if !$cfg;
+           die "auth domain '$realm' does not exist\n" if !$cfg;
            my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
            $plugin->authenticate_user($cfg, $realm, $ruid, $password);
        }
@@ -564,7 +569,7 @@ __PACKAGE__->register_method ({
                return $challenge;
            }
        } elsif ($action eq 'confirm') {
-           raise_param_exc('response' => "confirm action requires the 'response' parameter to be set")
+           raise_param_exc({ 'response' => "confirm action requires the 'response' parameter to be set" })
                if !defined($response);
 
            my ($type, $u2fdata) = PVE::AccessControl::user_get_tfa($userid, $realm);
@@ -594,6 +599,7 @@ __PACKAGE__->register_method({
     method => 'POST',
     permissions => { user => 'all' },
     protected => 1, # else we can't access shadow files
+    allowtoken => 0, # we don't want tokens to access TFA information
     description => 'Finish a u2f challenge.',
     parameters => {
        additionalProperties => 0,
@@ -657,4 +663,60 @@ __PACKAGE__->register_method({
        }
     }});
 
+__PACKAGE__->register_method({
+    name => 'permissions',
+    path => 'permissions',
+    method => 'GET',
+    description => 'Retrieve effective permissions of given user/token.',
+    permissions => {
+       description => "Each user/token is allowed to dump their own permissions. A user can dump the permissions of another user if they have 'Sys.Audit' permission on /access.",
+       user => 'all',
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           userid => {
+               type => 'string',
+               description => "User ID or full API token ID",
+               pattern => $PVE::AccessControl::userid_or_token_regex,
+               optional => 1,
+           },
+           path => get_standard_option('acl-path', {
+               description => "Only dump this specific path, not the whole tree.",
+               optional => 1,
+           }),
+       },
+    },
+    returns => {
+       type => 'object',
+       description => 'Map of "path" => (Map of "privilege" => "propagate boolean").',
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+
+       my $userid = $param->{userid};
+       if (defined($userid)) {
+           $rpcenv->check($rpcenv->get_user(), '/access', ['Sys.Audit']);
+       } else {
+           $userid = $rpcenv->get_user();
+       }
+
+       my $res;
+
+       if (my $path = $param->{path}) {
+           my $perms = $rpcenv->permissions($userid, $path);
+           if ($perms) {
+               $res = { $path => $perms };
+           } else {
+               $res = {};
+           }
+       } else {
+           $res = $rpcenv->get_effective_permissions($userid);
+       }
+
+       return $res;
+    }});
+
 1;