]> git.proxmox.com Git - pve-access-control.git/blame - PVE/API2/User.pm
delete TFA entries when deleting a user
[pve-access-control.git] / PVE / API2 / User.pm
CommitLineData
2c3a6c0a
DM
1package PVE::API2::User;
2
3use strict;
4use warnings;
37d45deb 5use PVE::Exception qw(raise raise_perm_exc);
2c3a6c0a
DM
6use PVE::Cluster qw (cfs_read_file cfs_write_file);
7use PVE::Tools qw(split_list);
8use PVE::AccessControl;
3a5ae7a0 9use PVE::JSONSchema qw(get_standard_option register_standard_option);
2c3a6c0a
DM
10
11use PVE::SafeSyslog;
12
2c3a6c0a
DM
13use PVE::RESTHandler;
14
15use base qw(PVE::RESTHandler);
16
3a5ae7a0
SI
17register_standard_option('user-enable', {
18 description => "Enable the account (default). You can set this to '0' to disable the account",
19 type => 'boolean',
20 optional => 1,
21 default => 1,
22});
23register_standard_option('user-expire', {
24 description => "Account expiration date (seconds since epoch). '0' means no expiration date.",
25 type => 'integer',
26 minimum => 0,
27 optional => 1,
28});
29register_standard_option('user-firstname', { type => 'string', optional => 1 });
30register_standard_option('user-lastname', { type => 'string', optional => 1 });
31register_standard_option('user-email', { type => 'string', optional => 1, format => 'email-opt' });
32register_standard_option('user-comment', { type => 'string', optional => 1 });
33register_standard_option('user-keys', {
34 description => "Keys for two factor auth (yubico).",
35 type => 'string',
36 optional => 1,
37});
38register_standard_option('group-list', {
39 type => 'string', format => 'pve-groupid-list',
40 optional => 1,
41 completion => \&PVE::AccessControl::complete_group,
42});
43
2c3a6c0a
DM
44my $extract_user_data = sub {
45 my ($data, $full) = @_;
46
47 my $res = {};
48
96f8ebd6 49 foreach my $prop (qw(enable expire firstname lastname email comment keys)) {
2c3a6c0a
DM
50 $res->{$prop} = $data->{$prop} if defined($data->{$prop});
51 }
52
53 return $res if !$full;
54
55 $res->{groups} = $data->{groups} ? [ keys %{$data->{groups}} ] : [];
56
57 return $res;
58};
59
60__PACKAGE__->register_method ({
0a6e09fd
PA
61 name => 'index',
62 path => '',
2c3a6c0a
DM
63 method => 'GET',
64 description => "User index.",
0a6e09fd 65 permissions => {
82b63965 66 description => "The returned list is restricted to users where you have 'User.Modify' or 'Sys.Audit' permissions on '/access/groups' or on a group the user belongs too. But it always includes the current (authenticated) user.",
96919234
DM
67 user => 'all',
68 },
2c3a6c0a
DM
69 parameters => {
70 additionalProperties => 0,
cb6f2f93
DM
71 properties => {
72 enabled => {
73 type => 'boolean',
74 description => "Optional filter for enable property.",
75 optional => 1,
76 }
77 },
2c3a6c0a
DM
78 },
79 returns => {
80 type => 'array',
81 items => {
82 type => "object",
83 properties => {
3a5ae7a0
SI
84 userid => get_standard_option('userid-completed'),
85 enable => get_standard_option('user-enable'),
86 expire => get_standard_option('user-expire'),
87 firstname => get_standard_option('user-firstname'),
88 lastname => get_standard_option('user-lastname'),
89 email => get_standard_option('user-email'),
90 comment => get_standard_option('user-comment'),
91 keys => get_standard_option('user-keys'),
2c3a6c0a
DM
92 },
93 },
94 links => [ { rel => 'child', href => "{userid}" } ],
95 },
96 code => sub {
97 my ($param) = @_;
0a6e09fd 98
930dcfc8 99 my $rpcenv = PVE::RPCEnvironment::get();
37d45deb 100 my $usercfg = $rpcenv->{user_cfg};
930dcfc8
DM
101 my $authuser = $rpcenv->get_user();
102
2c3a6c0a
DM
103 my $res = [];
104
82b63965
DM
105 my $privs = [ 'User.Modify', 'Sys.Audit' ];
106 my $canUserMod = $rpcenv->check_any($authuser, "/access/groups", $privs, 1);
b9180ed2 107 my $groups = $rpcenv->filter_groups($authuser, $privs, 1);
0a6e09fd 108 my $allowed_users = $rpcenv->group_member_join([keys %$groups]);
37d45deb 109
2c3a6c0a 110 foreach my $user (keys %{$usercfg->{users}}) {
37d45deb
DM
111
112 if (!($canUserMod || $user eq $authuser)) {
113 next if !$allowed_users->{$user};
114 }
930dcfc8 115
2c3a6c0a 116 my $entry = &$extract_user_data($usercfg->{users}->{$user});
cb6f2f93
DM
117
118 if (defined($param->{enabled})) {
119 next if $entry->{enable} && !$param->{enabled};
120 next if !$entry->{enable} && $param->{enabled};
121 }
122
2c3a6c0a
DM
123 $entry->{userid} = $user;
124 push @$res, $entry;
125 }
126
127 return $res;
128 }});
129
130__PACKAGE__->register_method ({
0a6e09fd 131 name => 'create_user',
2c3a6c0a 132 protected => 1,
0a6e09fd 133 path => '',
2c3a6c0a 134 method => 'POST',
0a6e09fd 135 permissions => {
82b63965
DM
136 description => "You need 'Realm.AllocateUser' on '/access/realm/<realm>' on the realm of user <userid>, and 'User.Modify' permissions to '/access/groups/<group>' for any group specified (or 'User.Modify' on '/access/groups' if you pass no groups.",
137 check => [ 'and',
138 [ 'userid-param', 'Realm.AllocateUser'],
139 [ 'userid-group', ['User.Modify'], groups_param => 1],
140 ],
96919234 141 },
2c3a6c0a
DM
142 description => "Create new user.",
143 parameters => {
0a6e09fd 144 additionalProperties => 0,
2c3a6c0a 145 properties => {
3a5ae7a0
SI
146 userid => get_standard_option('userid-completed'),
147 enable => get_standard_option('user-enable'),
148 expire => get_standard_option('user-expire'),
149 firstname => get_standard_option('user-firstname'),
150 lastname => get_standard_option('user-lastname'),
151 email => get_standard_option('user-email'),
152 comment => get_standard_option('user-comment'),
153 keys => get_standard_option('user-keys'),
37d45deb
DM
154 password => {
155 description => "Initial password.",
0a6e09fd
PA
156 type => 'string',
157 optional => 1,
158 minLength => 5,
159 maxLength => 64
37d45deb 160 },
3a5ae7a0 161 groups => get_standard_option('group-list'),
2c3a6c0a
DM
162 },
163 },
164 returns => { type => 'null' },
165 code => sub {
166 my ($param) = @_;
167
168 PVE::AccessControl::lock_user_config(
169 sub {
0a6e09fd 170
2c3a6c0a 171 my ($username, $ruid, $realm) = PVE::AccessControl::verify_username($param->{userid});
0a6e09fd 172
2c3a6c0a
DM
173 my $usercfg = cfs_read_file("user.cfg");
174
0a6e09fd 175 die "user '$username' already exists\n"
2c3a6c0a 176 if $usercfg->{users}->{$username};
0a6e09fd 177
2c3a6c0a 178 PVE::AccessControl::domain_set_password($realm, $ruid, $param->{password})
fdb30a4c 179 if defined($param->{password});
2c3a6c0a
DM
180
181 my $enable = defined($param->{enable}) ? $param->{enable} : 1;
182 $usercfg->{users}->{$username} = { enable => $enable };
183 $usercfg->{users}->{$username}->{expire} = $param->{expire} if $param->{expire};
184
185 if ($param->{groups}) {
186 foreach my $group (split_list($param->{groups})) {
187 if ($usercfg->{groups}->{$group}) {
188 PVE::AccessControl::add_user_group($username, $usercfg, $group);
189 } else {
190 die "no such group '$group'\n";
191 }
192 }
193 }
194
195 $usercfg->{users}->{$username}->{firstname} = $param->{firstname} if $param->{firstname};
196 $usercfg->{users}->{$username}->{lastname} = $param->{lastname} if $param->{lastname};
197 $usercfg->{users}->{$username}->{email} = $param->{email} if $param->{email};
198 $usercfg->{users}->{$username}->{comment} = $param->{comment} if $param->{comment};
96f8ebd6 199 $usercfg->{users}->{$username}->{keys} = $param->{keys} if $param->{keys};
2c3a6c0a
DM
200
201 cfs_write_file("user.cfg", $usercfg);
202 }, "create user failed");
203
204 return undef;
205 }});
206
207__PACKAGE__->register_method ({
0a6e09fd
PA
208 name => 'read_user',
209 path => '{userid}',
2c3a6c0a
DM
210 method => 'GET',
211 description => "Get user configuration.",
0a6e09fd 212 permissions => {
82b63965 213 check => ['userid-group', ['User.Modify', 'Sys.Audit']],
96919234 214 },
2c3a6c0a 215 parameters => {
0a6e09fd 216 additionalProperties => 0,
2c3a6c0a 217 properties => {
3a5ae7a0 218 userid => get_standard_option('userid-completed'),
2c3a6c0a
DM
219 },
220 },
221 returns => {
0a6e09fd 222 additionalProperties => 0,
2c3a6c0a 223 properties => {
3a5ae7a0
SI
224 enable => get_standard_option('user-enable'),
225 expire => get_standard_option('user-expire'),
226 firstname => get_standard_option('user-firstname'),
227 lastname => get_standard_option('user-lastname'),
228 email => get_standard_option('user-email'),
229 comment => get_standard_option('user-comment'),
230 keys => get_standard_option('user-keys'),
2c3a6c0a 231 groups => { type => 'array' },
3a5ae7a0
SI
232 },
233 type => "object"
2c3a6c0a
DM
234 },
235 code => sub {
236 my ($param) = @_;
237
0a6e09fd 238 my ($username, undef, $domain) =
2c3a6c0a
DM
239 PVE::AccessControl::verify_username($param->{userid});
240
241 my $usercfg = cfs_read_file("user.cfg");
2c3a6c0a 242
37d45deb 243 my $data = PVE::AccessControl::check_user_exist($usercfg, $username);
0a6e09fd 244
2c3a6c0a
DM
245 return &$extract_user_data($data, 1);
246 }});
247
248__PACKAGE__->register_method ({
0a6e09fd 249 name => 'update_user',
2c3a6c0a 250 protected => 1,
0a6e09fd 251 path => '{userid}',
2c3a6c0a 252 method => 'PUT',
0a6e09fd 253 permissions => {
96919234
DM
254 check => ['userid-group', ['User.Modify'], groups_param => 1 ],
255 },
2c3a6c0a
DM
256 description => "Update user configuration.",
257 parameters => {
0a6e09fd 258 additionalProperties => 0,
2c3a6c0a 259 properties => {
3a5ae7a0
SI
260 userid => get_standard_option('userid-completed'),
261 enable => get_standard_option('user-enable'),
262 expire => get_standard_option('user-expire'),
263 firstname => get_standard_option('user-firstname'),
264 lastname => get_standard_option('user-lastname'),
265 email => get_standard_option('user-email'),
266 comment => get_standard_option('user-comment'),
267 keys => get_standard_option('user-keys'),
268 groups => get_standard_option('group-list'),
0a6e09fd
PA
269 append => {
270 type => 'boolean',
2c3a6c0a
DM
271 optional => 1,
272 requires => 'groups',
273 },
2c3a6c0a
DM
274 },
275 },
276 returns => { type => 'null' },
277 code => sub {
278 my ($param) = @_;
37d45deb 279
0a6e09fd 280 my ($username, $ruid, $realm) =
37d45deb 281 PVE::AccessControl::verify_username($param->{userid});
0a6e09fd 282
2c3a6c0a
DM
283 PVE::AccessControl::lock_user_config(
284 sub {
0a6e09fd 285
2c3a6c0a
DM
286 my $usercfg = cfs_read_file("user.cfg");
287
37d45deb 288 PVE::AccessControl::check_user_exist($usercfg, $username);
2c3a6c0a
DM
289
290 $usercfg->{users}->{$username}->{enable} = $param->{enable} if defined($param->{enable});
291
292 $usercfg->{users}->{$username}->{expire} = $param->{expire} if defined($param->{expire});
293
0a6e09fd 294 PVE::AccessControl::delete_user_group($username, $usercfg)
e6521738 295 if (!$param->{append} && defined($param->{groups}));
2c3a6c0a
DM
296
297 if ($param->{groups}) {
298 foreach my $group (split_list($param->{groups})) {
299 if ($usercfg->{groups}->{$group}) {
300 PVE::AccessControl::add_user_group($username, $usercfg, $group);
301 } else {
302 die "no such group '$group'\n";
303 }
304 }
305 }
306
307 $usercfg->{users}->{$username}->{firstname} = $param->{firstname} if defined($param->{firstname});
308 $usercfg->{users}->{$username}->{lastname} = $param->{lastname} if defined($param->{lastname});
309 $usercfg->{users}->{$username}->{email} = $param->{email} if defined($param->{email});
310 $usercfg->{users}->{$username}->{comment} = $param->{comment} if defined($param->{comment});
96f8ebd6 311 $usercfg->{users}->{$username}->{keys} = $param->{keys} if defined($param->{keys});
2c3a6c0a
DM
312
313 cfs_write_file("user.cfg", $usercfg);
314 }, "update user failed");
0a6e09fd 315
2c3a6c0a
DM
316 return undef;
317 }});
318
319__PACKAGE__->register_method ({
0a6e09fd 320 name => 'delete_user',
2c3a6c0a 321 protected => 1,
0a6e09fd 322 path => '{userid}',
2c3a6c0a
DM
323 method => 'DELETE',
324 description => "Delete user.",
0a6e09fd 325 permissions => {
82b63965
DM
326 check => [ 'and',
327 [ 'userid-param', 'Realm.AllocateUser'],
328 [ 'userid-group', ['User.Modify']],
329 ],
12683df7 330 },
2c3a6c0a 331 parameters => {
0a6e09fd 332 additionalProperties => 0,
2c3a6c0a 333 properties => {
3a5ae7a0 334 userid => get_standard_option('userid-completed'),
2c3a6c0a
DM
335 }
336 },
337 returns => { type => 'null' },
338 code => sub {
339 my ($param) = @_;
0a6e09fd 340
37d45deb
DM
341 my $rpcenv = PVE::RPCEnvironment::get();
342 my $authuser = $rpcenv->get_user();
343
0a6e09fd 344 my ($userid, $ruid, $realm) =
37d45deb 345 PVE::AccessControl::verify_username($param->{userid});
2c3a6c0a
DM
346
347 PVE::AccessControl::lock_user_config(
348 sub {
349
2c3a6c0a
DM
350 my $usercfg = cfs_read_file("user.cfg");
351
5bb4e06a
DM
352 my $domain_cfg = cfs_read_file('domains.cfg');
353 if (my $cfg = $domain_cfg->{ids}->{$realm}) {
354 my $plugin = PVE::Auth::Plugin->lookup($cfg->{type});
355 $plugin->delete_user($cfg, $realm, $ruid);
356 }
2c3a6c0a 357
9536c4dc
WB
358 # Remove TFA data before removing the user entry as the user entry tells us whether
359 # we need ot update priv/tfa.cfg.
360 PVE::AccessControl::user_set_tfa($userid, $realm, undef, undef, $usercfg, $domain_cfg);
361
5bb4e06a 362 delete $usercfg->{users}->{$userid};
37d45deb
DM
363
364 PVE::AccessControl::delete_user_group($userid, $usercfg);
365 PVE::AccessControl::delete_user_acl($userid, $usercfg);
2c3a6c0a
DM
366 cfs_write_file("user.cfg", $usercfg);
367 }, "delete user failed");
0a6e09fd 368
2c3a6c0a
DM
369 return undef;
370 }});
371
3721;