]>
Commit | Line | Data |
---|---|---|
62ebb4bc DM |
1 | package PMG::API2::Users; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use Data::Dumper; | |
6 | ||
7 | use PVE::SafeSyslog; | |
8 | use PVE::Tools qw(extract_param); | |
9 | use PVE::JSONSchema qw(get_standard_option); | |
10 | use PVE::RESTHandler; | |
11 | use PVE::INotify; | |
db5051b4 | 12 | use PVE::Exception qw(raise_perm_exc); |
62ebb4bc | 13 | |
39fa67c9 | 14 | use PMG::RESTEnvironment; |
62ebb4bc | 15 | use PMG::UserConfig; |
27137458 | 16 | use PMG::TFAConfig; |
62ebb4bc DM |
17 | |
18 | use base qw(PVE::RESTHandler); | |
19 | ||
7bf06780 DM |
20 | my $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 | 283 | 1; |