]>
Commit | Line | Data |
---|---|---|
1360e6f0 DM |
1 | package PMG::API2::AccessControl; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::Exception qw(raise raise_perm_exc); | |
7 | use PVE::SafeSyslog; | |
9d82c6bc | 8 | use PMG::RESTEnvironment; |
1360e6f0 DM |
9 | use PVE::RESTHandler; |
10 | use PVE::JSONSchema qw(get_standard_option); | |
11 | ||
62ebb4bc DM |
12 | use PMG::Utils; |
13 | use PMG::UserConfig; | |
1360e6f0 | 14 | use PMG::AccessControl; |
4b0c6af3 | 15 | use PMG::API2::Users; |
1360e6f0 DM |
16 | |
17 | use Data::Dumper; | |
18 | ||
19 | use 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 |
61 | my $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 | ||
248 | 1; |