]> git.proxmox.com Git - pve-access-control.git/blame - PVE/API2/User.pm
d/control: bump debhelper compat to >= 12
[pve-access-control.git] / PVE / API2 / User.pm
CommitLineData
2c3a6c0a
DM
1package PVE::API2::User;
2
3use strict;
4use warnings;
4e4c8d40 5use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
2c3a6c0a 6use PVE::Cluster qw (cfs_read_file cfs_write_file);
4e4c8d40 7use PVE::Tools qw(split_list extract_param);
2c3a6c0a 8use PVE::AccessControl;
3a5ae7a0 9use PVE::JSONSchema qw(get_standard_option register_standard_option);
4e4c8d40 10use PVE::TokenConfig;
2c3a6c0a
DM
11
12use PVE::SafeSyslog;
13
2c3a6c0a
DM
14use PVE::RESTHandler;
15
16use base qw(PVE::RESTHandler);
17
3a5ae7a0
SI
18register_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});
24register_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});
30register_standard_option('user-firstname', { type => 'string', optional => 1 });
31register_standard_option('user-lastname', { type => 'string', optional => 1 });
32register_standard_option('user-email', { type => 'string', optional => 1, format => 'email-opt' });
33register_standard_option('user-comment', { type => 'string', optional => 1 });
34register_standard_option('user-keys', {
35 description => "Keys for two factor auth (yubico).",
36 type => 'string',
37 optional => 1,
38});
39register_standard_option('group-list', {
40 type => 'string', format => 'pve-groupid-list',
41 optional => 1,
42 completion => \&PVE::AccessControl::complete_group,
43});
4e4c8d40
FG
44register_standard_option('token-subid', {
45 type => 'string',
46 pattern => $PVE::AccessControl::token_subid_regex,
47 description => 'User-specific token identifier.',
48});
49register_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,
b974bdc0 54 default => 'same as user',
4e4c8d40
FG
55});
56register_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});
62register_standard_option('token-comment', { type => 'string', optional => 1 });
63register_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
72my $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};
3a5ae7a0 89
2c3a6c0a
DM
90my $extract_user_data = sub {
91 my ($data, $full) = @_;
92
93 my $res = {};
94
96f8ebd6 95 foreach my $prop (qw(enable expire firstname lastname email comment keys)) {
2c3a6c0a
DM
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}} ] : [];
4e4c8d40 102 $res->{tokens} = $data->{tokens};
2c3a6c0a
DM
103
104 return $res;
105};
106
107__PACKAGE__->register_method ({
0a6e09fd
PA
108 name => 'index',
109 path => '',
2c3a6c0a
DM
110 method => 'GET',
111 description => "User index.",
0a6e09fd 112 permissions => {
82b63965 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.",
96919234
DM
114 user => 'all',
115 },
2c3a6c0a
DM
116 parameters => {
117 additionalProperties => 0,
cb6f2f93
DM
118 properties => {
119 enabled => {
120 type => 'boolean',
121 description => "Optional filter for enable property.",
122 optional => 1,
3a4ed527
FG
123 },
124 full => {
125 type => 'boolean',
126 description => "Include group and token information.",
127 optional => 1,
128 default => 0,
cb6f2f93
DM
129 }
130 },
2c3a6c0a
DM
131 },
132 returns => {
133 type => 'array',
134 items => {
135 type => "object",
136 properties => {
3a5ae7a0
SI
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'),
3a4ed527
FG
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 }
2c3a6c0a
DM
153 },
154 },
155 links => [ { rel => 'child', href => "{userid}" } ],
156 },
157 code => sub {
158 my ($param) = @_;
0a6e09fd 159
930dcfc8 160 my $rpcenv = PVE::RPCEnvironment::get();
37d45deb 161 my $usercfg = $rpcenv->{user_cfg};
930dcfc8
DM
162 my $authuser = $rpcenv->get_user();
163
2c3a6c0a
DM
164 my $res = [];
165
82b63965
DM
166 my $privs = [ 'User.Modify', 'Sys.Audit' ];
167 my $canUserMod = $rpcenv->check_any($authuser, "/access/groups", $privs, 1);
b9180ed2 168 my $groups = $rpcenv->filter_groups($authuser, $privs, 1);
0a6e09fd 169 my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
37d45deb 170
2c3a6c0a 171 foreach my $user (keys %{$usercfg->{users}}) {
37d45deb
DM
172 if (!($canUserMod || $user eq $authuser)) {
173 next if !$allowed_users->{$user};
174 }
930dcfc8 175
3a4ed527 176 my $entry = &$extract_user_data($usercfg->{users}->{$user}, $param->{full});
cb6f2f93
DM
177
178 if (defined($param->{enabled})) {
179 next if $entry->{enable} && !$param->{enabled};
180 next if !$entry->{enable} && $param->{enabled};
181 }
182
3a4ed527
FG
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
2c3a6c0a
DM
187 $entry->{userid} = $user;
188 push @$res, $entry;
189 }
190
191 return $res;
192 }});
193
194__PACKAGE__->register_method ({
0a6e09fd 195 name => 'create_user',
2c3a6c0a 196 protected => 1,
0a6e09fd 197 path => '',
2c3a6c0a 198 method => 'POST',
0a6e09fd 199 permissions => {
82b63965
DM
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 ],
96919234 205 },
2c3a6c0a
DM
206 description => "Create new user.",
207 parameters => {
0a6e09fd 208 additionalProperties => 0,
2c3a6c0a 209 properties => {
3a5ae7a0
SI
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'),
37d45deb
DM
218 password => {
219 description => "Initial password.",
0a6e09fd
PA
220 type => 'string',
221 optional => 1,
222 minLength => 5,
223 maxLength => 64
37d45deb 224 },
3a5ae7a0 225 groups => get_standard_option('group-list'),
2c3a6c0a
DM
226 },
227 },
228 returns => { type => 'null' },
229 code => sub {
230 my ($param) = @_;
231
2dd1e1d4
TL
232 PVE::AccessControl::lock_user_config(sub {
233 my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
0a6e09fd 234
2dd1e1d4 235 my $usercfg = cfs_read_file("user.cfg");
0a6e09fd 236
f335d265
TL
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};
2c3a6c0a 240
2dd1e1d4
TL
241 PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password})
242 if defined($param->{password});
0a6e09fd 243
2dd1e1d4
TL
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};
2c3a6c0a 247
2dd1e1d4
TL
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";
2c3a6c0a
DM
254 }
255 }
2dd1e1d4 256 }
2c3a6c0a 257
2dd1e1d4
TL
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};
2c3a6c0a 263
2dd1e1d4
TL
264 cfs_write_file("user.cfg", $usercfg);
265 }, "create user failed");
2c3a6c0a
DM
266
267 return undef;
268 }});
269
270__PACKAGE__->register_method ({
0a6e09fd
PA
271 name => 'read_user',
272 path => '{userid}',
2c3a6c0a
DM
273 method => 'GET',
274 description => "Get user configuration.",
0a6e09fd 275 permissions => {
82b63965 276 check => ['userid-group', ['User.Modify', 'Sys.Audit']],
96919234 277 },
2c3a6c0a 278 parameters => {
0a6e09fd 279 additionalProperties => 0,
2c3a6c0a 280 properties => {
3a5ae7a0 281 userid => get_standard_option('userid-completed'),
2c3a6c0a
DM
282 },
283 },
284 returns => {
0a6e09fd 285 additionalProperties => 0,
2c3a6c0a 286 properties => {
3a5ae7a0
SI
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'),
4e4c8d40
FG
294 groups => {
295 type => 'array',
72c4589c 296 optional => 1,
4e4c8d40
FG
297 items => {
298 type => 'string',
299 format => 'pve-groupid',
300 },
301 },
302 tokens => {
72c4589c 303 optional => 1,
4e4c8d40
FG
304 type => 'object',
305 },
3a5ae7a0
SI
306 },
307 type => "object"
2c3a6c0a
DM
308 },
309 code => sub {
310 my ($param) = @_;
311
0a6e09fd 312 my ($username, undef, $domain) =
2c3a6c0a
DM
313 PVE::AccessControl::verify_username($param->{userid});
314
315 my $usercfg = cfs_read_file("user.cfg");
2c3a6c0a 316
37d45deb 317 my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
0a6e09fd 318
2c3a6c0a
DM
319 return &$extract_user_data($data, 1);
320 }});
321
322__PACKAGE__->register_method ({
0a6e09fd 323 name => 'update_user',
2c3a6c0a 324 protected => 1,
0a6e09fd 325 path => '{userid}',
2c3a6c0a 326 method => 'PUT',
0a6e09fd 327 permissions => {
96919234
DM
328 check => ['userid-group', ['User.Modify'], groups_param => 1 ],
329 },
2c3a6c0a
DM
330 description => "Update user configuration.",
331 parameters => {
0a6e09fd 332 additionalProperties => 0,
2c3a6c0a 333 properties => {
3a5ae7a0
SI
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'),
0a6e09fd
PA
343 append => {
344 type => 'boolean',
2c3a6c0a
DM
345 optional => 1,
346 requires => 'groups',
347 },
2c3a6c0a
DM
348 },
349 },
350 returns => { type => 'null' },
351 code => sub {
352 my ($param) = @_;
37d45deb 353
0a6e09fd 354 my ($username, $ruid, $realm) =
37d45deb 355 PVE::AccessControl::verify_username($param->{userid});
0a6e09fd 356
2c3a6c0a
DM
357 PVE::AccessControl::lock_user_config(
358 sub {
0a6e09fd 359
2c3a6c0a
DM
360 my $usercfg = cfs_read_file("user.cfg");
361
37d45deb 362 PVE::AccessControl::check_user_exist($usercfg, $username);
2c3a6c0a
DM
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
0a6e09fd 368 PVE::AccessControl::delete_user_group($username, $usercfg)
e6521738 369 if (!$param->{append} && defined($param->{groups}));
2c3a6c0a
DM
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});
96f8ebd6 385 $usercfg->{users}->{$username}->{keys} = $param->{keys} if defined($param->{keys});
2c3a6c0a
DM
386
387 cfs_write_file("user.cfg", $usercfg);
388 }, "update user failed");
0a6e09fd 389
2c3a6c0a
DM
390 return undef;
391 }});
392
393__PACKAGE__->register_method ({
0a6e09fd 394 name => 'delete_user',
2c3a6c0a 395 protected => 1,
0a6e09fd 396 path => '{userid}',
2c3a6c0a
DM
397 method => 'DELETE',
398 description => "Delete user.",
0a6e09fd 399 permissions => {
82b63965
DM
400 check => [ 'and',
401 [ 'userid-param', 'Realm.AllocateUser'],
402 [ 'userid-group', ['User.Modify']],
403 ],
12683df7 404 },
2c3a6c0a 405 parameters => {
0a6e09fd 406 additionalProperties => 0,
2c3a6c0a 407 properties => {
3a5ae7a0 408 userid => get_standard_option('userid-completed'),
2c3a6c0a
DM
409 }
410 },
411 returns => { type => 'null' },
412 code => sub {
413 my ($param) = @_;
0a6e09fd 414
37d45deb
DM
415 my $rpcenv = PVE::RPCEnvironment::get();
416 my $authuser = $rpcenv->get_user();
417
0a6e09fd 418 my ($userid, $ruid, $realm) =
37d45deb 419 PVE::AccessControl::verify_username($param->{userid});
2c3a6c0a
DM
420
421 PVE::AccessControl::lock_user_config(
422 sub {
423
2c3a6c0a
DM
424 my $usercfg = cfs_read_file("user.cfg");
425
5bb4e06a
DM
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 }
2c3a6c0a 431
9536c4dc
WB
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
5bb4e06a 436 delete $usercfg->{users}->{$userid};
37d45deb
DM
437
438 PVE::AccessControl::delete_user_group($userid, $usercfg);
439 PVE::AccessControl::delete_user_acl($userid, $usercfg);
2c3a6c0a
DM
440 cfs_write_file("user.cfg", $usercfg);
441 }, "delete user failed");
0a6e09fd 442
2c3a6c0a
DM
443 return undef;
444 }});
445
e51988b4
DC
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
4e4c8d40
FG
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 },
77bfb48e
TL
602 'full-tokenid' => {
603 type => 'string',
604 format_description => '<userid>!<tokenid>',
605 description => 'The full token id.',
606 },
4e4c8d40
FG
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);
77bfb48e 618 my ($full_tokenid, $value);
4e4c8d40
FG
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
77bfb48e 628 $full_tokenid = PVE::AccessControl::join_tokenid($userid, $tokenid);
4e4c8d40
FG
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
77bfb48e
TL
642 return {
643 info => $token,
644 value => $value,
645 'full-tokenid' => $full_tokenid,
646 };
4e4c8d40
FG
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 }});
2c3a6c0a 7471;