]> git.proxmox.com Git - pve-access-control.git/blame - src/PVE/API2/AccessControl.pm
api: implement openid API
[pve-access-control.git] / src / PVE / API2 / AccessControl.pm
CommitLineData
2c3a6c0a
DM
1package PVE::API2::AccessControl;
2
3use strict;
4use warnings;
5
2b4c98ab
WB
6use JSON;
7use MIME::Base64;
8
e240695b 9use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
2c3a6c0a
DM
10use PVE::SafeSyslog;
11use PVE::RPCEnvironment;
a427cecb 12use PVE::Cluster qw(cfs_read_file);
158514a8 13use PVE::DataCenterConfig;
2c3a6c0a
DM
14use PVE::RESTHandler;
15use PVE::AccessControl;
16use PVE::JSONSchema qw(get_standard_option);
17use PVE::API2::Domains;
18use PVE::API2::User;
19use PVE::API2::Group;
20use PVE::API2::Role;
21use PVE::API2::ACL;
ac344d7d 22use PVE::API2::OpenId;
8967f86f 23use PVE::Auth::Plugin;
47d731c7 24use PVE::OTP;
2c3a6c0a 25
2b4c98ab
WB
26my $u2f_available = 0;
27eval {
28 require PVE::U2F;
29 $u2f_available = 1;
30};
31
2c3a6c0a
DM
32use base qw(PVE::RESTHandler);
33
34__PACKAGE__->register_method ({
30dd4869 35 subclass => "PVE::API2::User",
2c3a6c0a
DM
36 path => 'users',
37});
38
39__PACKAGE__->register_method ({
30dd4869 40 subclass => "PVE::API2::Group",
2c3a6c0a
DM
41 path => 'groups',
42});
43
44__PACKAGE__->register_method ({
30dd4869 45 subclass => "PVE::API2::Role",
2c3a6c0a
DM
46 path => 'roles',
47});
48
49__PACKAGE__->register_method ({
30dd4869 50 subclass => "PVE::API2::ACL",
2c3a6c0a
DM
51 path => 'acl',
52});
53
54__PACKAGE__->register_method ({
30dd4869 55 subclass => "PVE::API2::Domains",
2c3a6c0a
DM
56 path => 'domains',
57});
58
ac344d7d
DM
59__PACKAGE__->register_method ({
60 subclass => "PVE::API2::OpenId",
61 path => 'openid',
62});
63
2c3a6c0a 64__PACKAGE__->register_method ({
30dd4869
TM
65 name => 'index',
66 path => '',
2c3a6c0a
DM
67 method => 'GET',
68 description => "Directory index.",
30dd4869 69 permissions => {
82b63965
DM
70 user => 'all',
71 },
2c3a6c0a
DM
72 parameters => {
73 additionalProperties => 0,
74 properties => {},
75 },
76 returns => {
77 type => 'array',
78 items => {
79 type => "object",
80 properties => {
81 subdir => { type => 'string' },
82 },
83 },
84 links => [ { rel => 'child', href => "{subdir}" } ],
85 },
86 code => sub {
87 my ($param) = @_;
30dd4869 88
2c3a6c0a
DM
89 my $res = [];
90
91 my $ma = __PACKAGE__->method_attributes();
92
93 foreach my $info (@$ma) {
94 next if !$info->{subclass};
95
96 my $subpath = $info->{match_re}->[0];
97
98 push @$res, { subdir => $subpath };
99 }
100
101 push @$res, { subdir => 'ticket' };
37d45deb 102 push @$res, { subdir => 'password' };
2c3a6c0a
DM
103
104 return $res;
105 }});
106
adf8d771
DM
107
108my $verify_auth = sub {
96f8ebd6 109 my ($rpcenv, $username, $pw_or_ticket, $otp, $path, $privs) = @_;
adf8d771
DM
110
111 my $normpath = PVE::AccessControl::normalize_path($path);
112
113 my $ticketuser;
114 if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) &&
115 ($ticketuser eq $username)) {
116 # valid ticket
117 } elsif (PVE::AccessControl::verify_vnc_ticket($pw_or_ticket, $username, $normpath, 1)) {
118 # valid vnc ticket
119 } else {
96f8ebd6 120 $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp);
adf8d771
DM
121 }
122
123 my $privlist = [ PVE::Tools::split_list($privs) ];
124 if (!($normpath && scalar(@$privlist) && $rpcenv->check($username, $normpath, $privlist))) {
125 die "no permission ($path, $privs)\n";
126 }
127
128 return { username => $username };
129};
130
131my $create_ticket = sub {
96f8ebd6 132 my ($rpcenv, $username, $pw_or_ticket, $otp) = @_;
adf8d771 133
f25628d3
WB
134 my ($ticketuser, undef, $tfa_info) = PVE::AccessControl::verify_ticket($pw_or_ticket, 1);
135 if (defined($ticketuser) && ($ticketuser eq 'root@pam' || $ticketuser eq $username)) {
136 if (defined($tfa_info)) {
137 die "incomplete ticket\n";
138 }
adf8d771
DM
139 # valid ticket. Note: root@pam can create tickets for other users
140 } else {
f25628d3 141 ($username, $tfa_info) = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp);
18f8ba18
WB
142 }
143
144 my %extra;
145 my $ticket_data = $username;
f25628d3
WB
146 if (defined($tfa_info)) {
147 $extra{NeedTFA} = 1;
148 if ($tfa_info->{type} eq 'u2f') {
149 my $u2finfo = $tfa_info->{data};
150 my $u2f = get_u2f_instance($rpcenv, $u2finfo->@{qw(publicKey keyHandle)});
151 my $challenge = $u2f->auth_challenge()
152 or die "failed to get u2f challenge\n";
153 $challenge = decode_json($challenge);
154 $extra{U2FChallenge} = $challenge;
155 $ticket_data = "u2f!$username!$challenge->{challenge}";
156 } else {
157 # General half-login / 'missing 2nd factor' ticket:
158 $ticket_data = "tfa!$username";
159 }
adf8d771
DM
160 }
161
18f8ba18 162 my $ticket = PVE::AccessControl::assemble_ticket($ticket_data);
adf8d771
DM
163 my $csrftoken = PVE::AccessControl::assemble_csrf_prevention_token($username);
164
165 return {
166 ticket => $ticket,
167 username => $username,
168 CSRFPreventionToken => $csrftoken,
18f8ba18 169 %extra,
adf8d771
DM
170 };
171};
172
39e4e363 173__PACKAGE__->register_method ({
30dd4869
TM
174 name => 'get_ticket',
175 path => 'ticket',
39e4e363
DM
176 method => 'GET',
177 permissions => { user => 'world' },
36dd9dbd 178 description => "Dummy. Useful for formatters which want to provide a login page.",
39e4e363
DM
179 parameters => {
180 additionalProperties => 0,
181 },
182 returns => { type => "null" },
183 code => sub { return undef; }});
30dd4869 184
2c3a6c0a 185__PACKAGE__->register_method ({
30dd4869
TM
186 name => 'create_ticket',
187 path => 'ticket',
2c3a6c0a 188 method => 'POST',
30dd4869 189 permissions => {
96919234 190 description => "You need to pass valid credientials.",
30dd4869 191 user => 'world'
96919234 192 },
2c3a6c0a 193 protected => 1, # else we can't access shadow files
49372390 194 allowtoken => 0, # we don't want tokens to create tickets
adf8d771 195 description => "Create or verify authentication ticket.",
2c3a6c0a
DM
196 parameters => {
197 additionalProperties => 0,
198 properties => {
199 username => {
3a5ae7a0
SI
200 description => "User name",
201 type => 'string',
202 maxLength => 64,
203 completion => \&PVE::AccessControl::complete_username,
2c3a6c0a
DM
204 },
205 realm => get_standard_option('realm', {
206 description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.",
3e5bfdf6
DM
207 optional => 1,
208 completion => \&PVE::AccessControl::complete_realm,
209 }),
30dd4869 210 password => {
2c3a6c0a
DM
211 description => "The secret password. This can also be a valid ticket.",
212 type => 'string',
213 },
96f8ebd6
DM
214 otp => {
215 description => "One-time password for Two-factor authentication.",
216 type => 'string',
217 optional => 1,
218 },
2c3a6c0a 219 path => {
adf8d771 220 description => "Verify ticket, and check if user have access 'privs' on 'path'",
2c3a6c0a
DM
221 type => 'string',
222 requires => 'privs',
223 optional => 1,
224 maxLength => 64,
225 },
30dd4869 226 privs => {
adf8d771 227 description => "Verify ticket, and check if user have access 'privs' on 'path'",
2c3a6c0a
DM
228 type => 'string' , format => 'pve-priv-list',
229 requires => 'path',
230 optional => 1,
231 maxLength => 64,
232 },
233 }
234 },
235 returns => {
236 type => "object",
237 properties => {
2c3a6c0a 238 username => { type => 'string' },
adf8d771
DM
239 ticket => { type => 'string', optional => 1},
240 CSRFPreventionToken => { type => 'string', optional => 1 },
e842fec5 241 clustername => { type => 'string', optional => 1 },
18f8ba18 242 # cap => computed api permissions, unless there's a u2f challenge
2c3a6c0a
DM
243 }
244 },
245 code => sub {
246 my ($param) = @_;
30dd4869 247
2c3a6c0a
DM
248 my $username = $param->{username};
249 $username .= "\@$param->{realm}" if $param->{realm};
250
eb41d200 251 $username = PVE::AccessControl::lookup_username($username);
2c3a6c0a 252 my $rpcenv = PVE::RPCEnvironment::get();
2c3a6c0a 253
adf8d771 254 my $res;
adf8d771 255 eval {
7070c1ae
DM
256 # test if user exists and is enabled
257 $rpcenv->check_user_enabled($username);
258
2c3a6c0a 259 if ($param->{path} && $param->{privs}) {
96f8ebd6 260 $res = &$verify_auth($rpcenv, $username, $param->{password}, $param->{otp},
adf8d771 261 $param->{path}, $param->{privs});
2c3a6c0a 262 } else {
96f8ebd6 263 $res = &$create_ticket($rpcenv, $username, $param->{password}, $param->{otp});
2c3a6c0a 264 }
2c3a6c0a
DM
265 };
266 if (my $err = $@) {
adf8d771 267 my $clientip = $rpcenv->get_client_ip() || '';
2c3a6c0a 268 syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
6126ab75 269 # do not return any info to prevent user enumeration attacks
fe2defd9 270 die PVE::Exception->new("authentication failure\n", code => 401);
2c3a6c0a
DM
271 }
272
ac344d7d 273 $res->{cap} = $rpcenv->compute_api_permission($username)
f25628d3 274 if !defined($res->{NeedTFA});
dd2cfee0 275
0fb0c62d
FG
276 my $clinfo = PVE::Cluster::get_clinfo();
277 if ($clinfo->{cluster}->{name} && $rpcenv->check($username, '/', ['Sys.Audit'], 1)) {
278 $res->{clustername} = $clinfo->{cluster}->{name};
e842fec5
TL
279 }
280
2c3a6c0a
DM
281 PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
282
adf8d771 283 return $res;
2c3a6c0a
DM
284 }});
285
37d45deb 286__PACKAGE__->register_method ({
765305e2 287 name => 'change_password',
30dd4869 288 path => 'password',
37d45deb 289 method => 'PUT',
30dd4869 290 permissions => {
82b63965 291 description => "Each user is allowed to change his own password. A user can change the password of another user if he has 'Realm.AllocateUser' (on the realm of user <userid>) and 'User.Modify' permission on /access/groups/<group> on a group where user <userid> is member of.",
30dd4869 292 check => [ 'or',
12683df7 293 ['userid-param', 'self'],
82b63965
DM
294 [ 'and',
295 [ 'userid-param', 'Realm.AllocateUser'],
296 [ 'userid-group', ['User.Modify']]
297 ]
12683df7
DM
298 ],
299 },
37d45deb 300 protected => 1, # else we can't access shadow files
49372390 301 allowtoken => 0, # we don't want tokens to change the regular user password
37d45deb
DM
302 description => "Change user password.",
303 parameters => {
304 additionalProperties => 0,
305 properties => {
3a5ae7a0 306 userid => get_standard_option('userid-completed'),
30dd4869 307 password => {
37d45deb
DM
308 description => "The new password.",
309 type => 'string',
30dd4869 310 minLength => 5,
37d45deb
DM
311 maxLength => 64,
312 },
313 }
314 },
315 returns => { type => "null" },
316 code => sub {
317 my ($param) = @_;
318
319 my $rpcenv = PVE::RPCEnvironment::get();
320 my $authuser = $rpcenv->get_user();
321
322 my ($userid, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
323
12683df7 324 $rpcenv->check_user_exist($userid);
37d45deb
DM
325
326 if ($authuser eq 'root@pam') {
327 # OK - root can change anything
328 } else {
329 if ($authuser eq $userid) {
330 $rpcenv->check_user_enabled($userid);
331 # OK - each user can change its own password
332 } else {
12683df7 333 # only root may change root password
37d45deb 334 raise_perm_exc() if $userid eq 'root@pam';
59321f26
DM
335 # do not allow to change system user passwords
336 raise_perm_exc() if $realm eq 'pam';
37d45deb
DM
337 }
338 }
339
340 PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password});
341
342 PVE::Cluster::log_msg('info', 'root@pam', "changed password for user '$userid'");
343
344 return undef;
345 }});
346
2b4c98ab
WB
347sub get_u2f_config() {
348 die "u2f support not available\n" if !$u2f_available;
349
350 my $dc = cfs_read_file('datacenter.cfg');
351 my $u2f = $dc->{u2f};
352 die "u2f not configured in datacenter.cfg\n" if !$u2f;
2b4c98ab
WB
353 return $u2f;
354}
355
356sub get_u2f_instance {
357 my ($rpcenv, $publicKey, $keyHandle) = @_;
358
359 # We store the public key base64 encoded (as the api provides it in binary)
360 $publicKey = decode_base64($publicKey) if defined($publicKey);
361
362 my $u2fconfig = get_u2f_config();
363 my $u2f = PVE::U2F->new();
364
365 # via the 'Host' header (in case a node has multiple hosts available).
366 my $origin = $u2fconfig->{origin};
367 if (!defined($origin)) {
368 $origin = $rpcenv->get_request_host(1);
369 if ($origin) {
370 $origin = "https://$origin";
371 } else {
372 die "failed to figure out u2f origin\n";
373 }
374 }
375
376 my $appid = $u2fconfig->{appid} // $origin;
377 $u2f->set_appid($appid);
378 $u2f->set_origin($origin);
379 $u2f->set_publicKey($publicKey) if defined($publicKey);
380 $u2f->set_keyHandle($keyHandle) if defined($keyHandle);
381 return $u2f;
382}
383
47d731c7
WB
384sub verify_user_tfa_config {
385 my ($type, $tfa_cfg, $value) = @_;
386
387 if (!defined($type)) {
388 die "missing tfa 'type'\n";
389 }
390
391 if ($type ne 'oath') {
392 die "invalid type for custom tfa authentication\n";
393 }
394
395 my $secret = $tfa_cfg->{keys}
396 or die "missing TOTP secret\n";
397 $tfa_cfg = $tfa_cfg->{config};
398 # Copy the hash to verify that we have no unexpected keys without modifying the original hash.
399 $tfa_cfg = {%$tfa_cfg};
400
401 # We can only verify 1 secret but oath_verify_otp allows multiple:
402 if (scalar(PVE::Tools::split_list($secret)) != 1) {
403 die "only exactly one secret key allowed\n";
404 }
405
406 my $digits = delete($tfa_cfg->{digits}) // 6;
407 my $step = delete($tfa_cfg->{step}) // 30;
408 # Maybe also this?
409 # my $algorithm = delete($tfa_cfg->{algorithm}) // 'sha1';
410
411 if (length(my $more = join(', ', keys %$tfa_cfg))) {
412 die "unexpected tfa config keys: $more\n";
413 }
414
415 PVE::OTP::oath_verify_otp($value, $secret, $step, $digits);
416}
417
2b4c98ab
WB
418__PACKAGE__->register_method ({
419 name => 'change_tfa',
420 path => 'tfa',
421 method => 'PUT',
422 permissions => {
47d731c7 423 description => 'A user can change their own u2f or totp token.',
2b4c98ab
WB
424 check => [ 'or',
425 ['userid-param', 'self'],
426 [ 'and',
427 [ 'userid-param', 'Realm.AllocateUser'],
428 [ 'userid-group', ['User.Modify']]
429 ]
430 ],
431 },
432 protected => 1, # else we can't access shadow files
49372390 433 allowtoken => 0, # we don't want tokens to change the regular user's TFA settings
2b4c98ab
WB
434 description => "Change user u2f authentication.",
435 parameters => {
436 additionalProperties => 0,
437 properties => {
438 userid => get_standard_option('userid', {
439 completion => \&PVE::AccessControl::complete_username,
440 }),
441 password => {
442 optional => 1, # Only required if not root@pam
443 description => "The current password.",
444 type => 'string',
445 minLength => 5,
446 maxLength => 64,
447 },
448 action => {
449 description => 'The action to perform',
450 type => 'string',
451 enum => [qw(delete new confirm)],
452 },
453 response => {
454 optional => 1,
47d731c7
WB
455 description =>
456 'Either the the response to the current u2f registration challenge,'
457 .' or, when adding TOTP, the currently valid TOTP value.',
458 type => 'string',
459 },
460 key => {
461 optional => 1,
462 description => 'When adding TOTP, the shared secret value.',
2b4c98ab 463 type => 'string',
0bf114df 464 format => 'pve-tfa-secret',
47d731c7
WB
465 },
466 config => {
467 optional => 1,
468 description => 'A TFA configuration. This must currently be of type TOTP of not set at all.',
469 type => 'string',
470 format => 'pve-tfa-config',
471 maxLength => 128,
2b4c98ab
WB
472 },
473 }
474 },
475 returns => { type => 'object' },
476 code => sub {
477 my ($param) = @_;
478
479 my $rpcenv = PVE::RPCEnvironment::get();
480 my $authuser = $rpcenv->get_user();
481
482 my $action = delete $param->{action};
483 my $response = delete $param->{response};
484 my $password = delete($param->{password}) // '';
47d731c7
WB
485 my $key = delete($param->{key});
486 my $config = delete($param->{config});
2b4c98ab
WB
487
488 my ($userid, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
489 $rpcenv->check_user_exist($userid);
490
491 # Only root may modify root
492 raise_perm_exc() if $userid eq 'root@pam' && $authuser ne 'root@pam';
493
494 # Regular users need to confirm their password to change u2f settings.
495 if ($authuser ne 'root@pam') {
e240695b 496 raise_param_exc({ 'password' => 'password is required to modify u2f data' })
2b4c98ab
WB
497 if !defined($password);
498 my $domain_cfg = cfs_read_file('domains.cfg');
499 my $cfg = $domain_cfg->{ids}->{$realm};
3443faca 500 die "auth domain '$realm' does not exist\n" if !$cfg;
2b4c98ab
WB
501 my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
502 $plugin->authenticate_user($cfg, $realm, $ruid, $password);
503 }
504
505 if ($action eq 'delete') {
506 PVE::AccessControl::user_set_tfa($userid, $realm, undef, undef);
507 PVE::Cluster::log_msg('info', $authuser, "deleted u2f data for user '$userid'");
508 } elsif ($action eq 'new') {
47d731c7
WB
509 if (defined($config)) {
510 $config = PVE::Auth::Plugin::parse_tfa_config($config);
511 my $type = delete($config->{type});
512 my $tfa_cfg = {
513 keys => $key,
514 config => $config,
515 };
516 verify_user_tfa_config($type, $tfa_cfg, $response);
517 PVE::AccessControl::user_set_tfa($userid, $realm, $type, $tfa_cfg);
518 } else {
519 # The default is U2F:
520 my $u2f = get_u2f_instance($rpcenv);
521 my $challenge = $u2f->registration_challenge()
522 or raise("failed to get u2f challenge");
523 $challenge = decode_json($challenge);
524 PVE::AccessControl::user_set_tfa($userid, $realm, 'u2f', $challenge);
525 return $challenge;
526 }
2b4c98ab 527 } elsif ($action eq 'confirm') {
e240695b 528 raise_param_exc({ 'response' => "confirm action requires the 'response' parameter to be set" })
2b4c98ab
WB
529 if !defined($response);
530
531 my ($type, $u2fdata) = PVE::AccessControl::user_get_tfa($userid, $realm);
532 raise("no u2f data available")
533 if (!defined($type) || $type ne 'u2f');
534
535 my $challenge = $u2fdata->{challenge}
536 or raise("no active challenge");
537
538 my $u2f = get_u2f_instance($rpcenv);
539 $u2f->set_challenge($challenge);
540 my ($keyHandle, $publicKey) = $u2f->registration_verify($response);
541 PVE::AccessControl::user_set_tfa($userid, $realm, 'u2f', {
542 keyHandle => $keyHandle,
eb25cbaf 543 publicKey => $publicKey, # already base64 encoded
2b4c98ab
WB
544 });
545 } else {
546 die "invalid action: $action\n";
547 }
548
549 return {};
550 }});
551
552__PACKAGE__->register_method({
553 name => 'verify_tfa',
554 path => 'tfa',
555 method => 'POST',
556 permissions => { user => 'all' },
557 protected => 1, # else we can't access shadow files
49372390 558 allowtoken => 0, # we don't want tokens to access TFA information
2b4c98ab
WB
559 description => 'Finish a u2f challenge.',
560 parameters => {
561 additionalProperties => 0,
562 properties => {
563 response => {
564 type => 'string',
565 description => 'The response to the current authentication challenge.',
566 },
567 }
568 },
569 returns => {
570 type => 'object',
571 properties => {
572 ticket => { type => 'string' },
573 # cap
574 }
575 },
576 code => sub {
577 my ($param) = @_;
578
579 my $rpcenv = PVE::RPCEnvironment::get();
2b4c98ab
WB
580 my $authuser = $rpcenv->get_user();
581 my ($username, undef, $realm) = PVE::AccessControl::verify_username($authuser);
582
f25628d3
WB
583 my ($tfa_type, $tfa_data) = PVE::AccessControl::user_get_tfa($username, $realm);
584 if (!defined($tfa_type)) {
2b4c98ab
WB
585 raise('no u2f data available');
586 }
587
f25628d3
WB
588 eval {
589 if ($tfa_type eq 'u2f') {
590 my $challenge = $rpcenv->get_u2f_challenge()
591 or raise('no active challenge');
2b4c98ab 592
f25628d3
WB
593 my $keyHandle = $tfa_data->{keyHandle};
594 my $publicKey = $tfa_data->{publicKey};
595 raise("incomplete u2f setup")
596 if !defined($keyHandle) || !defined($publicKey);
2b4c98ab 597
f25628d3
WB
598 my $u2f = get_u2f_instance($rpcenv, $publicKey, $keyHandle);
599 $u2f->set_challenge($challenge);
600
601 my ($counter, $present) = $u2f->auth_verify($param->{response});
602 # Do we want to do anything with these?
603 } else {
604 # sanity check before handing off to the verification code:
605 my $keys = $tfa_data->{keys} or die "missing tfa keys\n";
606 my $config = $tfa_data->{config} or die "bad tfa entry\n";
607 PVE::AccessControl::verify_one_time_pw($tfa_type, $authuser, $keys, $config, $param->{response});
608 }
2b4c98ab
WB
609 };
610 if (my $err = $@) {
611 my $clientip = $rpcenv->get_client_ip() || '';
612 syslog('err', "authentication verification failure; rhost=$clientip user=$authuser msg=$err");
613 die PVE::Exception->new("authentication failure\n", code => 401);
614 }
615
2b4c98ab 616 return {
f25628d3 617 ticket => PVE::AccessControl::assemble_ticket($authuser),
ac344d7d 618 cap => $rpcenv->compute_api_permission($authuser),
2b4c98ab
WB
619 }
620 }});
621
c3fa8a36
FG
622__PACKAGE__->register_method({
623 name => 'permissions',
624 path => 'permissions',
625 method => 'GET',
626 description => 'Retrieve effective permissions of given user/token.',
627 permissions => {
628 description => "Each user/token is allowed to dump their own permissions. A user can dump the permissions of another user if they have 'Sys.Audit' permission on /access.",
629 user => 'all',
630 },
631 parameters => {
632 additionalProperties => 0,
633 properties => {
634 userid => {
635 type => 'string',
636 description => "User ID or full API token ID",
637 pattern => $PVE::AccessControl::userid_or_token_regex,
638 optional => 1,
639 },
640 path => get_standard_option('acl-path', {
641 description => "Only dump this specific path, not the whole tree.",
642 optional => 1,
643 }),
644 },
645 },
646 returns => {
647 type => 'object',
648 description => 'Map of "path" => (Map of "privilege" => "propagate boolean").',
649 },
650 code => sub {
651 my ($param) = @_;
652
653 my $rpcenv = PVE::RPCEnvironment::get();
654
655 my $userid = $param->{userid};
656 if (defined($userid)) {
657 $rpcenv->check($rpcenv->get_user(), '/access', ['Sys.Audit']);
658 } else {
659 $userid = $rpcenv->get_user();
660 }
661
662 my $res;
663
664 if (my $path = $param->{path}) {
665 my $perms = $rpcenv->permissions($userid, $path);
666 if ($perms) {
667 $res = { $path => $perms };
668 } else {
669 $res = {};
670 }
671 } else {
672 $res = $rpcenv->get_effective_permissions($userid);
673 }
674
675 return $res;
676 }});
677
2c3a6c0a 6781;