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