89c83430165e1ce84b2dbe051b48cae0c8c06d63
[pve-access-control.git] / PVE / API2 / User.pm
1 package PVE::API2::User;
2
3 use strict;
4 use warnings;
5 use PVE::Exception qw(raise raise_perm_exc);
6 use PVE::Cluster qw (cfs_read_file cfs_write_file);
7 use PVE::Tools qw(split_list);
8 use PVE::AccessControl;
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('user-enable', {
18     title => "Enable",
19     description => "Enable the account (default). You can set this to '0' to disable the account",
20     type => 'boolean',
21     optional => 1,
22     default => 1,
23 });
24 register_standard_option('user-expire', {
25     description => "Account expiration date (seconds since epoch). '0' means no expiration date.",
26     type => 'integer',
27     minimum => 0,
28     optional => 1,
29 });
30 register_standard_option('user-firstname', { type => 'string', optional => 1 });
31 register_standard_option('user-lastname', { type => 'string', optional => 1 });
32 register_standard_option('user-email', { type => 'string', optional => 1, format => 'email-opt' });
33 register_standard_option('user-comment', { type => 'string', optional => 1 });
34 register_standard_option('user-keys', {
35     description => "Keys for two factor auth (yubico).",
36     type => 'string',
37     optional => 1,
38 });
39 register_standard_option('group-list', {
40     type => 'string', format => 'pve-groupid-list',
41     optional => 1,
42     completion => \&PVE::AccessControl::complete_group,
43 });
44
45 my $extract_user_data = sub {
46     my ($data, $full) = @_;
47
48     my $res = {};
49
50     foreach my $prop (qw(enable expire firstname lastname email comment keys)) {
51         $res->{$prop} = $data->{$prop} if defined($data->{$prop});
52     }
53
54     return $res if !$full;
55
56     $res->{groups} = $data->{groups} ? [ keys %{$data->{groups}} ] : [];
57
58     return $res;
59 };
60
61 __PACKAGE__->register_method ({
62     name => 'index',
63     path => '',
64     method => 'GET',
65     description => "User index.",
66     permissions => {
67         description => "The returned list is restricted to users where you have 'User.Modify' or 'Sys.Audit' permissions on '/access/groups' or on a group the user belongs too. But it always includes the current (authenticated) user.",
68         user => 'all',
69     },
70     parameters => {
71         additionalProperties => 0,
72         properties => {
73             enabled => {
74                 type => 'boolean',
75                 description => "Optional filter for enable property.",
76                 optional => 1,
77             }
78         },
79     },
80     returns => {
81         type => 'array',
82         items => {
83             type => "object",
84             properties => {
85                 userid => get_standard_option('userid-completed'),
86                 enable => get_standard_option('user-enable'),
87                 expire => get_standard_option('user-expire'),
88                 firstname => get_standard_option('user-firstname'),
89                 lastname => get_standard_option('user-lastname'),
90                 email => get_standard_option('user-email'),
91                 comment => get_standard_option('user-comment'),
92                 keys => get_standard_option('user-keys'),
93             },
94         },
95         links => [ { rel => 'child', href => "{userid}" } ],
96     },
97     code => sub {
98         my ($param) = @_;
99
100         my $rpcenv = PVE::RPCEnvironment::get();
101         my $usercfg = $rpcenv->{user_cfg};
102         my $authuser = $rpcenv->get_user();
103
104         my $res = [];
105
106         my $privs = [ 'User.Modify', 'Sys.Audit' ];
107         my $canUserMod = $rpcenv->check_any($authuser, "/access/groups", $privs, 1);
108         my $groups = $rpcenv->filter_groups($authuser, $privs, 1);
109         my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
110
111         foreach my $user (keys %{$usercfg->{users}}) {
112
113             if (!($canUserMod || $user eq $authuser)) {
114                 next if !$allowed_users->{$user};
115             }
116
117             my $entry = &$extract_user_data($usercfg->{users}->{$user});
118
119             if (defined($param->{enabled})) {
120                 next if $entry->{enable} && !$param->{enabled};
121                 next if !$entry->{enable} && $param->{enabled};
122             }
123
124             $entry->{userid} = $user;
125             push @$res, $entry;
126         }
127
128         return $res;
129     }});
130
131 __PACKAGE__->register_method ({
132     name => 'create_user',
133     protected => 1,
134     path => '',
135     method => 'POST',
136     permissions => {
137         description => "You need 'Realm.AllocateUser' on '/access/realm/<realm>' on the realm of user <userid>, and 'User.Modify' permissions to '/access/groups/<group>' for any group specified (or 'User.Modify' on '/access/groups' if you pass no groups.",
138         check => [ 'and',
139                    [ 'userid-param', 'Realm.AllocateUser'],
140                    [ 'userid-group', ['User.Modify'], groups_param => 1],
141             ],
142     },
143     description => "Create new user.",
144     parameters => {
145         additionalProperties => 0,
146         properties => {
147             userid => get_standard_option('userid-completed'),
148             enable => get_standard_option('user-enable'),
149             expire => get_standard_option('user-expire'),
150             firstname => get_standard_option('user-firstname'),
151             lastname => get_standard_option('user-lastname'),
152             email => get_standard_option('user-email'),
153             comment => get_standard_option('user-comment'),
154             keys => get_standard_option('user-keys'),
155             password => {
156                 description => "Initial password.",
157                 type => 'string',
158                 optional => 1,
159                 minLength => 5,
160                 maxLength => 64
161             },
162             groups => get_standard_option('group-list'),
163         },
164     },
165     returns => { type => 'null' },
166     code => sub {
167         my ($param) = @_;
168
169         PVE::AccessControl::lock_user_config(
170             sub {
171
172                 my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
173
174                 my $usercfg = cfs_read_file("user.cfg");
175
176                 die "user '$username' already exists\n"
177                     if $usercfg->{users}->{$username};
178
179                 PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password})
180                     if defined($param->{password});
181
182                 my $enable = defined($param->{enable}) ? $param->{enable} : 1;
183                 $usercfg->{users}->{$username} = { enable => $enable };
184                 $usercfg->{users}->{$username}->{expire} = $param->{expire} if $param->{expire};
185
186                 if ($param->{groups}) {
187                     foreach my $group (split_list($param->{groups})) {
188                         if ($usercfg->{groups}->{$group}) {
189                             PVE::AccessControl::add_user_group($username, $usercfg, $group);
190                         } else {
191                             die "no such group '$group'\n";
192                         }
193                     }
194                 }
195
196                 $usercfg->{users}->{$username}->{firstname} = $param->{firstname} if $param->{firstname};
197                 $usercfg->{users}->{$username}->{lastname} = $param->{lastname} if $param->{lastname};
198                 $usercfg->{users}->{$username}->{email} = $param->{email} if $param->{email};
199                 $usercfg->{users}->{$username}->{comment} = $param->{comment} if $param->{comment};
200                 $usercfg->{users}->{$username}->{keys} = $param->{keys} if $param->{keys};
201
202                 cfs_write_file("user.cfg", $usercfg);
203             }, "create user failed");
204
205         return undef;
206     }});
207
208 __PACKAGE__->register_method ({
209     name => 'read_user',
210     path => '{userid}',
211     method => 'GET',
212     description => "Get user configuration.",
213     permissions => {
214         check => ['userid-group', ['User.Modify', 'Sys.Audit']],
215     },
216     parameters => {
217         additionalProperties => 0,
218         properties => {
219             userid => get_standard_option('userid-completed'),
220         },
221     },
222     returns => {
223         additionalProperties => 0,
224         properties => {
225             enable => get_standard_option('user-enable'),
226             expire => get_standard_option('user-expire'),
227             firstname => get_standard_option('user-firstname'),
228             lastname => get_standard_option('user-lastname'),
229             email => get_standard_option('user-email'),
230             comment => get_standard_option('user-comment'),
231             keys => get_standard_option('user-keys'),
232             groups => { type => 'array' },
233         },
234         type => "object"
235     },
236     code => sub {
237         my ($param) = @_;
238
239         my ($username, undef, $domain) =
240             PVE::AccessControl::verify_username($param->{userid});
241
242         my $usercfg = cfs_read_file("user.cfg");
243
244         my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
245
246         return &$extract_user_data($data, 1);
247     }});
248
249 __PACKAGE__->register_method ({
250     name => 'update_user',
251     protected => 1,
252     path => '{userid}',
253     method => 'PUT',
254     permissions => {
255         check => ['userid-group', ['User.Modify'], groups_param => 1 ],
256     },
257     description => "Update user configuration.",
258     parameters => {
259         additionalProperties => 0,
260         properties => {
261             userid => get_standard_option('userid-completed'),
262             enable => get_standard_option('user-enable'),
263             expire => get_standard_option('user-expire'),
264             firstname => get_standard_option('user-firstname'),
265             lastname => get_standard_option('user-lastname'),
266             email => get_standard_option('user-email'),
267             comment => get_standard_option('user-comment'),
268             keys => get_standard_option('user-keys'),
269             groups => get_standard_option('group-list'),
270             append => {
271                 type => 'boolean',
272                 optional => 1,
273                 requires => 'groups',
274             },
275         },
276     },
277     returns => { type => 'null' },
278     code => sub {
279         my ($param) = @_;
280
281         my ($username, $ruid, $realm) =
282             PVE::AccessControl::verify_username($param->{userid});
283
284         PVE::AccessControl::lock_user_config(
285             sub {
286
287                 my $usercfg = cfs_read_file("user.cfg");
288
289                 PVE::AccessControl::check_user_exist($usercfg, $username);
290
291                 $usercfg->{users}->{$username}->{enable} = $param->{enable} if defined($param->{enable});
292
293                 $usercfg->{users}->{$username}->{expire} = $param->{expire} if defined($param->{expire});
294
295                 PVE::AccessControl::delete_user_group($username, $usercfg)
296                     if (!$param->{append} && defined($param->{groups}));
297
298                 if ($param->{groups}) {
299                     foreach my $group (split_list($param->{groups})) {
300                         if ($usercfg->{groups}->{$group}) {
301                             PVE::AccessControl::add_user_group($username, $usercfg, $group);
302                         } else {
303                             die "no such group '$group'\n";
304                         }
305                     }
306                 }
307
308                 $usercfg->{users}->{$username}->{firstname} = $param->{firstname} if defined($param->{firstname});
309                 $usercfg->{users}->{$username}->{lastname} = $param->{lastname} if defined($param->{lastname});
310                 $usercfg->{users}->{$username}->{email} = $param->{email} if defined($param->{email});
311                 $usercfg->{users}->{$username}->{comment} = $param->{comment} if defined($param->{comment});
312                 $usercfg->{users}->{$username}->{keys} = $param->{keys} if defined($param->{keys});
313
314                 cfs_write_file("user.cfg", $usercfg);
315             }, "update user failed");
316
317         return undef;
318     }});
319
320 __PACKAGE__->register_method ({
321     name => 'delete_user',
322     protected => 1,
323     path => '{userid}',
324     method => 'DELETE',
325     description => "Delete user.",
326     permissions => {
327         check => [ 'and',
328                    [ 'userid-param', 'Realm.AllocateUser'],
329                    [ 'userid-group', ['User.Modify']],
330             ],
331     },
332     parameters => {
333         additionalProperties => 0,
334         properties => {
335             userid => get_standard_option('userid-completed'),
336         }
337     },
338     returns => { type => 'null' },
339     code => sub {
340         my ($param) = @_;
341
342         my $rpcenv = PVE::RPCEnvironment::get();
343         my $authuser = $rpcenv->get_user();
344
345         my ($userid, $ruid, $realm) =
346             PVE::AccessControl::verify_username($param->{userid});
347
348         PVE::AccessControl::lock_user_config(
349             sub {
350
351                 my $usercfg = cfs_read_file("user.cfg");
352
353                 my $domain_cfg = cfs_read_file('domains.cfg');
354                 if (my $cfg = $domain_cfg->{ids}->{$realm}) {
355                     my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
356                     $plugin->delete_user($cfg, $realm, $ruid);
357                 }
358
359                 delete $usercfg->{users}->{$userid};
360
361                 PVE::AccessControl::delete_user_group($userid, $usercfg);
362                 PVE::AccessControl::delete_user_acl($userid, $usercfg);
363
364                 cfs_write_file("user.cfg", $usercfg);
365             }, "delete user failed");
366
367         return undef;
368     }});
369
370 1;