]> git.proxmox.com Git - pve-access-control.git/blame - src/PVE/API2/User.pm
api: users: code-style cleanup and sort when iterating users
[pve-access-control.git] / src / PVE / API2 / User.pm
CommitLineData
2c3a6c0a
DM
1package PVE::API2::User;
2
3use strict;
4use warnings;
525a931b 5
4e4c8d40 6use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
2c3a6c0a 7use PVE::Cluster qw (cfs_read_file cfs_write_file);
4e4c8d40 8use PVE::Tools qw(split_list extract_param);
3a5ae7a0 9use PVE::JSONSchema qw(get_standard_option register_standard_option);
2c3a6c0a
DM
10use PVE::SafeSyslog;
11
525a931b
TL
12use PVE::AccessControl;
13use PVE::TokenConfig;
14
2c3a6c0a
DM
15use PVE::RESTHandler;
16
17use base qw(PVE::RESTHandler);
18
3a5ae7a0
SI
19register_standard_option('user-enable', {
20 description => "Enable the account (default). You can set this to '0' to disable the account",
21 type => 'boolean',
22 optional => 1,
23 default => 1,
24});
25register_standard_option('user-expire', {
26 description => "Account expiration date (seconds since epoch). '0' means no expiration date.",
27 type => 'integer',
28 minimum => 0,
29 optional => 1,
30});
31register_standard_option('user-firstname', { type => 'string', optional => 1 });
32register_standard_option('user-lastname', { type => 'string', optional => 1 });
33register_standard_option('user-email', { type => 'string', optional => 1, format => 'email-opt' });
34register_standard_option('user-comment', { type => 'string', optional => 1 });
35register_standard_option('user-keys', {
36 description => "Keys for two factor auth (yubico).",
37 type => 'string',
38 optional => 1,
39});
40register_standard_option('group-list', {
41 type => 'string', format => 'pve-groupid-list',
42 optional => 1,
43 completion => \&PVE::AccessControl::complete_group,
44});
4e4c8d40
FG
45register_standard_option('token-subid', {
46 type => 'string',
47 pattern => $PVE::AccessControl::token_subid_regex,
48 description => 'User-specific token identifier.',
49});
50register_standard_option('token-expire', {
51 description => "API token expiration date (seconds since epoch). '0' means no expiration date.",
52 type => 'integer',
53 minimum => 0,
54 optional => 1,
b974bdc0 55 default => 'same as user',
4e4c8d40
FG
56});
57register_standard_option('token-privsep', {
58 description => "Restrict API token privileges with separate ACLs (default), or give full privileges of corresponding user.",
59 type => 'boolean',
60 optional => 1,
61 default => 1,
62});
63register_standard_option('token-comment', { type => 'string', optional => 1 });
64register_standard_option('token-info', {
65 type => 'object',
66 properties => {
67 expire => get_standard_option('token-expire'),
68 privsep => get_standard_option('token-privsep'),
69 comment => get_standard_option('token-comment'),
70 }
71});
72
73my $token_info_extend = sub {
74 my ($props) = @_;
75
76 my $obj = get_standard_option('token-info');
77 my $base_props = $obj->{properties};
78 $obj->{properties} = {};
79
80 foreach my $prop (keys %$base_props) {
81 $obj->{properties}->{$prop} = $base_props->{$prop};
82 }
83
84 foreach my $add_prop (keys %$props) {
85 $obj->{properties}->{$add_prop} = $props->{$add_prop};
86 }
87
88 return $obj;
89};
3a5ae7a0 90
2c3a6c0a
DM
91my $extract_user_data = sub {
92 my ($data, $full) = @_;
93
94 my $res = {};
95
96f8ebd6 96 foreach my $prop (qw(enable expire firstname lastname email comment keys)) {
2c3a6c0a
DM
97 $res->{$prop} = $data->{$prop} if defined($data->{$prop});
98 }
99
100 return $res if !$full;
101
102 $res->{groups} = $data->{groups} ? [ keys %{$data->{groups}} ] : [];
4e4c8d40 103 $res->{tokens} = $data->{tokens};
2c3a6c0a
DM
104
105 return $res;
106};
107
108__PACKAGE__->register_method ({
0a6e09fd
PA
109 name => 'index',
110 path => '',
2c3a6c0a
DM
111 method => 'GET',
112 description => "User index.",
0a6e09fd 113 permissions => {
82b63965 114 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.",
96919234
DM
115 user => 'all',
116 },
2c3a6c0a
DM
117 parameters => {
118 additionalProperties => 0,
cb6f2f93
DM
119 properties => {
120 enabled => {
121 type => 'boolean',
122 description => "Optional filter for enable property.",
123 optional => 1,
3a4ed527
FG
124 },
125 full => {
126 type => 'boolean',
127 description => "Include group and token information.",
128 optional => 1,
129 default => 0,
cb6f2f93
DM
130 }
131 },
2c3a6c0a
DM
132 },
133 returns => {
134 type => 'array',
135 items => {
136 type => "object",
137 properties => {
3a5ae7a0
SI
138 userid => get_standard_option('userid-completed'),
139 enable => get_standard_option('user-enable'),
140 expire => get_standard_option('user-expire'),
141 firstname => get_standard_option('user-firstname'),
142 lastname => get_standard_option('user-lastname'),
143 email => get_standard_option('user-email'),
144 comment => get_standard_option('user-comment'),
145 keys => get_standard_option('user-keys'),
3a4ed527
FG
146 groups => get_standard_option('group-list'),
147 tokens => {
148 type => 'array',
149 optional => 1,
150 items => $token_info_extend->({
151 tokenid => get_standard_option('token-subid'),
152 }),
8bb59c26 153 },
3f6023f5
TL
154 'realm-type' => {
155 type => 'string', format => 'pve-realm',
8bb59c26 156 description => 'The type of the users realm',
3f6023f5 157 optional => 1, # it should always be there, but we use conditional code below, so..
8bb59c26 158 },
2c3a6c0a
DM
159 },
160 },
161 links => [ { rel => 'child', href => "{userid}" } ],
162 },
163 code => sub {
164 my ($param) = @_;
0a6e09fd 165
930dcfc8 166 my $rpcenv = PVE::RPCEnvironment::get();
37d45deb 167 my $usercfg = $rpcenv->{user_cfg};
930dcfc8
DM
168 my $authuser = $rpcenv->get_user();
169
8bb59c26
DC
170 my $domainscfg = cfs_read_file('domains.cfg');
171 my $domainids = $domainscfg->{ids};
172
2c3a6c0a
DM
173 my $res = [];
174
82b63965
DM
175 my $privs = [ 'User.Modify', 'Sys.Audit' ];
176 my $canUserMod = $rpcenv->check_any($authuser, "/access/groups", $privs, 1);
b9180ed2 177 my $groups = $rpcenv->filter_groups($authuser, $privs, 1);
0a6e09fd 178 my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
37d45deb 179
525a931b 180 foreach my $user (sort keys %{$usercfg->{users}}) {
37d45deb
DM
181 if (!($canUserMod || $user eq $authuser)) {
182 next if !$allowed_users->{$user};
183 }
930dcfc8 184
525a931b 185 my $entry = $extract_user_data->($usercfg->{users}->{$user}, $param->{full});
cb6f2f93
DM
186
187 if (defined($param->{enabled})) {
188 next if $entry->{enable} && !$param->{enabled};
189 next if !$entry->{enable} && $param->{enabled};
190 }
191
3a4ed527 192 $entry->{groups} = join(',', @{$entry->{groups}}) if $entry->{groups};
525a931b
TL
193
194 if (defined(my $tokens = $entry->{tokens})) {
195 $entry->{tokens} = [
196 map { { tokenid => $_, %{$tokens->{$_}} } } sort keys %$tokens
197 ];
198 }
3a4ed527 199
8bb59c26 200 my (undef, undef, $realm) = PVE::AccessControl::verify_username($user, 1);
3f6023f5
TL
201 if (defined($realm) && exists($domainids->{$realm})) {
202 $entry->{'realm-type'} = $domainids->{$realm}->{type};
8bb59c26
DC
203 }
204
2c3a6c0a 205 $entry->{userid} = $user;
525a931b 206
2c3a6c0a
DM
207 push @$res, $entry;
208 }
209
210 return $res;
211 }});
212
213__PACKAGE__->register_method ({
0a6e09fd 214 name => 'create_user',
2c3a6c0a 215 protected => 1,
0a6e09fd 216 path => '',
2c3a6c0a 217 method => 'POST',
0a6e09fd 218 permissions => {
82b63965
DM
219 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.",
220 check => [ 'and',
221 [ 'userid-param', 'Realm.AllocateUser'],
222 [ 'userid-group', ['User.Modify'], groups_param => 1],
223 ],
96919234 224 },
2c3a6c0a
DM
225 description => "Create new user.",
226 parameters => {
0a6e09fd 227 additionalProperties => 0,
2c3a6c0a 228 properties => {
3a5ae7a0
SI
229 userid => get_standard_option('userid-completed'),
230 enable => get_standard_option('user-enable'),
231 expire => get_standard_option('user-expire'),
232 firstname => get_standard_option('user-firstname'),
233 lastname => get_standard_option('user-lastname'),
234 email => get_standard_option('user-email'),
235 comment => get_standard_option('user-comment'),
236 keys => get_standard_option('user-keys'),
37d45deb
DM
237 password => {
238 description => "Initial password.",
0a6e09fd
PA
239 type => 'string',
240 optional => 1,
241 minLength => 5,
242 maxLength => 64
37d45deb 243 },
3a5ae7a0 244 groups => get_standard_option('group-list'),
2c3a6c0a
DM
245 },
246 },
247 returns => { type => 'null' },
248 code => sub {
249 my ($param) = @_;
250
2dd1e1d4
TL
251 PVE::AccessControl::lock_user_config(sub {
252 my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
0a6e09fd 253
2dd1e1d4 254 my $usercfg = cfs_read_file("user.cfg");
0a6e09fd 255
f335d265
TL
256 # ensure "user exists" check works for case insensitive realms
257 $username = PVE::AccessControl::lookup_username($username, 1);
258 die "user '$username' already exists\n" if $usercfg->{users}->{$username};
2c3a6c0a 259
2dd1e1d4
TL
260 PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password})
261 if defined($param->{password});
0a6e09fd 262
2dd1e1d4
TL
263 my $enable = defined($param->{enable}) ? $param->{enable} : 1;
264 $usercfg->{users}->{$username} = { enable => $enable };
265 $usercfg->{users}->{$username}->{expire} = $param->{expire} if $param->{expire};
2c3a6c0a 266
2dd1e1d4
TL
267 if ($param->{groups}) {
268 foreach my $group (split_list($param->{groups})) {
269 if ($usercfg->{groups}->{$group}) {
270 PVE::AccessControl::add_user_group($username, $usercfg, $group);
271 } else {
272 die "no such group '$group'\n";
2c3a6c0a
DM
273 }
274 }
2dd1e1d4 275 }
2c3a6c0a 276
2dd1e1d4
TL
277 $usercfg->{users}->{$username}->{firstname} = $param->{firstname} if $param->{firstname};
278 $usercfg->{users}->{$username}->{lastname} = $param->{lastname} if $param->{lastname};
279 $usercfg->{users}->{$username}->{email} = $param->{email} if $param->{email};
280 $usercfg->{users}->{$username}->{comment} = $param->{comment} if $param->{comment};
281 $usercfg->{users}->{$username}->{keys} = $param->{keys} if $param->{keys};
2c3a6c0a 282
2dd1e1d4
TL
283 cfs_write_file("user.cfg", $usercfg);
284 }, "create user failed");
2c3a6c0a
DM
285
286 return undef;
287 }});
288
289__PACKAGE__->register_method ({
0a6e09fd
PA
290 name => 'read_user',
291 path => '{userid}',
2c3a6c0a
DM
292 method => 'GET',
293 description => "Get user configuration.",
0a6e09fd 294 permissions => {
82b63965 295 check => ['userid-group', ['User.Modify', 'Sys.Audit']],
96919234 296 },
2c3a6c0a 297 parameters => {
0a6e09fd 298 additionalProperties => 0,
2c3a6c0a 299 properties => {
3a5ae7a0 300 userid => get_standard_option('userid-completed'),
2c3a6c0a
DM
301 },
302 },
303 returns => {
0a6e09fd 304 additionalProperties => 0,
2c3a6c0a 305 properties => {
3a5ae7a0
SI
306 enable => get_standard_option('user-enable'),
307 expire => get_standard_option('user-expire'),
308 firstname => get_standard_option('user-firstname'),
309 lastname => get_standard_option('user-lastname'),
310 email => get_standard_option('user-email'),
311 comment => get_standard_option('user-comment'),
312 keys => get_standard_option('user-keys'),
4e4c8d40
FG
313 groups => {
314 type => 'array',
72c4589c 315 optional => 1,
4e4c8d40
FG
316 items => {
317 type => 'string',
318 format => 'pve-groupid',
319 },
320 },
321 tokens => {
72c4589c 322 optional => 1,
4e4c8d40
FG
323 type => 'object',
324 },
3a5ae7a0
SI
325 },
326 type => "object"
2c3a6c0a
DM
327 },
328 code => sub {
329 my ($param) = @_;
330
0a6e09fd 331 my ($username, undef, $domain) =
2c3a6c0a
DM
332 PVE::AccessControl::verify_username($param->{userid});
333
334 my $usercfg = cfs_read_file("user.cfg");
2c3a6c0a 335
37d45deb 336 my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
0a6e09fd 337
2c3a6c0a
DM
338 return &$extract_user_data($data, 1);
339 }});
340
341__PACKAGE__->register_method ({
0a6e09fd 342 name => 'update_user',
2c3a6c0a 343 protected => 1,
0a6e09fd 344 path => '{userid}',
2c3a6c0a 345 method => 'PUT',
0a6e09fd 346 permissions => {
96919234
DM
347 check => ['userid-group', ['User.Modify'], groups_param => 1 ],
348 },
2c3a6c0a
DM
349 description => "Update user configuration.",
350 parameters => {
0a6e09fd 351 additionalProperties => 0,
2c3a6c0a 352 properties => {
3a5ae7a0
SI
353 userid => get_standard_option('userid-completed'),
354 enable => get_standard_option('user-enable'),
355 expire => get_standard_option('user-expire'),
356 firstname => get_standard_option('user-firstname'),
357 lastname => get_standard_option('user-lastname'),
358 email => get_standard_option('user-email'),
359 comment => get_standard_option('user-comment'),
360 keys => get_standard_option('user-keys'),
361 groups => get_standard_option('group-list'),
0a6e09fd
PA
362 append => {
363 type => 'boolean',
2c3a6c0a
DM
364 optional => 1,
365 requires => 'groups',
366 },
2c3a6c0a
DM
367 },
368 },
369 returns => { type => 'null' },
370 code => sub {
371 my ($param) = @_;
37d45deb 372
0a6e09fd 373 my ($username, $ruid, $realm) =
37d45deb 374 PVE::AccessControl::verify_username($param->{userid});
0a6e09fd 375
2c3a6c0a
DM
376 PVE::AccessControl::lock_user_config(
377 sub {
0a6e09fd 378
2c3a6c0a
DM
379 my $usercfg = cfs_read_file("user.cfg");
380
37d45deb 381 PVE::AccessControl::check_user_exist($usercfg, $username);
2c3a6c0a
DM
382
383 $usercfg->{users}->{$username}->{enable} = $param->{enable} if defined($param->{enable});
384
385 $usercfg->{users}->{$username}->{expire} = $param->{expire} if defined($param->{expire});
386
0a6e09fd 387 PVE::AccessControl::delete_user_group($username, $usercfg)
e6521738 388 if (!$param->{append} && defined($param->{groups}));
2c3a6c0a
DM
389
390 if ($param->{groups}) {
391 foreach my $group (split_list($param->{groups})) {
392 if ($usercfg->{groups}->{$group}) {
393 PVE::AccessControl::add_user_group($username, $usercfg, $group);
394 } else {
395 die "no such group '$group'\n";
396 }
397 }
398 }
399
400 $usercfg->{users}->{$username}->{firstname} = $param->{firstname} if defined($param->{firstname});
401 $usercfg->{users}->{$username}->{lastname} = $param->{lastname} if defined($param->{lastname});
402 $usercfg->{users}->{$username}->{email} = $param->{email} if defined($param->{email});
403 $usercfg->{users}->{$username}->{comment} = $param->{comment} if defined($param->{comment});
96f8ebd6 404 $usercfg->{users}->{$username}->{keys} = $param->{keys} if defined($param->{keys});
2c3a6c0a
DM
405
406 cfs_write_file("user.cfg", $usercfg);
407 }, "update user failed");
0a6e09fd 408
2c3a6c0a
DM
409 return undef;
410 }});
411
412__PACKAGE__->register_method ({
0a6e09fd 413 name => 'delete_user',
2c3a6c0a 414 protected => 1,
0a6e09fd 415 path => '{userid}',
2c3a6c0a
DM
416 method => 'DELETE',
417 description => "Delete user.",
0a6e09fd 418 permissions => {
82b63965
DM
419 check => [ 'and',
420 [ 'userid-param', 'Realm.AllocateUser'],
421 [ 'userid-group', ['User.Modify']],
422 ],
12683df7 423 },
2c3a6c0a 424 parameters => {
0a6e09fd 425 additionalProperties => 0,
2c3a6c0a 426 properties => {
3a5ae7a0 427 userid => get_standard_option('userid-completed'),
2c3a6c0a
DM
428 }
429 },
430 returns => { type => 'null' },
431 code => sub {
432 my ($param) = @_;
0a6e09fd 433
37d45deb
DM
434 my $rpcenv = PVE::RPCEnvironment::get();
435 my $authuser = $rpcenv->get_user();
436
0a6e09fd 437 my ($userid, $ruid, $realm) =
37d45deb 438 PVE::AccessControl::verify_username($param->{userid});
2c3a6c0a
DM
439
440 PVE::AccessControl::lock_user_config(
441 sub {
442
2c3a6c0a
DM
443 my $usercfg = cfs_read_file("user.cfg");
444
5bb4e06a
DM
445 my $domain_cfg = cfs_read_file('domains.cfg');
446 if (my $cfg = $domain_cfg->{ids}->{$realm}) {
447 my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
448 $plugin->delete_user($cfg, $realm, $ruid);
449 }
2c3a6c0a 450
9536c4dc
WB
451 # Remove TFA data before removing the user entry as the user entry tells us whether
452 # we need ot update priv/tfa.cfg.
453 PVE::AccessControl::user_set_tfa($userid, $realm, undef, undef, $usercfg, $domain_cfg);
454
5bb4e06a 455 delete $usercfg->{users}->{$userid};
37d45deb
DM
456
457 PVE::AccessControl::delete_user_group($userid, $usercfg);
458 PVE::AccessControl::delete_user_acl($userid, $usercfg);
2c3a6c0a
DM
459 cfs_write_file("user.cfg", $usercfg);
460 }, "delete user failed");
0a6e09fd 461
2c3a6c0a
DM
462 return undef;
463 }});
464
e51988b4
DC
465__PACKAGE__->register_method ({
466 name => 'read_user_tfa_type',
467 path => '{userid}/tfa',
468 method => 'GET',
469 protected => 1,
470 description => "Get user TFA types (Personal and Realm).",
471 permissions => {
472 check => [ 'or',
473 ['userid-param', 'self'],
474 ['userid-group', ['User.Modify', 'Sys.Audit']],
475 ],
476 },
477 parameters => {
478 additionalProperties => 0,
479 properties => {
480 userid => get_standard_option('userid-completed'),
481 },
482 },
483 returns => {
484 additionalProperties => 0,
485 properties => {
486 realm => {
487 type => 'string',
488 enum => [qw(oath yubico)],
489 description => "The type of TFA the users realm has set, if any.",
490 optional => 1,
491 },
492 user => {
493 type => 'string',
494 enum => [qw(oath u2f)],
495 description => "The type of TFA the user has set, if any.",
496 optional => 1,
497 },
498 },
499 type => "object"
500 },
501 code => sub {
502 my ($param) = @_;
503
504 my ($username, undef, $realm) = PVE::AccessControl::verify_username($param->{userid});
505
506
507 my $domain_cfg = cfs_read_file('domains.cfg');
508 my $realm_cfg = $domain_cfg->{ids}->{$realm};
509 die "auth domain '$realm' does not exist\n" if !$realm_cfg;
510
511 my $realm_tfa = {};
512 $realm_tfa = PVE::Auth::Plugin::parse_tfa_config($realm_cfg->{tfa})
513 if $realm_cfg->{tfa};
514
515 my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
516 my $tfa = $tfa_cfg->{users}->{$username};
517
518 my $res = {};
519 $res->{realm} = $realm_tfa->{type} if $realm_tfa->{type};
520 $res->{user} = $tfa->{type} if $tfa->{type};
521 return $res;
522 }});
523
4e4c8d40
FG
524__PACKAGE__->register_method ({
525 name => 'token_index',
526 path => '{userid}/token',
527 method => 'GET',
528 description => "Get user API tokens.",
529 permissions => {
530 check => ['or',
531 ['userid-param', 'self'],
532 ['perm', '/access/users/{userid}', ['User.Modify']],
533 ],
534 },
535 parameters => {
536 additionalProperties => 0,
537 properties => {
538 userid => get_standard_option('userid-completed'),
539 },
540 },
541 returns => {
542 type => "array",
543 items => $token_info_extend->({
544 tokenid => get_standard_option('token-subid'),
545 }),
546 links => [ { rel => 'child', href => "{tokenid}" } ],
547 },
548 code => sub {
549 my ($param) = @_;
550
551 my $userid = PVE::AccessControl::verify_username($param->{userid});
552 my $usercfg = cfs_read_file("user.cfg");
553
554 my $user = PVE::AccessControl::check_user_exist($usercfg, $userid);
555
556 my $tokens = $user->{tokens} // {};
557 return [ map { $tokens->{$_}->{tokenid} = $_; $tokens->{$_} } keys %$tokens];
558 }});
559
560__PACKAGE__->register_method ({
561 name => 'read_token',
562 path => '{userid}/token/{tokenid}',
563 method => 'GET',
564 description => "Get specific API token information.",
565 permissions => {
566 check => ['or',
567 ['userid-param', 'self'],
568 ['perm', '/access/users/{userid}', ['User.Modify']],
569 ],
570 },
571 parameters => {
572 additionalProperties => 0,
573 properties => {
574 userid => get_standard_option('userid-completed'),
575 tokenid => get_standard_option('token-subid'),
576 },
577 },
578 returns => get_standard_option('token-info'),
579 code => sub {
580 my ($param) = @_;
581
582 my $userid = PVE::AccessControl::verify_username($param->{userid});
583 my $tokenid = $param->{tokenid};
584
585 my $usercfg = cfs_read_file("user.cfg");
586
587 return PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
588 }});
589
590__PACKAGE__->register_method ({
591 name => 'generate_token',
592 path => '{userid}/token/{tokenid}',
593 method => 'POST',
594 description => "Generate a new API token for a specific user. NOTE: returns API token value, which needs to be stored as it cannot be retrieved afterwards!",
595 protected => 1,
596 permissions => {
597 check => ['or',
598 ['userid-param', 'self'],
599 ['perm', '/access/users/{userid}', ['User.Modify']],
600 ],
601 },
602 parameters => {
603 additionalProperties => 0,
604 properties => {
605 userid => get_standard_option('userid-completed'),
606 tokenid => get_standard_option('token-subid'),
607 expire => get_standard_option('token-expire'),
608 privsep => get_standard_option('token-privsep'),
609 comment => get_standard_option('token-comment'),
610 },
611 },
612 returns => {
613 additionalProperties => 0,
614 type => "object",
615 properties => {
616 info => get_standard_option('token-info'),
617 value => {
618 type => 'string',
619 description => 'API token value used for authentication.',
620 },
77bfb48e
TL
621 'full-tokenid' => {
622 type => 'string',
623 format_description => '<userid>!<tokenid>',
624 description => 'The full token id.',
625 },
4e4c8d40
FG
626 },
627 },
628 code => sub {
629 my ($param) = @_;
630
631 my $userid = PVE::AccessControl::verify_username(extract_param($param, 'userid'));
632 my $tokenid = extract_param($param, 'tokenid');
633
634 my $usercfg = cfs_read_file("user.cfg");
635
636 my $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid, 1);
77bfb48e 637 my ($full_tokenid, $value);
4e4c8d40
FG
638
639 PVE::AccessControl::check_user_exist($usercfg, $userid);
640 raise_param_exc({ 'tokenid' => 'Token already exists.' }) if defined($token);
641
642 my $generate_and_add_token = sub {
643 $usercfg = cfs_read_file("user.cfg");
644 PVE::AccessControl::check_user_exist($usercfg, $userid);
645 die "Token already exists.\n" if defined(PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid, 1));
646
77bfb48e 647 $full_tokenid = PVE::AccessControl::join_tokenid($userid, $tokenid);
4e4c8d40
FG
648 $value = PVE::TokenConfig::generate_token($full_tokenid);
649
650 $token = {};
651 $token->{privsep} = defined($param->{privsep}) ? $param->{privsep} : 1;
652 $token->{expire} = $param->{expire} if defined($param->{expire});
653 $token->{comment} = $param->{comment} if $param->{comment};
654
655 $usercfg->{users}->{$userid}->{tokens}->{$tokenid} = $token;
656 cfs_write_file("user.cfg", $usercfg);
657 };
658
659 PVE::AccessControl::lock_user_config($generate_and_add_token, 'generating token failed');
660
77bfb48e
TL
661 return {
662 info => $token,
663 value => $value,
664 'full-tokenid' => $full_tokenid,
665 };
4e4c8d40
FG
666 }});
667
668
669__PACKAGE__->register_method ({
670 name => 'update_token_info',
671 path => '{userid}/token/{tokenid}',
672 method => 'PUT',
673 description => "Update API token for a specific user.",
674 protected => 1,
675 permissions => {
676 check => ['or',
677 ['userid-param', 'self'],
678 ['perm', '/access/users/{userid}', ['User.Modify']],
679 ],
680 },
681 parameters => {
682 additionalProperties => 0,
683 properties => {
684 userid => get_standard_option('userid-completed'),
685 tokenid => get_standard_option('token-subid'),
686 expire => get_standard_option('token-expire'),
687 privsep => get_standard_option('token-privsep'),
688 comment => get_standard_option('token-comment'),
689 },
690 },
691 returns => get_standard_option('token-info', { description => "Updated token information." }),
692 code => sub {
693 my ($param) = @_;
694
695 my $userid = PVE::AccessControl::verify_username(extract_param($param, 'userid'));
696 my $tokenid = extract_param($param, 'tokenid');
697
698 my $usercfg = cfs_read_file("user.cfg");
699 my $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
700
701 my $update_token = sub {
702 $usercfg = cfs_read_file("user.cfg");
703 $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
704
705 my $full_tokenid = PVE::AccessControl::join_tokenid($userid, $tokenid);
706
707 $token->{privsep} = $param->{privsep} if defined($param->{privsep});
708 $token->{expire} = $param->{expire} if defined($param->{expire});
709 $token->{comment} = $param->{comment} if $param->{comment};
710
711 $usercfg->{users}->{$userid}->{tokens}->{$tokenid} = $token;
712 cfs_write_file("user.cfg", $usercfg);
713 };
714
715 PVE::AccessControl::lock_user_config($update_token, 'updating token info failed');
716
717 return $token;
718 }});
719
720
721__PACKAGE__->register_method ({
722 name => 'remove_token',
723 path => '{userid}/token/{tokenid}',
724 method => 'DELETE',
725 description => "Remove API token for a specific user.",
726 protected => 1,
727 permissions => {
728 check => ['or',
729 ['userid-param', 'self'],
730 ['perm', '/access/users/{userid}', ['User.Modify']],
731 ],
732 },
733 parameters => {
734 additionalProperties => 0,
735 properties => {
736 userid => get_standard_option('userid-completed'),
737 tokenid => get_standard_option('token-subid'),
738 },
739 },
740 returns => { type => 'null' },
741 code => sub {
742 my ($param) = @_;
743
744 my $userid = PVE::AccessControl::verify_username(extract_param($param, 'userid'));
745 my $tokenid = extract_param($param, 'tokenid');
746
747 my $usercfg = cfs_read_file("user.cfg");
748 my $token = PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
749
750 my $update_token = sub {
751 $usercfg = cfs_read_file("user.cfg");
752
753 PVE::AccessControl::check_token_exist($usercfg, $userid, $tokenid);
754
755 my $full_tokenid = PVE::AccessControl::join_tokenid($userid, $tokenid);
756 PVE::TokenConfig::delete_token($full_tokenid);
757 delete $usercfg->{users}->{$userid}->{tokens}->{$tokenid};
758
759 cfs_write_file("user.cfg", $usercfg);
760 };
761
762 PVE::AccessControl::lock_user_config($update_token, 'deleting token failed');
763
764 return;
765 }});
2c3a6c0a 7661;