]>
Commit | Line | Data |
---|---|---|
2c3a6c0a DM |
1 | package PVE::API2::AccessControl; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
2b4c98ab WB |
6 | use JSON; |
7 | use MIME::Base64; | |
8 | ||
37d45deb | 9 | use PVE::Exception qw(raise raise_perm_exc); |
2c3a6c0a DM |
10 | use PVE::SafeSyslog; |
11 | use PVE::RPCEnvironment; | |
a427cecb | 12 | use PVE::Cluster qw(cfs_read_file); |
e842fec5 | 13 | use PVE::Corosync; |
2c3a6c0a DM |
14 | use PVE::RESTHandler; |
15 | use PVE::AccessControl; | |
16 | use PVE::JSONSchema qw(get_standard_option); | |
17 | use PVE::API2::Domains; | |
18 | use PVE::API2::User; | |
19 | use PVE::API2::Group; | |
20 | use PVE::API2::Role; | |
21 | use PVE::API2::ACL; | |
22 | ||
2b4c98ab WB |
23 | my $u2f_available = 0; |
24 | eval { | |
25 | require PVE::U2F; | |
26 | $u2f_available = 1; | |
27 | }; | |
28 | ||
2c3a6c0a DM |
29 | use base qw(PVE::RESTHandler); |
30 | ||
31 | __PACKAGE__->register_method ({ | |
32 | subclass => "PVE::API2::User", | |
33 | path => 'users', | |
34 | }); | |
35 | ||
36 | __PACKAGE__->register_method ({ | |
37 | subclass => "PVE::API2::Group", | |
38 | path => 'groups', | |
39 | }); | |
40 | ||
41 | __PACKAGE__->register_method ({ | |
42 | subclass => "PVE::API2::Role", | |
43 | path => 'roles', | |
44 | }); | |
45 | ||
46 | __PACKAGE__->register_method ({ | |
47 | subclass => "PVE::API2::ACL", | |
48 | path => 'acl', | |
49 | }); | |
50 | ||
51 | __PACKAGE__->register_method ({ | |
52 | subclass => "PVE::API2::Domains", | |
53 | path => 'domains', | |
54 | }); | |
55 | ||
56 | __PACKAGE__->register_method ({ | |
57 | name => 'index', | |
58 | path => '', | |
59 | method => 'GET', | |
60 | description => "Directory index.", | |
82b63965 DM |
61 | permissions => { |
62 | user => 'all', | |
63 | }, | |
2c3a6c0a DM |
64 | parameters => { |
65 | additionalProperties => 0, | |
66 | properties => {}, | |
67 | }, | |
68 | returns => { | |
69 | type => 'array', | |
70 | items => { | |
71 | type => "object", | |
72 | properties => { | |
73 | subdir => { type => 'string' }, | |
74 | }, | |
75 | }, | |
76 | links => [ { rel => 'child', href => "{subdir}" } ], | |
77 | }, | |
78 | code => sub { | |
79 | my ($param) = @_; | |
80 | ||
81 | my $res = []; | |
82 | ||
83 | my $ma = __PACKAGE__->method_attributes(); | |
84 | ||
85 | foreach my $info (@$ma) { | |
86 | next if !$info->{subclass}; | |
87 | ||
88 | my $subpath = $info->{match_re}->[0]; | |
89 | ||
90 | push @$res, { subdir => $subpath }; | |
91 | } | |
92 | ||
93 | push @$res, { subdir => 'ticket' }; | |
37d45deb | 94 | push @$res, { subdir => 'password' }; |
2c3a6c0a DM |
95 | |
96 | return $res; | |
97 | }}); | |
98 | ||
adf8d771 DM |
99 | |
100 | my $verify_auth = sub { | |
96f8ebd6 | 101 | my ($rpcenv, $username, $pw_or_ticket, $otp, $path, $privs) = @_; |
adf8d771 DM |
102 | |
103 | my $normpath = PVE::AccessControl::normalize_path($path); | |
104 | ||
105 | my $ticketuser; | |
106 | if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) && | |
107 | ($ticketuser eq $username)) { | |
108 | # valid ticket | |
109 | } elsif (PVE::AccessControl::verify_vnc_ticket($pw_or_ticket, $username, $normpath, 1)) { | |
110 | # valid vnc ticket | |
111 | } else { | |
96f8ebd6 | 112 | $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp); |
adf8d771 DM |
113 | } |
114 | ||
115 | my $privlist = [ PVE::Tools::split_list($privs) ]; | |
116 | if (!($normpath && scalar(@$privlist) && $rpcenv->check($username, $normpath, $privlist))) { | |
117 | die "no permission ($path, $privs)\n"; | |
118 | } | |
119 | ||
120 | return { username => $username }; | |
121 | }; | |
122 | ||
123 | my $create_ticket = sub { | |
96f8ebd6 | 124 | my ($rpcenv, $username, $pw_or_ticket, $otp) = @_; |
adf8d771 DM |
125 | |
126 | my $ticketuser; | |
127 | if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) && | |
128 | ($ticketuser eq 'root@pam' || $ticketuser eq $username)) { | |
129 | # valid ticket. Note: root@pam can create tickets for other users | |
130 | } else { | |
96f8ebd6 | 131 | $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket, $otp); |
adf8d771 DM |
132 | } |
133 | ||
134 | my $ticket = PVE::AccessControl::assemble_ticket($username); | |
135 | my $csrftoken = PVE::AccessControl::assemble_csrf_prevention_token($username); | |
136 | ||
137 | return { | |
138 | ticket => $ticket, | |
139 | username => $username, | |
140 | CSRFPreventionToken => $csrftoken, | |
141 | }; | |
142 | }; | |
143 | ||
dd2cfee0 DM |
144 | my $compute_api_permission = sub { |
145 | my ($rpcenv, $authuser) = @_; | |
146 | ||
147 | my $usercfg = $rpcenv->{user_cfg}; | |
148 | ||
a2c18811 TL |
149 | my $res = {}; |
150 | my $priv_re_map = { | |
151 | vms => qr/VM\.|Permissions\.Modify/, | |
152 | access => qr/(User|Group)\.|Permissions\.Modify/, | |
f5848089 | 153 | storage => qr/Datastore\.|Permissions\.Modify/, |
a2c18811 TL |
154 | nodes => qr/Sys\.|Permissions\.Modify/, |
155 | dc => qr/Sys\.Audit/, | |
dd2cfee0 | 156 | }; |
a2c18811 TL |
157 | map { $res->{$_} = {} } keys %$priv_re_map; |
158 | ||
159 | my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage']; | |
160 | ||
161 | my $checked_paths = {}; | |
162 | foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) { | |
163 | next if $checked_paths->{$path}; | |
164 | $checked_paths->{$path} = 1; | |
165 | ||
166 | my $path_perm = $rpcenv->permissions($authuser, $path); | |
167 | ||
168 | my $toplevel = ($path =~ /^\/(\w+)/) ? $1 : 'dc'; | |
169 | if ($toplevel eq 'pool') { | |
170 | foreach my $priv (keys %$path_perm) { | |
171 | if ($priv =~ m/^VM\./) { | |
172 | $res->{vms}->{$priv} = 1; | |
173 | } elsif ($priv =~ m/^Datastore\./) { | |
174 | $res->{storage}->{$priv} = 1; | |
175 | } elsif ($priv eq 'Permissions.Modify') { | |
176 | $res->{storage}->{$priv} = 1; | |
177 | $res->{vms}->{$priv} = 1; | |
178 | } | |
179 | } | |
180 | } else { | |
181 | my $priv_regex = $priv_re_map->{$toplevel} // next; | |
182 | foreach my $priv (keys %$path_perm) { | |
183 | next if $priv !~ m/^($priv_regex)/; | |
184 | $res->{$toplevel}->{$priv} = 1; | |
185 | } | |
dd2cfee0 DM |
186 | } |
187 | } | |
188 | ||
dd2cfee0 DM |
189 | return $res; |
190 | }; | |
191 | ||
39e4e363 DM |
192 | __PACKAGE__->register_method ({ |
193 | name => 'get_ticket', | |
194 | path => 'ticket', | |
195 | method => 'GET', | |
196 | permissions => { user => 'world' }, | |
36dd9dbd | 197 | description => "Dummy. Useful for formatters which want to provide a login page.", |
39e4e363 DM |
198 | parameters => { |
199 | additionalProperties => 0, | |
200 | }, | |
201 | returns => { type => "null" }, | |
202 | code => sub { return undef; }}); | |
203 | ||
2c3a6c0a DM |
204 | __PACKAGE__->register_method ({ |
205 | name => 'create_ticket', | |
206 | path => 'ticket', | |
207 | method => 'POST', | |
96919234 DM |
208 | permissions => { |
209 | description => "You need to pass valid credientials.", | |
210 | user => 'world' | |
211 | }, | |
2c3a6c0a | 212 | protected => 1, # else we can't access shadow files |
adf8d771 | 213 | description => "Create or verify authentication ticket.", |
2c3a6c0a DM |
214 | parameters => { |
215 | additionalProperties => 0, | |
216 | properties => { | |
217 | username => { | |
3a5ae7a0 SI |
218 | description => "User name", |
219 | type => 'string', | |
220 | maxLength => 64, | |
221 | completion => \&PVE::AccessControl::complete_username, | |
2c3a6c0a DM |
222 | }, |
223 | realm => get_standard_option('realm', { | |
224 | description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.", | |
3e5bfdf6 DM |
225 | optional => 1, |
226 | completion => \&PVE::AccessControl::complete_realm, | |
227 | }), | |
2c3a6c0a DM |
228 | password => { |
229 | description => "The secret password. This can also be a valid ticket.", | |
230 | type => 'string', | |
231 | }, | |
96f8ebd6 DM |
232 | otp => { |
233 | description => "One-time password for Two-factor authentication.", | |
234 | type => 'string', | |
235 | optional => 1, | |
236 | }, | |
2c3a6c0a | 237 | path => { |
adf8d771 | 238 | description => "Verify ticket, and check if user have access 'privs' on 'path'", |
2c3a6c0a DM |
239 | type => 'string', |
240 | requires => 'privs', | |
241 | optional => 1, | |
242 | maxLength => 64, | |
243 | }, | |
244 | privs => { | |
adf8d771 | 245 | description => "Verify ticket, and check if user have access 'privs' on 'path'", |
2c3a6c0a DM |
246 | type => 'string' , format => 'pve-priv-list', |
247 | requires => 'path', | |
248 | optional => 1, | |
249 | maxLength => 64, | |
250 | }, | |
251 | } | |
252 | }, | |
253 | returns => { | |
254 | type => "object", | |
255 | properties => { | |
2c3a6c0a | 256 | username => { type => 'string' }, |
adf8d771 DM |
257 | ticket => { type => 'string', optional => 1}, |
258 | CSRFPreventionToken => { type => 'string', optional => 1 }, | |
e842fec5 | 259 | clustername => { type => 'string', optional => 1 }, |
2c3a6c0a DM |
260 | } |
261 | }, | |
262 | code => sub { | |
263 | my ($param) = @_; | |
264 | ||
265 | my $username = $param->{username}; | |
266 | $username .= "\@$param->{realm}" if $param->{realm}; | |
267 | ||
268 | my $rpcenv = PVE::RPCEnvironment::get(); | |
2c3a6c0a | 269 | |
adf8d771 | 270 | my $res; |
adf8d771 | 271 | eval { |
7070c1ae DM |
272 | # test if user exists and is enabled |
273 | $rpcenv->check_user_enabled($username); | |
274 | ||
2c3a6c0a | 275 | if ($param->{path} && $param->{privs}) { |
96f8ebd6 | 276 | $res = &$verify_auth($rpcenv, $username, $param->{password}, $param->{otp}, |
adf8d771 | 277 | $param->{path}, $param->{privs}); |
2c3a6c0a | 278 | } else { |
96f8ebd6 | 279 | $res = &$create_ticket($rpcenv, $username, $param->{password}, $param->{otp}); |
2c3a6c0a | 280 | } |
2c3a6c0a DM |
281 | }; |
282 | if (my $err = $@) { | |
adf8d771 | 283 | my $clientip = $rpcenv->get_client_ip() || ''; |
2c3a6c0a | 284 | syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err"); |
6126ab75 | 285 | # do not return any info to prevent user enumeration attacks |
fe2defd9 | 286 | die PVE::Exception->new("authentication failure\n", code => 401); |
2c3a6c0a DM |
287 | } |
288 | ||
dd2cfee0 DM |
289 | $res->{cap} = &$compute_api_permission($rpcenv, $username); |
290 | ||
e842fec5 TL |
291 | if (PVE::Corosync::check_conf_exists(1)) { |
292 | if ($rpcenv->check($username, '/', ['Sys.Audit'], 1)) { | |
b27ae8aa TL |
293 | eval { |
294 | my $conf = cfs_read_file('corosync.conf'); | |
295 | my $totem = PVE::Corosync::totem_config($conf); | |
296 | if ($totem->{cluster_name}) { | |
297 | $res->{clustername} = $totem->{cluster_name}; | |
298 | } | |
299 | }; | |
300 | warn "$@\n" if $@; | |
e842fec5 TL |
301 | } |
302 | } | |
303 | ||
2c3a6c0a DM |
304 | PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'"); |
305 | ||
adf8d771 | 306 | return $res; |
2c3a6c0a DM |
307 | }}); |
308 | ||
37d45deb | 309 | __PACKAGE__->register_method ({ |
765305e2 | 310 | name => 'change_password', |
37d45deb DM |
311 | path => 'password', |
312 | method => 'PUT', | |
12683df7 | 313 | permissions => { |
82b63965 | 314 | 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.", |
12683df7 DM |
315 | check => [ 'or', |
316 | ['userid-param', 'self'], | |
82b63965 DM |
317 | [ 'and', |
318 | [ 'userid-param', 'Realm.AllocateUser'], | |
319 | [ 'userid-group', ['User.Modify']] | |
320 | ] | |
12683df7 DM |
321 | ], |
322 | }, | |
37d45deb DM |
323 | protected => 1, # else we can't access shadow files |
324 | description => "Change user password.", | |
325 | parameters => { | |
326 | additionalProperties => 0, | |
327 | properties => { | |
3a5ae7a0 | 328 | userid => get_standard_option('userid-completed'), |
37d45deb DM |
329 | password => { |
330 | description => "The new password.", | |
331 | type => 'string', | |
332 | minLength => 5, | |
333 | maxLength => 64, | |
334 | }, | |
335 | } | |
336 | }, | |
337 | returns => { type => "null" }, | |
338 | code => sub { | |
339 | my ($param) = @_; | |
340 | ||
341 | my $rpcenv = PVE::RPCEnvironment::get(); | |
342 | my $authuser = $rpcenv->get_user(); | |
343 | ||
344 | my ($userid, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid}); | |
345 | ||
12683df7 | 346 | $rpcenv->check_user_exist($userid); |
37d45deb DM |
347 | |
348 | if ($authuser eq 'root@pam') { | |
349 | # OK - root can change anything | |
350 | } else { | |
351 | if ($authuser eq $userid) { | |
352 | $rpcenv->check_user_enabled($userid); | |
353 | # OK - each user can change its own password | |
354 | } else { | |
12683df7 | 355 | # only root may change root password |
37d45deb | 356 | raise_perm_exc() if $userid eq 'root@pam'; |
59321f26 DM |
357 | # do not allow to change system user passwords |
358 | raise_perm_exc() if $realm eq 'pam'; | |
37d45deb DM |
359 | } |
360 | } | |
361 | ||
362 | PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password}); | |
363 | ||
364 | PVE::Cluster::log_msg('info', 'root@pam', "changed password for user '$userid'"); | |
365 | ||
366 | return undef; | |
367 | }}); | |
368 | ||
2b4c98ab WB |
369 | sub get_u2f_config() { |
370 | die "u2f support not available\n" if !$u2f_available; | |
371 | ||
372 | my $dc = cfs_read_file('datacenter.cfg'); | |
373 | my $u2f = $dc->{u2f}; | |
374 | die "u2f not configured in datacenter.cfg\n" if !$u2f; | |
375 | $u2f = PVE::JSONSchema::parse_property_string($PVE::Cluster::u2f_format, $u2f); | |
376 | return $u2f; | |
377 | } | |
378 | ||
379 | sub get_u2f_instance { | |
380 | my ($rpcenv, $publicKey, $keyHandle) = @_; | |
381 | ||
382 | # We store the public key base64 encoded (as the api provides it in binary) | |
383 | $publicKey = decode_base64($publicKey) if defined($publicKey); | |
384 | ||
385 | my $u2fconfig = get_u2f_config(); | |
386 | my $u2f = PVE::U2F->new(); | |
387 | ||
388 | # via the 'Host' header (in case a node has multiple hosts available). | |
389 | my $origin = $u2fconfig->{origin}; | |
390 | if (!defined($origin)) { | |
391 | $origin = $rpcenv->get_request_host(1); | |
392 | if ($origin) { | |
393 | $origin = "https://$origin"; | |
394 | } else { | |
395 | die "failed to figure out u2f origin\n"; | |
396 | } | |
397 | } | |
398 | ||
399 | my $appid = $u2fconfig->{appid} // $origin; | |
400 | $u2f->set_appid($appid); | |
401 | $u2f->set_origin($origin); | |
402 | $u2f->set_publicKey($publicKey) if defined($publicKey); | |
403 | $u2f->set_keyHandle($keyHandle) if defined($keyHandle); | |
404 | return $u2f; | |
405 | } | |
406 | ||
407 | __PACKAGE__->register_method ({ | |
408 | name => 'change_tfa', | |
409 | path => 'tfa', | |
410 | method => 'PUT', | |
411 | permissions => { | |
412 | description => 'A user can change their own u2f token.', | |
413 | check => [ 'or', | |
414 | ['userid-param', 'self'], | |
415 | [ 'and', | |
416 | [ 'userid-param', 'Realm.AllocateUser'], | |
417 | [ 'userid-group', ['User.Modify']] | |
418 | ] | |
419 | ], | |
420 | }, | |
421 | protected => 1, # else we can't access shadow files | |
422 | description => "Change user u2f authentication.", | |
423 | parameters => { | |
424 | additionalProperties => 0, | |
425 | properties => { | |
426 | userid => get_standard_option('userid', { | |
427 | completion => \&PVE::AccessControl::complete_username, | |
428 | }), | |
429 | password => { | |
430 | optional => 1, # Only required if not root@pam | |
431 | description => "The current password.", | |
432 | type => 'string', | |
433 | minLength => 5, | |
434 | maxLength => 64, | |
435 | }, | |
436 | action => { | |
437 | description => 'The action to perform', | |
438 | type => 'string', | |
439 | enum => [qw(delete new confirm)], | |
440 | }, | |
441 | response => { | |
442 | optional => 1, | |
443 | description => 'The response to the current registration challenge.', | |
444 | type => 'string', | |
445 | }, | |
446 | } | |
447 | }, | |
448 | returns => { type => 'object' }, | |
449 | code => sub { | |
450 | my ($param) = @_; | |
451 | ||
452 | my $rpcenv = PVE::RPCEnvironment::get(); | |
453 | my $authuser = $rpcenv->get_user(); | |
454 | ||
455 | my $action = delete $param->{action}; | |
456 | my $response = delete $param->{response}; | |
457 | my $password = delete($param->{password}) // ''; | |
458 | ||
459 | my ($userid, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid}); | |
460 | $rpcenv->check_user_exist($userid); | |
461 | ||
462 | # Only root may modify root | |
463 | raise_perm_exc() if $userid eq 'root@pam' && $authuser ne 'root@pam'; | |
464 | ||
465 | # Regular users need to confirm their password to change u2f settings. | |
466 | if ($authuser ne 'root@pam') { | |
467 | raise_param_exc('password' => 'password is required to modify u2f data') | |
468 | if !defined($password); | |
469 | my $domain_cfg = cfs_read_file('domains.cfg'); | |
470 | my $cfg = $domain_cfg->{ids}->{$realm}; | |
471 | die "auth domain '$realm' does not exists\n" if !$cfg; | |
472 | my $plugin = PVE::Auth::Plugin->lookup($cfg->{type}); | |
473 | $plugin->authenticate_user($cfg, $realm, $ruid, $password); | |
474 | } | |
475 | ||
476 | if ($action eq 'delete') { | |
477 | PVE::AccessControl::user_set_tfa($userid, $realm, undef, undef); | |
478 | PVE::Cluster::log_msg('info', $authuser, "deleted u2f data for user '$userid'"); | |
479 | } elsif ($action eq 'new') { | |
480 | my $u2f = get_u2f_instance($rpcenv); | |
481 | my $challenge = $u2f->registration_challenge() | |
482 | or raise("failed to get u2f challenge"); | |
483 | $challenge = decode_json($challenge); | |
484 | PVE::AccessControl::user_set_tfa($userid, $realm, 'u2f', $challenge); | |
485 | return $challenge; | |
486 | } elsif ($action eq 'confirm') { | |
487 | raise_param_exc('response' => "confirm action requires the 'response' parameter to be set") | |
488 | if !defined($response); | |
489 | ||
490 | my ($type, $u2fdata) = PVE::AccessControl::user_get_tfa($userid, $realm); | |
491 | raise("no u2f data available") | |
492 | if (!defined($type) || $type ne 'u2f'); | |
493 | ||
494 | my $challenge = $u2fdata->{challenge} | |
495 | or raise("no active challenge"); | |
496 | ||
497 | my $u2f = get_u2f_instance($rpcenv); | |
498 | $u2f->set_challenge($challenge); | |
499 | my ($keyHandle, $publicKey) = $u2f->registration_verify($response); | |
500 | PVE::AccessControl::user_set_tfa($userid, $realm, 'u2f', { | |
501 | keyHandle => $keyHandle, | |
502 | publicKey => encode_base64($publicKey, ''), | |
503 | }); | |
504 | } else { | |
505 | die "invalid action: $action\n"; | |
506 | } | |
507 | ||
508 | return {}; | |
509 | }}); | |
510 | ||
511 | __PACKAGE__->register_method({ | |
512 | name => 'verify_tfa', | |
513 | path => 'tfa', | |
514 | method => 'POST', | |
515 | permissions => { user => 'all' }, | |
516 | protected => 1, # else we can't access shadow files | |
517 | description => 'Finish a u2f challenge.', | |
518 | parameters => { | |
519 | additionalProperties => 0, | |
520 | properties => { | |
521 | response => { | |
522 | type => 'string', | |
523 | description => 'The response to the current authentication challenge.', | |
524 | }, | |
525 | } | |
526 | }, | |
527 | returns => { | |
528 | type => 'object', | |
529 | properties => { | |
530 | ticket => { type => 'string' }, | |
531 | # cap | |
532 | } | |
533 | }, | |
534 | code => sub { | |
535 | my ($param) = @_; | |
536 | ||
537 | my $rpcenv = PVE::RPCEnvironment::get(); | |
538 | my $challenge = $rpcenv->get_u2f_challenge() | |
539 | or raise('no active challenge'); | |
540 | my $authuser = $rpcenv->get_user(); | |
541 | my ($username, undef, $realm) = PVE::AccessControl::verify_username($authuser); | |
542 | ||
543 | my ($tfa_type, $u2fdata) = PVE::AccessControl::user_get_tfa($username, $realm); | |
544 | if (!defined($tfa_type) || $tfa_type ne 'u2f') { | |
545 | raise('no u2f data available'); | |
546 | } | |
547 | ||
548 | my $keyHandle = $u2fdata->{keyHandle}; | |
549 | my $publicKey = $u2fdata->{publicKey}; | |
550 | raise("incomplete u2f setup") | |
551 | if !defined($keyHandle) || !defined($publicKey); | |
552 | ||
553 | my $u2f = get_u2f_instance($rpcenv, $publicKey, $keyHandle); | |
554 | $u2f->set_challenge($challenge); | |
555 | ||
556 | eval { | |
557 | my ($counter, $present) = $u2f->auth_verify($param->{response}); | |
558 | # Do we want to do anything with these? | |
559 | }; | |
560 | if (my $err = $@) { | |
561 | my $clientip = $rpcenv->get_client_ip() || ''; | |
562 | syslog('err', "authentication verification failure; rhost=$clientip user=$authuser msg=$err"); | |
563 | die PVE::Exception->new("authentication failure\n", code => 401); | |
564 | } | |
565 | ||
566 | # create a new ticket for the user: | |
567 | my $ticket_data = "u2f!$authuser!verified"; | |
568 | return { | |
569 | ticket => PVE::AccessControl::assemble_ticket($ticket_data), | |
570 | cap => &$compute_api_permission($rpcenv, $authuser), | |
571 | } | |
572 | }}); | |
573 | ||
2c3a6c0a | 574 | 1; |