]> git.proxmox.com Git - pve-access-control.git/blame - src/PVE/API2/TFA.pm
move TFA api path into its own module
[pve-access-control.git] / src / PVE / API2 / TFA.pm
CommitLineData
dc547a13
WB
1package PVE::API2::TFA;
2
3use strict;
4use warnings;
5
6use PVE::AccessControl;
7use PVE::Cluster qw(cfs_read_file);
8use PVE::JSONSchema qw(get_standard_option);
9use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
10use PVE::RPCEnvironment;
11
12use PVE::API2::AccessControl; # for old login api get_u2f_instance method
13
14use PVE::RESTHandler;
15
16use base qw(PVE::RESTHandler);
17
18### OLD API
19
20__PACKAGE__->register_method({
21 name => 'verify_tfa',
22 path => '',
23 method => 'POST',
24 permissions => { user => 'all' },
25 protected => 1, # else we can't access shadow files
26 allowtoken => 0, # we don't want tokens to access TFA information
27 description => 'Finish a u2f challenge.',
28 parameters => {
29 additionalProperties => 0,
30 properties => {
31 response => {
32 type => 'string',
33 description => 'The response to the current authentication challenge.',
34 },
35 }
36 },
37 returns => {
38 type => 'object',
39 properties => {
40 ticket => { type => 'string' },
41 # cap
42 }
43 },
44 code => sub {
45 my ($param) = @_;
46
47 my $rpcenv = PVE::RPCEnvironment::get();
48 my $authuser = $rpcenv->get_user();
49 my ($username, undef, $realm) = PVE::AccessControl::verify_username($authuser);
50
51 my ($tfa_type, $tfa_data) = PVE::AccessControl::user_get_tfa($username, $realm, 0);
52 if (!defined($tfa_type)) {
53 raise('no u2f data available');
54 }
55
56 eval {
57 if ($tfa_type eq 'u2f') {
58 my $challenge = $rpcenv->get_u2f_challenge()
59 or raise('no active challenge');
60
61 my $keyHandle = $tfa_data->{keyHandle};
62 my $publicKey = $tfa_data->{publicKey};
63 raise("incomplete u2f setup")
64 if !defined($keyHandle) || !defined($publicKey);
65
66 my $u2f = PVE::API2::AccessControl::get_u2f_instance($rpcenv, $publicKey, $keyHandle);
67 $u2f->set_challenge($challenge);
68
69 my ($counter, $present) = $u2f->auth_verify($param->{response});
70 # Do we want to do anything with these?
71 } else {
72 # sanity check before handing off to the verification code:
73 my $keys = $tfa_data->{keys} or die "missing tfa keys\n";
74 my $config = $tfa_data->{config} or die "bad tfa entry\n";
75 PVE::AccessControl::verify_one_time_pw($tfa_type, $authuser, $keys, $config, $param->{response});
76 }
77 };
78 if (my $err = $@) {
79 my $clientip = $rpcenv->get_client_ip() || '';
80 syslog('err', "authentication verification failure; rhost=$clientip user=$authuser msg=$err");
81 die PVE::Exception->new("authentication failure\n", code => 401);
82 }
83
84 return {
85 ticket => PVE::AccessControl::assemble_ticket($authuser),
86 cap => $rpcenv->compute_api_permission($authuser),
87 }
88 }});
89
90### END OLD API
91
92my $TFA_TYPE_SCHEMA = {
93 type => 'string',
94 description => 'TFA Entry Type.',
95 enum => [qw(totp u2f webauthn recovery yubico)],
96};
97
98my %TFA_INFO_PROPERTIES = (
99 id => {
100 type => 'string',
101 description => 'The id used to reference this entry.',
102 },
103 description => {
104 type => 'string',
105 description => 'User chosen description for this entry.',
106 },
107 created => {
108 type => 'integer',
109 description => 'Creation time of this entry as unix epoch.',
110 },
111 enable => {
112 type => 'boolean',
113 description => 'Whether this TFA entry is currently enabled.',
114 optional => 1,
115 default => 1,
116 },
117);
118
119my $TYPED_TFA_ENTRY_SCHEMA = {
120 type => 'object',
121 description => 'TFA Entry.',
122 properties => {
123 type => $TFA_TYPE_SCHEMA,
124 %TFA_INFO_PROPERTIES,
125 },
126};
127
128my $TFA_ID_SCHEMA = {
129 type => 'string',
130 description => 'A TFA entry id.',
131};
132
133__PACKAGE__->register_method ({
134 name => 'list_user_tfa',
135 path => '{userid}',
136 method => 'GET',
137 permissions => {
138 check => [ 'or',
139 ['userid-param', 'self'],
140 ['userid-group', ['User.Modify', 'Sys.Audit']],
141 ],
142 },
143 protected => 1, # else we can't access shadow files
144 allowtoken => 0, # we don't want tokens to change the regular user's TFA settings
145 description => 'List TFA configurations of users.',
146 parameters => {
147 additionalProperties => 0,
148 properties => {
149 userid => get_standard_option('userid', {
150 completion => \&PVE::AccessControl::complete_username,
151 }),
152 }
153 },
154 returns => {
155 description => "A list of the user's TFA entries.",
156 type => 'array',
157 items => $TYPED_TFA_ENTRY_SCHEMA,
158 },
159 code => sub {
160 my ($param) = @_;
161 my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
162 return $tfa_cfg->api_list_user_tfa($param->{userid});
163 }});
164
165__PACKAGE__->register_method ({
166 name => 'get_tfa_entry',
167 path => '{userid}/{id}',
168 method => 'GET',
169 permissions => {
170 check => [ 'or',
171 ['userid-param', 'self'],
172 ['userid-group', ['User.Modify', 'Sys.Audit']],
173 ],
174 },
175 protected => 1, # else we can't access shadow files
176 allowtoken => 0, # we don't want tokens to change the regular user's TFA settings
177 description => 'A requested TFA entry if present.',
178 parameters => {
179 additionalProperties => 0,
180 properties => {
181 userid => get_standard_option('userid', {
182 completion => \&PVE::AccessControl::complete_username,
183 }),
184 id => $TFA_ID_SCHEMA,
185 }
186 },
187 returns => $TYPED_TFA_ENTRY_SCHEMA,
188 code => sub {
189 my ($param) = @_;
190 my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
191 return $tfa_cfg->api_get_tfa_entry($param->{userid}, $param->{id});
192 }});
193
194__PACKAGE__->register_method ({
195 name => 'list_tfa',
196 path => '',
197 method => 'GET',
198 permissions => {
199 description => "Returns all or just the logged-in user, depending on privileges.",
200 user => 'all',
201 },
202 protected => 1, # else we can't access shadow files
203 allowtoken => 0, # we don't want tokens to change the regular user's TFA settings
204 description => 'List TFA configurations of users.',
205 parameters => {
206 additionalProperties => 0,
207 properties => {}
208 },
209 returns => {
210 description => "The list tuples of user and TFA entries.",
211 type => 'array',
212 items => {
213 type => 'object',
214 properties => {
215 userid => {
216 type => 'string',
217 description => 'User this entry belongs to.',
218 },
219 entries => {
220 type => 'array',
221 items => $TYPED_TFA_ENTRY_SCHEMA,
222 },
223 },
224 },
225 },
226 code => sub {
227 my ($param) = @_;
228
229 my $rpcenv = PVE::RPCEnvironment::get();
230 my $authuser = $rpcenv->get_user();
231
232
233 my $top_level_allowed = ($authuser eq 'root@pam');
234
235 my $tfa_cfg = cfs_read_file('priv/tfa.cfg');
236 return $tfa_cfg->api_list_tfa($authuser, $top_level_allowed);
237 }});
238
2391;