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