]>
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 | ||
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 }, | |
d3d13358 | 134 | role => { type => 'string', optional => 1}, |
1360e6f0 DM |
135 | } |
136 | }, | |
137 | code => sub { | |
138 | my ($param) = @_; | |
139 | ||
140 | my $username = $param->{username}; | |
141 | $username .= "\@$param->{realm}" if $param->{realm}; | |
142 | ||
f1c29260 | 143 | my $rpcenv = PMG::RESTEnvironment->get(); |
1360e6f0 | 144 | |
1360e6f0 DM |
145 | my $res; |
146 | eval { | |
d3d13358 | 147 | my $role = PMG::AccessControl::check_user_enabled($username); |
1360e6f0 | 148 | $res = &$create_ticket($rpcenv, $username, $param->{password}, $param->{otp}); |
d3d13358 | 149 | $res->{role} = $role; |
1360e6f0 DM |
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 | |
2a19e199 DM |
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 | }, | |
1360e6f0 DM |
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 | ||
f1c29260 | 189 | my $rpcenv = PMG::RESTEnvironment->get(); |
1360e6f0 DM |
190 | my $authuser = $rpcenv->get_user(); |
191 | ||
62ebb4bc | 192 | my ($userid, $ruid, $realm) = PMG::Utils::verify_username($param->{userid}); |
1360e6f0 DM |
193 | |
194 | if ($authuser eq 'root@pam') { | |
195 | # OK - root can change anything | |
196 | } else { | |
197 | if ($authuser eq $userid) { | |
62ebb4bc DM |
198 | # OK - each enable user can change its own password |
199 | PMG::AccessControl::check_user_enabled($userid); | |
1360e6f0 DM |
200 | } else { |
201 | raise_perm_exc(); | |
202 | } | |
203 | } | |
204 | ||
be06bc52 | 205 | PMG::AccessControl::set_user_password($userid, $param->{password}); |
1360e6f0 DM |
206 | |
207 | syslog('info', "changed password for user '$userid'"); | |
208 | ||
209 | return undef; | |
210 | }}); | |
211 | ||
212 | 1; |