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;