3e42ac06d8a9a85131a00fc62b6a02997689203d
[pve-access-control.git] / PVE / API2 / ACL.pm
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);
9 use PVE::JSONSchema qw(get_standard_option register_standard_option);
10
11 use PVE::SafeSyslog;
12
13 use PVE::RESTHandler;
14
15 use base qw(PVE::RESTHandler);
16
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
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'] },
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}) {
64             return $res;
65         }
66
67         my $audit = $rpcenv->check($authuser, '/access', ['Sys.Audit'], 1);
68
69         my $acl = $usercfg->{acl};
70         foreach my $path (keys %$acl) {
71             foreach my $type (qw(users groups)) {
72                 my $d = $acl->{$path}->{$type};
73                 next if !$d;
74                 next if !($audit || $rpcenv->check_perm_modify($authuser, $path, 1));
75                 foreach my $id (keys %$d) {
76                     foreach my $role (keys %{$d->{$id}}) {
77                         my $propagate = $d->{$id}->{$role};
78                         push @$res, {
79                             path => $path,
80                             type => $type eq 'groups' ? 'group' : 'user',
81                             ugid => $id,
82                             roleid => $role,
83                             propagate => $propagate,
84                         };
85                     }
86                 }
87             }
88         }
89
90         return $res;
91     }});
92
93 __PACKAGE__->register_method ({
94     name => 'update_acl',
95     protected => 1,
96     path => '',
97     method => 'PUT',
98     permissions => {
99         check => ['perm-modify', '{path}'],
100     },
101     description => "Update Access Control List (add or remove permissions).",
102     parameters => {
103         additionalProperties => 0,
104         properties => {
105             propagate => get_standard_option('acl-propagate'),
106             path => get_standard_option('acl-path'),
107             users => {
108                 description => "List of users.",
109                 type => 'string',  format => 'pve-userid-list',
110                 optional => 1,
111             },
112             groups => {
113                 description => "List of groups.",
114                 type => 'string', format => 'pve-groupid-list',
115                 optional => 1,
116             },
117             roles => {
118                 description => "List of roles.",
119                 type => 'string', format => 'pve-roleid-list',
120             },
121             delete => {
122                 description => "Remove permissions (instead of adding it).",
123                 type => 'boolean',
124                 optional => 1,
125             },
126         },
127     },
128     returns => { type => 'null' },
129     code => sub {
130         my ($param) = @_;
131
132         if (!($param->{users} || $param->{groups})) {
133             raise_param_exc({
134                 users => "either 'users' or 'groups' is required.",
135                 groups => "either 'users' or 'groups' is required." });
136         }
137
138         my $path = PVE::AccessControl::normalize_path($param->{path});
139         raise_param_exc({ path => "invalid ACL path '$param->{path}'" }) if !$path;
140
141         PVE::AccessControl::lock_user_config(
142             sub {
143
144                 my $cfg = cfs_read_file("user.cfg");
145
146                 my $propagate = 1;
147
148                 if (defined($param->{propagate})) {
149                     $propagate = $param->{propagate} ? 1 : 0;
150                 }
151
152                 foreach my $role (split_list($param->{roles})) {
153                     die "role '$role' does not exist\n"
154                         if !$cfg->{roles}->{$role};
155
156                     foreach my $group (split_list($param->{groups})) {
157
158                         die "group '$group' does not exist\n"
159                             if !$cfg->{groups}->{$group};
160
161                         if ($param->{delete}) {
162                             delete($cfg->{acl}->{$path}->{groups}->{$group}->{$role});
163                         } else {
164                             $cfg->{acl}->{$path}->{groups}->{$group}->{$role} = $propagate;
165                         }
166                     }
167
168                     foreach my $userid (split_list($param->{users})) {
169                         my $username = PVE::AccessControl::verify_username($userid);
170
171                         die "user '$username' does not exist\n"
172                             if !$cfg->{users}->{$username};
173
174                         if ($param->{delete}) {
175                             delete($cfg->{acl}->{$path}->{users}->{$username}->{$role});
176                         } else {
177                             $cfg->{acl}->{$path}->{users}->{$username}->{$role} = $propagate;
178                         }
179                     }
180                 }
181
182                 cfs_write_file("user.cfg", $cfg);
183             }, "ACL update failed");
184
185         return undef;
186     }});
187
188 1;