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