]> git.proxmox.com Git - pve-access-control.git/blame - PVE/API2/AccessControl.pm
use new decode_utf8_parameters() to decode CGI parameters
[pve-access-control.git] / PVE / API2 / AccessControl.pm
CommitLineData
2c3a6c0a
DM
1package PVE::API2::AccessControl;
2
3use strict;
4use warnings;
5
6use PVE::SafeSyslog;
7use PVE::RPCEnvironment;
a427cecb 8use PVE::Cluster qw(cfs_read_file);
2c3a6c0a
DM
9use PVE::RESTHandler;
10use PVE::AccessControl;
11use PVE::JSONSchema qw(get_standard_option);
12use PVE::API2::Domains;
13use PVE::API2::User;
14use PVE::API2::Group;
15use PVE::API2::Role;
16use PVE::API2::ACL;
17
18use base qw(PVE::RESTHandler);
19
20__PACKAGE__->register_method ({
21 subclass => "PVE::API2::User",
22 path => 'users',
23});
24
25__PACKAGE__->register_method ({
26 subclass => "PVE::API2::Group",
27 path => 'groups',
28});
29
30__PACKAGE__->register_method ({
31 subclass => "PVE::API2::Role",
32 path => 'roles',
33});
34
35__PACKAGE__->register_method ({
36 subclass => "PVE::API2::ACL",
37 path => 'acl',
38});
39
40__PACKAGE__->register_method ({
41 subclass => "PVE::API2::Domains",
42 path => 'domains',
43});
44
45__PACKAGE__->register_method ({
46 name => 'index',
47 path => '',
48 method => 'GET',
49 description => "Directory index.",
50 parameters => {
51 additionalProperties => 0,
52 properties => {},
53 },
54 returns => {
55 type => 'array',
56 items => {
57 type => "object",
58 properties => {
59 subdir => { type => 'string' },
60 },
61 },
62 links => [ { rel => 'child', href => "{subdir}" } ],
63 },
64 code => sub {
65 my ($param) = @_;
66
67 my $res = [];
68
69 my $ma = __PACKAGE__->method_attributes();
70
71 foreach my $info (@$ma) {
72 next if !$info->{subclass};
73
74 my $subpath = $info->{match_re}->[0];
75
76 push @$res, { subdir => $subpath };
77 }
78
79 push @$res, { subdir => 'ticket' };
80
81 return $res;
82 }});
83
adf8d771
DM
84
85my $verify_auth = sub {
86 my ($rpcenv, $username, $pw_or_ticket, $path, $privs) = @_;
87
88 my $normpath = PVE::AccessControl::normalize_path($path);
89
90 my $ticketuser;
91 if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) &&
92 ($ticketuser eq $username)) {
93 # valid ticket
94 } elsif (PVE::AccessControl::verify_vnc_ticket($pw_or_ticket, $username, $normpath, 1)) {
95 # valid vnc ticket
96 } else {
97 $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket);
98 }
99
100 my $privlist = [ PVE::Tools::split_list($privs) ];
101 if (!($normpath && scalar(@$privlist) && $rpcenv->check($username, $normpath, $privlist))) {
102 die "no permission ($path, $privs)\n";
103 }
104
105 return { username => $username };
106};
107
108my $create_ticket = sub {
109 my ($rpcenv, $username, $pw_or_ticket) = @_;
110
111 my $ticketuser;
112 if (($ticketuser = PVE::AccessControl::verify_ticket($pw_or_ticket, 1)) &&
113 ($ticketuser eq 'root@pam' || $ticketuser eq $username)) {
114 # valid ticket. Note: root@pam can create tickets for other users
115 } else {
116 $username = PVE::AccessControl::authenticate_user($username, $pw_or_ticket);
117 }
118
119 my $ticket = PVE::AccessControl::assemble_ticket($username);
120 my $csrftoken = PVE::AccessControl::assemble_csrf_prevention_token($username);
121
122 return {
123 ticket => $ticket,
124 username => $username,
125 CSRFPreventionToken => $csrftoken,
126 };
127};
128
2c3a6c0a
DM
129__PACKAGE__->register_method ({
130 name => 'create_ticket',
131 path => 'ticket',
132 method => 'POST',
133 permissions => { user => 'world' },
134 protected => 1, # else we can't access shadow files
adf8d771 135 description => "Create or verify authentication ticket.",
2c3a6c0a
DM
136 parameters => {
137 additionalProperties => 0,
138 properties => {
139 username => {
140 description => "User name",
141 type => 'string',
142 maxLength => 64,
143 },
144 realm => get_standard_option('realm', {
145 description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.",
146 optional => 1}),
147 password => {
148 description => "The secret password. This can also be a valid ticket.",
149 type => 'string',
150 },
151 path => {
adf8d771 152 description => "Verify ticket, and check if user have access 'privs' on 'path'",
2c3a6c0a
DM
153 type => 'string',
154 requires => 'privs',
155 optional => 1,
156 maxLength => 64,
157 },
158 privs => {
adf8d771 159 description => "Verify ticket, and check if user have access 'privs' on 'path'",
2c3a6c0a
DM
160 type => 'string' , format => 'pve-priv-list',
161 requires => 'path',
162 optional => 1,
163 maxLength => 64,
164 },
165 }
166 },
167 returns => {
168 type => "object",
169 properties => {
2c3a6c0a 170 username => { type => 'string' },
adf8d771
DM
171 ticket => { type => 'string', optional => 1},
172 CSRFPreventionToken => { type => 'string', optional => 1 },
2c3a6c0a
DM
173 }
174 },
175 code => sub {
176 my ($param) = @_;
177
178 my $username = $param->{username};
179 $username .= "\@$param->{realm}" if $param->{realm};
180
181 my $rpcenv = PVE::RPCEnvironment::get();
2c3a6c0a 182
adf8d771 183 my $res;
2c3a6c0a 184
adf8d771 185 eval {
7070c1ae
DM
186 # test if user exists and is enabled
187 $rpcenv->check_user_enabled($username);
188
2c3a6c0a 189 if ($param->{path} && $param->{privs}) {
adf8d771
DM
190 $res = &$verify_auth($rpcenv, $username, $param->{password},
191 $param->{path}, $param->{privs});
2c3a6c0a 192 } else {
adf8d771 193 $res = &$create_ticket($rpcenv, $username, $param->{password});
2c3a6c0a 194 }
2c3a6c0a
DM
195 };
196 if (my $err = $@) {
adf8d771 197 my $clientip = $rpcenv->get_client_ip() || '';
2c3a6c0a
DM
198 syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
199 die $err;
200 }
201
202 PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
203
adf8d771 204 return $res;
2c3a6c0a
DM
205 }});
206
2071;