]> git.proxmox.com Git - pmg-api.git/blob - PMG/API2/AccessControl.pm
implement new '@quarantine' realm
[pmg-api.git] / PMG / API2 / AccessControl.pm
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;
8 use PMG::RESTEnvironment;
9 use PVE::RESTHandler;
10 use PVE::JSONSchema qw(get_standard_option);
11
12 use PMG::Utils;
13 use PMG::UserConfig;
14 use PMG::AccessControl;
15 use PMG::API2::Users;
16
17 use Data::Dumper;
18
19 use base qw(PVE::RESTHandler);
20
21 __PACKAGE__->register_method ({
22 subclass => "PMG::API2::Users",
23 path => 'users',
24 });
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
51 my $res = [
52 { subdir => 'ticket' },
53 { subdir => 'password' },
54 { subdir => 'users' },
55 ];
56
57 return $res;
58 }});
59
60
61 my $create_ticket = sub {
62 my ($rpcenv, $username, $pw_or_ticket, $otp) = @_;
63
64 my $ticketuser;
65
66 if ($pw_or_ticket =~ m/^PMGQUAR:/) {
67 PMG::Ticket::verify_quarantine_ticket($pw_or_ticket);
68
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 }
78
79 my $role = PMG::AccessControl::check_user_enabled($rpcenv->{usercfg}, $username);
80
81 if (($ticketuser = PMG::Ticket::verify_ticket($pw_or_ticket, 1)) &&
82 ($ticketuser eq 'root@pam' || $ticketuser eq $username)) {
83 # valid ticket. Note: root@pam can create tickets for other users
84 } else {
85 $username = PMG::AccessControl::authenticate_user($username, $pw_or_ticket, $otp);
86 }
87
88 my $ticket = PMG::Ticket::assemble_ticket($username);
89 my $csrftoken = PMG::Ticket::assemble_csrf_prevention_token($username);
90
91 return {
92 role => $role,
93 ticket => $ticket,
94 username => $username,
95 CSRFPreventionToken => $csrftoken,
96 };
97 };
98
99
100 __PACKAGE__->register_method ({
101 name => 'get_ticket',
102 path => 'ticket',
103 method => 'GET',
104 permissions => { user => 'world' },
105 description => "Dummy. Useful for formaters which want to priovde a login page.",
106 parameters => {
107 additionalProperties => 0,
108 },
109 returns => { type => "null" },
110 code => sub { return undef; }});
111
112 __PACKAGE__->register_method ({
113 name => 'create_ticket',
114 path => 'ticket',
115 method => 'POST',
116 permissions => {
117 description => "You need to pass valid credientials.",
118 user => 'world'
119 },
120 protected => 1, # else we can't access shadow files
121 description => "Create or verify authentication ticket.",
122 parameters => {
123 additionalProperties => 0,
124 properties => {
125 username => {
126 description => "User name",
127 type => 'string',
128 maxLength => 64,
129 },
130 realm => get_standard_option('realm', {
131 description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.",
132 optional => 1,
133 }),
134 password => {
135 description => "The secret password. This can also be a valid ticket.",
136 type => 'string',
137 },
138 otp => {
139 description => "One-time password for Two-factor authentication.",
140 type => 'string',
141 optional => 1,
142 },
143 }
144 },
145 returns => {
146 type => "object",
147 properties => {
148 username => { type => 'string' },
149 ticket => { type => 'string', optional => 1},
150 CSRFPreventionToken => { type => 'string', optional => 1 },
151 role => { type => 'string', optional => 1},
152 }
153 },
154 code => sub {
155 my ($param) = @_;
156
157 my $username = $param->{username};
158
159 if ($param->{realm}) {
160 $username .= "\@$param->{realm}";
161 } elsif ($username !~ m/\@(pam|pve)$/) {
162 $username .= "\@quarantine";
163 }
164
165 my $rpcenv = PMG::RESTEnvironment->get();
166
167 my $res;
168 eval {
169 $res = &$create_ticket($rpcenv, $username, $param->{password}, $param->{otp});
170 };
171 if (my $err = $@) {
172 my $clientip = $rpcenv->get_client_ip() || '';
173 syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
174 # do not return any info to prevent user enumeration attacks
175 die PVE::Exception->new("authentication failure\n", code => 401);
176 }
177
178 syslog('info', "successful auth for user '$username'");
179
180 return $res;
181 }});
182
183 __PACKAGE__->register_method ({
184 name => 'change_passsword',
185 path => 'password',
186 method => 'PUT',
187 protected => 1, # else we can't access shadow files
188 permissions => {
189 description => "Each user is allowed to change his own password. Only root can change the password of another user.",
190 user => 'all',
191 },
192 description => "Change user password.",
193 parameters => {
194 additionalProperties => 0,
195 properties => {
196 userid => get_standard_option('userid'),
197 password => {
198 description => "The new password.",
199 type => 'string',
200 minLength => 5,
201 maxLength => 64,
202 },
203 }
204 },
205 returns => { type => "null" },
206 code => sub {
207 my ($param) = @_;
208
209 my $rpcenv = PMG::RESTEnvironment->get();
210 my $authuser = $rpcenv->get_user();
211
212 my ($userid, $ruid, $realm) = PMG::Utils::verify_username($param->{userid});
213
214 if ($authuser eq 'root@pam') {
215 # OK - root can change anything
216 } else {
217 if ($realm eq 'pmg' && $authuser eq $userid) {
218 # OK - each enable user can change its own password
219 PMG::AccessControl::check_user_enabled($rpcenv->{usercfg}, $userid);
220 } else {
221 raise_perm_exc();
222 }
223 }
224
225 PMG::AccessControl::set_user_password($userid, $param->{password});
226
227 syslog('info', "changed password for user '$userid'");
228
229 return undef;
230 }});
231
232 1;