]> git.proxmox.com Git - pmg-api.git/blame - src/PMG/API2/Users.pm
api: include tfa lock status in user list
[pmg-api.git] / src / PMG / API2 / Users.pm
CommitLineData
62ebb4bc
DM
1package PMG::API2::Users;
2
3use strict;
4use warnings;
5use Data::Dumper;
6
7use PVE::SafeSyslog;
8use PVE::Tools qw(extract_param);
9use PVE::JSONSchema qw(get_standard_option);
10use PVE::RESTHandler;
11use PVE::INotify;
db5051b4 12use PVE::Exception qw(raise_perm_exc);
62ebb4bc 13
39fa67c9 14use PMG::RESTEnvironment;
62ebb4bc 15use PMG::UserConfig;
27137458 16use PMG::TFAConfig;
62ebb4bc
DM
17
18use base qw(PVE::RESTHandler);
19
7bf06780
DM
20my $extract_userdata = sub {
21 my ($entry) = @_;
22
23 my $res = {};
24 foreach my $k (keys %$entry) {
25 $res->{$k} = $entry->{$k} if $k ne 'crypt_pass';
26 }
27
28 return $res;
29};
30
62ebb4bc
DM
31__PACKAGE__->register_method ({
32 name => 'index',
33 path => '',
34 method => 'GET',
35 description => "List users.",
36 proxyto => 'master',
edd6dd1c 37 protected => 1,
39fa67c9 38 permissions => { check => [ 'admin', 'qmanager', 'audit' ] },
62ebb4bc
DM
39 parameters => {
40 additionalProperties => 0,
41 properties => {},
42 },
43 returns => {
44 type => 'array',
45 items => {
46 type => "object",
47 properties => {
48 userid => { type => 'string'},
49 enable => { type => 'boolean'},
50 role => { type => 'string'},
51 comment => { type => 'string', optional => 1},
29d02b90
WB
52 'totp-locked' => {
53 type => 'boolean',
54 optional => 1,
55 description => 'True if the user is currently locked out of TOTP factors.',
56 },
57 'tfa-locked-until' => {
58 type => 'integer',
59 optional => 1,
60 description =>
61 'Contains a timestamp until when a user is locked out of 2nd factors.',
62 },
62ebb4bc
DM
63 },
64 },
65 links => [ { rel => 'child', href => "{userid}" } ],
66 },
67 code => sub {
68 my ($param) = @_;
69
70 my $cfg = PMG::UserConfig->new();
29d02b90 71 my $tfa_cfg = PMG::TFAConfig->new();
62ebb4bc 72
39fa67c9
DC
73 my $rpcenv = PMG::RESTEnvironment->get();
74 my $authuser = $rpcenv->get_user();
75 my $role = $rpcenv->get_role();
76
62ebb4bc
DM
77 my $res = [];
78
79 foreach my $userid (sort keys %$cfg) {
39fa67c9 80 next if $role eq 'qmanager' && $authuser ne $userid;
29d02b90
WB
81 my $entry = $extract_userdata->($cfg->{$userid});
82 if (defined($tfa_cfg)) {
83 if (my $data = $tfa_cfg->tfa_lock_status($userid)) {
84 for (qw(totp-locked tfa-locked-until)) {
85 $entry->{$_} = $data->{$_} if exists($data->{$_});
86 }
87 }
88 }
89 push @$res, $entry;
62ebb4bc
DM
90 }
91
92 return $res;
93 }});
94
95__PACKAGE__->register_method ({
96 name => 'create',
97 path => '',
98 method => 'POST',
99 proxyto => 'master',
100 protected => 1,
9dae0b9a 101 description => "Create new user",
8333a87c 102 parameters => $PMG::UserConfig::create_schema,
62ebb4bc
DM
103 returns => { type => 'null' },
104 code => sub {
105 my ($param) = @_;
106
107 my $code = sub {
108
109 my $cfg = PMG::UserConfig->new();
110
111 die "User '$param->{userid}' already exists\n"
112 if $cfg->{$param->{userid}};
113
fff8e89c
DM
114 my $entry = {};
115 foreach my $k (keys %$param) {
116 my $v = $param->{$k};
117 if ($k eq 'password') {
1a8170cf 118 $entry->{crypt_pass} = PVE::Tools::encrypt_pw($v);
fff8e89c
DM
119 } else {
120 $entry->{$k} = $v;
121 }
122 }
123
124 $entry->{enable} //= 0;
125 $entry->{expire} //= 0;
126 $entry->{role} //= 'audit';
127
128 $cfg->{$param->{userid}} = $entry;
62ebb4bc
DM
129
130 $cfg->write();
131 };
132
133 PMG::UserConfig::lock_config($code, "create user failed");
134
135 return undef;
136 }});
137
138__PACKAGE__->register_method ({
139 name => 'read',
140 path => '{userid}',
141 method => 'GET',
142 description => "Read User data.",
db5051b4 143 permissions => { check => [ 'admin', 'qmanager', 'audit' ] },
62ebb4bc 144 proxyto => 'master',
edd6dd1c 145 protected => 1,
62ebb4bc
DM
146 parameters => {
147 additionalProperties => 0,
148 properties => {
4d813470 149 userid => get_standard_option('userid'),
62ebb4bc
DM
150 },
151 },
152 returns => {
153 type => "object",
154 properties => {},
155 },
156 code => sub {
157 my ($param) = @_;
158
159 my $cfg = PMG::UserConfig->new();
160
db5051b4
DC
161 my $rpcenv = PMG::RESTEnvironment->get();
162 my $authuser = $rpcenv->get_user();
163 my $role = $rpcenv->get_role();
164
165 raise_perm_exc()
166 if $role eq 'qmanager' && $authuser ne $param->{userid};
167
7bf06780
DM
168 my $data = $cfg->lookup_user_data($param->{userid});
169
170 my $res = $extract_userdata->($data);
171
172 return $res;
62ebb4bc
DM
173 }});
174
175__PACKAGE__->register_method ({
176 name => 'write',
177 path => '{userid}',
178 method => 'PUT',
179 description => "Update user data.",
180 protected => 1,
181 proxyto => 'master',
0ecf02bc 182 parameters => $PMG::UserConfig::update_schema,
62ebb4bc
DM
183 returns => { type => 'null' },
184 code => sub {
185 my ($param) = @_;
186
187 my $code = sub {
188
189 my $cfg = PMG::UserConfig->new();
190
0ecf02bc
DM
191 my $userid = extract_param($param, 'userid');
192
193 my $entry = $cfg->lookup_user_data($userid);
62ebb4bc 194
0ecf02bc
DM
195 my $delete_str = extract_param($param, 'delete');
196 die "no options specified\n"
197 if !$delete_str && !scalar(keys %$param);
198
199 foreach my $k (PVE::Tools::split_list($delete_str)) {
200 delete $entry->{$k};
201 }
202
203 foreach my $k (keys %$param) {
204 my $v = $param->{$k};
205 if ($k eq 'password') {
1a8170cf 206 $entry->{crypt_pass} = PVE::Tools::encrypt_pw($v);
0ecf02bc
DM
207 } else {
208 $entry->{$k} = $v;
209 }
210 }
62ebb4bc
DM
211
212 $cfg->write();
213 };
214
215 PMG::UserConfig::lock_config($code, "update user failed");
216
217 return undef;
218 }});
219
220__PACKAGE__->register_method ({
221 name => 'delete',
222 path => '{userid}',
223 method => 'DELETE',
224 description => "Delete a user.",
225 protected => 1,
226 proxyto => 'master',
227 parameters => {
228 additionalProperties => 0,
229 properties => {
277c84e4 230 userid => get_standard_option('userid'),
62ebb4bc
DM
231 }
232 },
233 returns => { type => 'null' },
234 code => sub {
235 my ($param) = @_;
236
237 my $code = sub {
238
239 my $cfg = PMG::UserConfig->new();
240
241 $cfg->lookup_user_data($param->{userid}); # user exists?
242
243 delete $cfg->{$param->{userid}};
244
245 $cfg->write();
246 };
247
248 PMG::UserConfig::lock_config($code, "delete user failed");
249
250 return undef;
251 }});
252
27137458
WB
253__PACKAGE__->register_method ({
254 name => 'unlock_tfa',
255 path => '{userid}/unlock-tfa',
256 method => 'PUT',
257 protected => 1,
258 description => "Unlock a user's TFA authentication.",
259 permissions => { check => [ 'admin' ] },
260 parameters => {
261 additionalProperties => 0,
262 properties => {
263 userid => get_standard_option('userid'),
264 },
265 },
266 returns => { type => 'boolean' },
267 code => sub {
268 my ($param) = @_;
269
270 my $userid = extract_param($param, "userid");
271
272 my $user_was_locked = PMG::TFAConfig::lock_config(sub {
273 my $tfa_cfg = PMG::TFAConfig->new();
274 my $was_locked = $tfa_cfg->api_unlock_tfa($userid);
275 $tfa_cfg->write() if $was_locked;
276 return $was_locked;
277 });
278
279 return $user_was_locked;
280 }});
281
282
62ebb4bc 2831;