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