]> git.proxmox.com Git - pmg-api.git/blob - PMG/API2/AccessControl.pm
move users API to /access/users
[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 PVE::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 }
135 },
136 code => sub {
137 my ($param) = @_;
138
139 my $username = $param->{username};
140 $username .= "\@$param->{realm}" if $param->{realm};
141
142 my $rpcenv = PVE::RESTEnvironment::get();
143
144 my $res;
145 eval {
146 PMG::AccessControl::check_user_enabled($username);
147 $res = &$create_ticket($rpcenv, $username, $param->{password}, $param->{otp});
148 };
149 if (my $err = $@) {
150 my $clientip = $rpcenv->get_client_ip() || '';
151 syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
152 # do not return any info to prevent user enumeration attacks
153 die PVE::Exception->new("authentication failure\n", code => 401);
154 }
155
156 syslog('info', "successful auth for user '$username'");
157
158 return $res;
159 }});
160
161 __PACKAGE__->register_method ({
162 name => 'change_passsword',
163 path => 'password',
164 method => 'PUT',
165 protected => 1, # else we can't access shadow files
166 description => "Change user password.",
167 parameters => {
168 additionalProperties => 0,
169 properties => {
170 userid => get_standard_option('userid'),
171 password => {
172 description => "The new password.",
173 type => 'string',
174 minLength => 5,
175 maxLength => 64,
176 },
177 }
178 },
179 returns => { type => "null" },
180 code => sub {
181 my ($param) = @_;
182
183 my $rpcenv = PVE::RESTEnvironment::get();
184 my $authuser = $rpcenv->get_user();
185
186 my ($userid, $ruid, $realm) = PMG::Utils::verify_username($param->{userid});
187
188 if ($authuser eq 'root@pam') {
189 # OK - root can change anything
190 } else {
191 if ($authuser eq $userid) {
192 # OK - each enable user can change its own password
193 PMG::AccessControl::check_user_enabled($userid);
194 } else {
195 raise_perm_exc();
196 }
197 }
198
199 PMG::AccessControl::domain_set_password($realm, $ruid, $param->{password});
200
201 syslog('info', "changed password for user '$userid'");
202
203 return undef;
204 }});
205
206 1;