]>
Commit | Line | Data |
---|---|---|
2c3a6c0a DM |
1 | package PVE::API2::ACL; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use PVE::Cluster qw (cfs_read_file cfs_write_file); | |
6 | use PVE::Tools qw(split_list); | |
7 | use PVE::AccessControl; | |
8 | use PVE::Exception qw(raise_param_exc); | |
3a5ae7a0 | 9 | use PVE::JSONSchema qw(get_standard_option register_standard_option); |
2c3a6c0a DM |
10 | |
11 | use PVE::SafeSyslog; | |
12 | ||
2c3a6c0a DM |
13 | use PVE::RESTHandler; |
14 | ||
15 | use base qw(PVE::RESTHandler); | |
16 | ||
3a5ae7a0 SI |
17 | register_standard_option('acl-propagate', { |
18 | description => "Allow to propagate (inherit) permissions.", | |
19 | type => 'boolean', | |
20 | optional => 1, | |
21 | default => 1, | |
22 | }); | |
23 | register_standard_option('acl-path', { | |
24 | description => "Access control path", | |
25 | type => 'string', | |
26 | }); | |
27 | ||
2c3a6c0a | 28 | __PACKAGE__->register_method ({ |
0a6e09fd PA |
29 | name => 'read_acl', |
30 | path => '', | |
2c3a6c0a DM |
31 | method => 'GET', |
32 | description => "Get Access Control List (ACLs).", | |
0a6e09fd | 33 | permissions => { |
fc21a5c2 | 34 | description => "The returned list is restricted to objects where you have rights to modify permissions.", |
e3a3a0d7 | 35 | user => 'all', |
96919234 | 36 | }, |
2c3a6c0a DM |
37 | parameters => { |
38 | additionalProperties => 0, | |
39 | properties => {}, | |
40 | }, | |
41 | returns => { | |
42 | type => 'array', | |
43 | items => { | |
44 | type => "object", | |
45 | additionalProperties => 0, | |
46 | properties => { | |
3a5ae7a0 SI |
47 | propagate => get_standard_option('acl-propagate'), |
48 | path => get_standard_option('acl-path'), | |
6b5736d5 | 49 | type => { type => 'string', enum => ['user', 'group', 'token'] }, |
af5d7da7 DM |
50 | ugid => { type => 'string' }, |
51 | roleid => { type => 'string' }, | |
2c3a6c0a DM |
52 | }, |
53 | }, | |
54 | }, | |
55 | code => sub { | |
56 | my ($param) = @_; | |
0a6e09fd | 57 | |
e3a3a0d7 DM |
58 | my $rpcenv = PVE::RPCEnvironment::get(); |
59 | my $authuser = $rpcenv->get_user(); | |
2c3a6c0a DM |
60 | my $res = []; |
61 | ||
e3a3a0d7 | 62 | my $usercfg = $rpcenv->{user_cfg}; |
170cf17b | 63 | if (!$usercfg || !$usercfg->{acl_root}) { |
4384e19e | 64 | return $res; |
2c3a6c0a DM |
65 | } |
66 | ||
e3a3a0d7 DM |
67 | my $audit = $rpcenv->check($authuser, '/access', ['Sys.Audit'], 1); |
68 | ||
170cf17b FG |
69 | my $root = $usercfg->{acl_root}; |
70 | PVE::AccessControl::iterate_acl_tree("/", $root, sub { | |
71 | my ($path, $node) = @_; | |
6b5736d5 | 72 | foreach my $type (qw(user group token)) { |
170cf17b | 73 | my $d = $node->{"${type}s"}; |
2c3a6c0a | 74 | next if !$d; |
e3a3a0d7 | 75 | next if !($audit || $rpcenv->check_perm_modify($authuser, $path, 1)); |
2c3a6c0a DM |
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, | |
6b5736d5 | 81 | type => $type, |
2c3a6c0a DM |
82 | ugid => $id, |
83 | roleid => $role, | |
84 | propagate => $propagate, | |
85 | }; | |
86 | } | |
87 | } | |
88 | } | |
170cf17b | 89 | }); |
2c3a6c0a DM |
90 | |
91 | return $res; | |
92 | }}); | |
93 | ||
94 | __PACKAGE__->register_method ({ | |
0a6e09fd | 95 | name => 'update_acl', |
2c3a6c0a | 96 | protected => 1, |
0a6e09fd | 97 | path => '', |
2c3a6c0a | 98 | method => 'PUT', |
0a6e09fd | 99 | permissions => { |
e3a3a0d7 | 100 | check => ['perm-modify', '{path}'], |
96919234 | 101 | }, |
2c3a6c0a DM |
102 | description => "Update Access Control List (add or remove permissions).", |
103 | parameters => { | |
0a6e09fd | 104 | additionalProperties => 0, |
2c3a6c0a | 105 | properties => { |
3a5ae7a0 SI |
106 | propagate => get_standard_option('acl-propagate'), |
107 | path => get_standard_option('acl-path'), | |
0a6e09fd | 108 | users => { |
2c3a6c0a | 109 | description => "List of users.", |
0a6e09fd | 110 | type => 'string', format => 'pve-userid-list', |
2c3a6c0a DM |
111 | optional => 1, |
112 | }, | |
0a6e09fd | 113 | groups => { |
2c3a6c0a DM |
114 | description => "List of groups.", |
115 | type => 'string', format => 'pve-groupid-list', | |
0a6e09fd | 116 | optional => 1, |
2c3a6c0a | 117 | }, |
6b5736d5 FG |
118 | tokens => { |
119 | description => "List of API tokens.", | |
120 | type => 'string', format => 'pve-tokenid-list', | |
121 | optional => 1, | |
122 | }, | |
0a6e09fd | 123 | roles => { |
2c3a6c0a DM |
124 | description => "List of roles.", |
125 | type => 'string', format => 'pve-roleid-list', | |
126 | }, | |
2c3a6c0a DM |
127 | delete => { |
128 | description => "Remove permissions (instead of adding it).", | |
0a6e09fd | 129 | type => 'boolean', |
2c3a6c0a DM |
130 | optional => 1, |
131 | }, | |
132 | }, | |
133 | }, | |
134 | returns => { type => 'null' }, | |
135 | code => sub { | |
136 | my ($param) = @_; | |
137 | ||
6b5736d5 FG |
138 | if (!($param->{users} || $param->{groups} || $param->{tokens})) { |
139 | raise_param_exc({ map { $_ => "either 'users', 'groups' or 'tokens' is required." } qw(users groups tokens) }); | |
2c3a6c0a DM |
140 | } |
141 | ||
142 | my $path = PVE::AccessControl::normalize_path($param->{path}); | |
143 | raise_param_exc({ path => "invalid ACL path '$param->{path}'" }) if !$path; | |
144 | ||
20c60513 LS |
145 | if (!$param->{delete} && !PVE::AccessControl::check_path($path)) { |
146 | raise_param_exc({ path => "invalid ACL path '$param->{path}'" }); | |
147 | } | |
148 | ||
2c3a6c0a DM |
149 | PVE::AccessControl::lock_user_config( |
150 | sub { | |
2c3a6c0a DM |
151 | my $cfg = cfs_read_file("user.cfg"); |
152 | ||
46bfd59d FG |
153 | my $rpcenv = PVE::RPCEnvironment::get(); |
154 | my $authuser = $rpcenv->get_user(); | |
155 | my $auth_user_privs = $rpcenv->permissions($authuser, $path); | |
156 | ||
e2993b66 | 157 | my $propagate = 1; |
0a6e09fd | 158 | |
e2993b66 DM |
159 | if (defined($param->{propagate})) { |
160 | $propagate = $param->{propagate} ? 1 : 0; | |
161 | } | |
2c3a6c0a | 162 | |
170cf17b FG |
163 | my $node = PVE::AccessControl::find_acl_tree_node($cfg->{acl_root}, $path); |
164 | ||
2c3a6c0a | 165 | foreach my $role (split_list($param->{roles})) { |
0a6e09fd | 166 | die "role '$role' does not exist\n" |
2c3a6c0a DM |
167 | if !$cfg->{roles}->{$role}; |
168 | ||
46bfd59d FG |
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 | ||
2c3a6c0a DM |
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}) { | |
170cf17b | 194 | delete($node->{groups}->{$group}->{$role}); |
2c3a6c0a | 195 | } else { |
170cf17b | 196 | $node->{groups}->{$group}->{$role} = $propagate; |
2c3a6c0a DM |
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}) { | |
170cf17b | 207 | delete ($node->{users}->{$username}->{$role}); |
2c3a6c0a | 208 | } else { |
170cf17b | 209 | $node->{users}->{$username}->{$role} = $propagate; |
0a6e09fd | 210 | } |
2c3a6c0a | 211 | } |
6b5736d5 FG |
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}) { | |
170cf17b | 218 | delete $node->{tokens}->{$tokenid}->{$role}; |
6b5736d5 | 219 | } else { |
170cf17b | 220 | $node->{tokens}->{$tokenid}->{$role} = $propagate; |
6b5736d5 FG |
221 | } |
222 | } | |
2c3a6c0a DM |
223 | } |
224 | ||
225 | cfs_write_file("user.cfg", $cfg); | |
226 | }, "ACL update failed"); | |
227 | ||
228 | return undef; | |
229 | }}); | |
230 | ||
231 | 1; |