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