]> git.proxmox.com Git - pmg-api.git/blame - PMG/API2/AccessControl.pm
UserConfig: rename verity_entry to verify_entry
[pmg-api.git] / PMG / API2 / AccessControl.pm
CommitLineData
1360e6f0
DM
1package PMG::API2::AccessControl;
2
3use strict;
4use warnings;
5
6use PVE::Exception qw(raise raise_perm_exc);
7use PVE::SafeSyslog;
9d82c6bc 8use PMG::RESTEnvironment;
1360e6f0
DM
9use PVE::RESTHandler;
10use PVE::JSONSchema qw(get_standard_option);
11
62ebb4bc
DM
12use PMG::Utils;
13use PMG::UserConfig;
1360e6f0 14use PMG::AccessControl;
4b0c6af3 15use PMG::API2::Users;
1360e6f0
DM
16
17use Data::Dumper;
18
19use base qw(PVE::RESTHandler);
20
4b0c6af3
DM
21__PACKAGE__->register_method ({
22 subclass => "PMG::API2::Users",
23 path => 'users',
24});
1360e6f0
DM
25
26__PACKAGE__->register_method ({
27 name => 'index',
28 path => '',
29 method => 'GET',
30 description => "Directory index.",
31 permissions => {
32 user => 'all',
33 },
34 parameters => {
35 additionalProperties => 0,
36 properties => {},
37 },
38 returns => {
39 type => 'array',
40 items => {
41 type => "object",
42 properties => {
43 subdir => { type => 'string' },
44 },
45 },
46 links => [ { rel => 'child', href => "{subdir}" } ],
47 },
48 code => sub {
49 my ($param) = @_;
50
4b0c6af3
DM
51 my $res = [
52 { subdir => 'ticket' },
53 { subdir => 'password' },
54 { subdir => 'users' },
55 ];
1360e6f0
DM
56
57 return $res;
58 }});
59
60
87ccdf70
DC
61my $create_or_verify_ticket = sub {
62 my ($rpcenv, $username, $pw_or_ticket, $otp, $path) = @_;
1360e6f0
DM
63
64 my $ticketuser;
c2c88208
DM
65
66 if ($pw_or_ticket =~ m/^PMGQUAR:/) {
6060e345
DM
67 my $ticketuser = PMG::Ticket::verify_quarantine_ticket($pw_or_ticket);
68 if ($ticketuser eq $username) {
69 my $csrftoken = PMG::Ticket::assemble_csrf_prevention_token($username);
70
71 return {
72 role => 'quser',
73 ticket => $pw_or_ticket,
74 username => $username,
75 CSRFPreventionToken => $csrftoken,
76 };
77 }
c2c88208
DM
78 }
79
80 my $role = PMG::AccessControl::check_user_enabled($rpcenv->{usercfg}, $username);
81
1360e6f0
DM
82 if (($ticketuser = PMG::Ticket::verify_ticket($pw_or_ticket, 1)) &&
83 ($ticketuser eq 'root@pam' || $ticketuser eq $username)) {
84 # valid ticket. Note: root@pam can create tickets for other users
1965ec69 85 } elsif ($path && PMG::Ticket::verify_vnc_ticket($pw_or_ticket, $username, $path, 1)) {
87ccdf70 86 # valid vnc ticket for $path
1360e6f0
DM
87 } else {
88 $username = PMG::AccessControl::authenticate_user($username, $pw_or_ticket, $otp);
89 }
90
87ccdf70
DC
91 if (defined($path)) {
92 # verify only
93 return { username => $username };
94 }
95
1360e6f0
DM
96 my $ticket = PMG::Ticket::assemble_ticket($username);
97 my $csrftoken = PMG::Ticket::assemble_csrf_prevention_token($username);
98
99 return {
c2c88208 100 role => $role,
1360e6f0
DM
101 ticket => $ticket,
102 username => $username,
103 CSRFPreventionToken => $csrftoken,
104 };
105};
106
107
108__PACKAGE__->register_method ({
109 name => 'get_ticket',
110 path => 'ticket',
111 method => 'GET',
112 permissions => { user => 'world' },
113 description => "Dummy. Useful for formaters which want to priovde a login page.",
114 parameters => {
115 additionalProperties => 0,
116 },
117 returns => { type => "null" },
118 code => sub { return undef; }});
119
120__PACKAGE__->register_method ({
121 name => 'create_ticket',
122 path => 'ticket',
123 method => 'POST',
124 permissions => {
125 description => "You need to pass valid credientials.",
126 user => 'world'
127 },
128 protected => 1, # else we can't access shadow files
129 description => "Create or verify authentication ticket.",
130 parameters => {
131 additionalProperties => 0,
132 properties => {
133 username => {
134 description => "User name",
135 type => 'string',
136 maxLength => 64,
137 },
3b0e7530 138 realm => get_standard_option('realm', {
1360e6f0
DM
139 description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.",
140 optional => 1,
141 }),
142 password => {
143 description => "The secret password. This can also be a valid ticket.",
144 type => 'string',
145 },
146 otp => {
147 description => "One-time password for Two-factor authentication.",
148 type => 'string',
149 optional => 1,
150 },
87ccdf70
DC
151 path => {
152 description => "Verify ticket, and check if user have access on 'path'",
153 type => 'string',
154 optional => 1,
155 maxLength => 64,
156 },
1360e6f0
DM
157 }
158 },
159 returns => {
160 type => "object",
161 properties => {
162 username => { type => 'string' },
163 ticket => { type => 'string', optional => 1},
164 CSRFPreventionToken => { type => 'string', optional => 1 },
d3d13358 165 role => { type => 'string', optional => 1},
1360e6f0
DM
166 }
167 },
168 code => sub {
169 my ($param) = @_;
170
171 my $username = $param->{username};
6c54c10a 172
3b0e7530
DM
173 if ($username !~ m/\@(pam|pmg|quarantine)$/) {
174 my $realm = $param->{realm} // 'quarantine';
175 $username .= "\@$realm";
6c54c10a 176 }
1360e6f0 177
3b0e7530
DM
178 $username = 'root@pam' if $username eq 'root@pmg';
179
f1c29260 180 my $rpcenv = PMG::RESTEnvironment->get();
1360e6f0 181
1360e6f0
DM
182 my $res;
183 eval {
87ccdf70
DC
184 $res = &$create_or_verify_ticket($rpcenv, $username,
185 $param->{password}, $param->{otp}, $param->{path});
1360e6f0
DM
186 };
187 if (my $err = $@) {
188 my $clientip = $rpcenv->get_client_ip() || '';
189 syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
190 # do not return any info to prevent user enumeration attacks
191 die PVE::Exception->new("authentication failure\n", code => 401);
192 }
193
194 syslog('info', "successful auth for user '$username'");
195
196 return $res;
197 }});
198
199__PACKAGE__->register_method ({
200 name => 'change_passsword',
201 path => 'password',
202 method => 'PUT',
203 protected => 1, # else we can't access shadow files
2a19e199
DM
204 permissions => {
205 description => "Each user is allowed to change his own password. Only root can change the password of another user.",
206 user => 'all',
207 },
1360e6f0
DM
208 description => "Change user password.",
209 parameters => {
210 additionalProperties => 0,
211 properties => {
212 userid => get_standard_option('userid'),
213 password => {
214 description => "The new password.",
215 type => 'string',
216 minLength => 5,
217 maxLength => 64,
218 },
219 }
220 },
221 returns => { type => "null" },
222 code => sub {
223 my ($param) = @_;
224
f1c29260 225 my $rpcenv = PMG::RESTEnvironment->get();
1360e6f0
DM
226 my $authuser = $rpcenv->get_user();
227
62ebb4bc 228 my ($userid, $ruid, $realm) = PMG::Utils::verify_username($param->{userid});
1360e6f0
DM
229
230 if ($authuser eq 'root@pam') {
231 # OK - root can change anything
232 } else {
6c54c10a 233 if ($realm eq 'pmg' && $authuser eq $userid) {
62ebb4bc 234 # OK - each enable user can change its own password
27ca2dae 235 PMG::AccessControl::check_user_enabled($rpcenv->{usercfg}, $userid);
1360e6f0
DM
236 } else {
237 raise_perm_exc();
238 }
239 }
240
be06bc52 241 PMG::AccessControl::set_user_password($userid, $param->{password});
1360e6f0
DM
242
243 syslog('info', "changed password for user '$userid'");
244
245 return undef;
246 }});
247
2481;