]> git.proxmox.com Git - pve-access-control.git/blame_incremental - src/PVE/API2/ACL.pm
bump version to 8.1.4
[pve-access-control.git] / src / PVE / API2 / ACL.pm
... / ...
CommitLineData
1package PVE::API2::ACL;
2
3use strict;
4use warnings;
5use PVE::Cluster qw (cfs_read_file cfs_write_file);
6use PVE::Tools qw(split_list);
7use PVE::AccessControl;
8use PVE::Exception qw(raise_param_exc);
9use PVE::JSONSchema qw(get_standard_option register_standard_option);
10
11use PVE::SafeSyslog;
12
13use PVE::RESTHandler;
14
15use base qw(PVE::RESTHandler);
16
17register_standard_option('acl-propagate', {
18 description => "Allow to propagate (inherit) permissions.",
19 type => 'boolean',
20 optional => 1,
21 default => 1,
22});
23register_standard_option('acl-path', {
24 description => "Access control path",
25 type => 'string',
26});
27
28__PACKAGE__->register_method ({
29 name => 'read_acl',
30 path => '',
31 method => 'GET',
32 description => "Get Access Control List (ACLs).",
33 permissions => {
34 description => "The returned list is restricted to objects where you have rights to modify permissions.",
35 user => 'all',
36 },
37 parameters => {
38 additionalProperties => 0,
39 properties => {},
40 },
41 returns => {
42 type => 'array',
43 items => {
44 type => "object",
45 additionalProperties => 0,
46 properties => {
47 propagate => get_standard_option('acl-propagate'),
48 path => get_standard_option('acl-path'),
49 type => { type => 'string', enum => ['user', 'group', 'token'] },
50 ugid => { type => 'string' },
51 roleid => { type => 'string' },
52 },
53 },
54 },
55 code => sub {
56 my ($param) = @_;
57
58 my $rpcenv = PVE::RPCEnvironment::get();
59 my $authuser = $rpcenv->get_user();
60 my $res = [];
61
62 my $usercfg = $rpcenv->{user_cfg};
63 if (!$usercfg || !$usercfg->{acl_root}) {
64 return $res;
65 }
66
67 my $audit = $rpcenv->check($authuser, '/access', ['Sys.Audit'], 1);
68
69 my $root = $usercfg->{acl_root};
70 PVE::AccessControl::iterate_acl_tree("/", $root, sub {
71 my ($path, $node) = @_;
72 foreach my $type (qw(user group token)) {
73 my $d = $node->{"${type}s"};
74 next if !$d;
75 next if !($audit || $rpcenv->check_perm_modify($authuser, $path, 1));
76 foreach my $id (keys %$d) {
77 foreach my $role (keys %{$d->{$id}}) {
78 my $propagate = $d->{$id}->{$role};
79 push @$res, {
80 path => $path,
81 type => $type,
82 ugid => $id,
83 roleid => $role,
84 propagate => $propagate,
85 };
86 }
87 }
88 }
89 });
90
91 return $res;
92 }});
93
94__PACKAGE__->register_method ({
95 name => 'update_acl',
96 protected => 1,
97 path => '',
98 method => 'PUT',
99 permissions => {
100 check => ['perm-modify', '{path}'],
101 },
102 description => "Update Access Control List (add or remove permissions).",
103 parameters => {
104 additionalProperties => 0,
105 properties => {
106 propagate => get_standard_option('acl-propagate'),
107 path => get_standard_option('acl-path'),
108 users => {
109 description => "List of users.",
110 type => 'string', format => 'pve-userid-list',
111 optional => 1,
112 },
113 groups => {
114 description => "List of groups.",
115 type => 'string', format => 'pve-groupid-list',
116 optional => 1,
117 },
118 tokens => {
119 description => "List of API tokens.",
120 type => 'string', format => 'pve-tokenid-list',
121 optional => 1,
122 },
123 roles => {
124 description => "List of roles.",
125 type => 'string', format => 'pve-roleid-list',
126 },
127 delete => {
128 description => "Remove permissions (instead of adding it).",
129 type => 'boolean',
130 optional => 1,
131 },
132 },
133 },
134 returns => { type => 'null' },
135 code => sub {
136 my ($param) = @_;
137
138 if (!($param->{users} || $param->{groups} || $param->{tokens})) {
139 raise_param_exc({ map { $_ => "either 'users', 'groups' or 'tokens' is required." } qw(users groups tokens) });
140 }
141
142 my $path = PVE::AccessControl::normalize_path($param->{path});
143 raise_param_exc({ path => "invalid ACL path '$param->{path}'" }) if !$path;
144
145 if (!$param->{delete} && !PVE::AccessControl::check_path($path)) {
146 raise_param_exc({ path => "invalid ACL path '$param->{path}'" });
147 }
148
149 PVE::AccessControl::lock_user_config(
150 sub {
151 my $cfg = cfs_read_file("user.cfg");
152
153 my $rpcenv = PVE::RPCEnvironment::get();
154 my $authuser = $rpcenv->get_user();
155 my $auth_user_privs = $rpcenv->permissions($authuser, $path);
156
157 my $propagate = 1;
158
159 if (defined($param->{propagate})) {
160 $propagate = $param->{propagate} ? 1 : 0;
161 }
162
163 my $node = PVE::AccessControl::find_acl_tree_node($cfg->{acl_root}, $path);
164
165 foreach my $role (split_list($param->{roles})) {
166 die "role '$role' does not exist\n"
167 if !$cfg->{roles}->{$role};
168
169 if (!$auth_user_privs->{'Permissions.Modify'}) {
170 # 'perm-modify' allows /vms/* with VM.Allocate and similar restricted use cases
171 # filter those to only allow handing out a subset of currently active privs
172 my $role_privs = $cfg->{roles}->{$role};
173 my $verb = $param->{delete} ? 'remove' : 'add';
174 foreach my $priv (keys $role_privs->%*) {
175 raise_param_exc({ role => "Cannot $verb role '$role' - requires 'Permissions.Modify' or superset of privileges." })
176 if !defined($auth_user_privs->{$priv});
177
178 # propagation is only potentially problematic for adding ACLs, not removing..
179 raise_param_exc({ role => "Cannot $verb role '$role' with propagation - requires 'Permissions.Modify' or propagated superset of privileges." })
180 if $propagate && $auth_user_privs->{$priv} != $propagate && !$param->{delete};
181 }
182
183 # NoAccess has no privs, needs an explicit check
184 raise_param_exc({ role => "Cannot $verb role '$role' - requires 'Permissions.Modify'"})
185 if $role eq 'NoAccess';
186 }
187
188 foreach my $group (split_list($param->{groups})) {
189
190 die "group '$group' does not exist\n"
191 if !$cfg->{groups}->{$group};
192
193 if ($param->{delete}) {
194 delete($node->{groups}->{$group}->{$role});
195 } else {
196 $node->{groups}->{$group}->{$role} = $propagate;
197 }
198 }
199
200 foreach my $userid (split_list($param->{users})) {
201 my $username = PVE::AccessControl::verify_username($userid);
202
203 die "user '$username' does not exist\n"
204 if !$cfg->{users}->{$username};
205
206 if ($param->{delete}) {
207 delete ($node->{users}->{$username}->{$role});
208 } else {
209 $node->{users}->{$username}->{$role} = $propagate;
210 }
211 }
212
213 foreach my $tokenid (split_list($param->{tokens})) {
214 my ($username, $token) = PVE::AccessControl::split_tokenid($tokenid);
215 PVE::AccessControl::check_token_exist($cfg, $username, $token);
216
217 if ($param->{delete}) {
218 delete $node->{tokens}->{$tokenid}->{$role};
219 } else {
220 $node->{tokens}->{$tokenid}->{$role} = $propagate;
221 }
222 }
223 }
224
225 cfs_write_file("user.cfg", $cfg);
226 }, "ACL update failed");
227
228 return undef;
229 }});
230
2311;