import cfs_read_file
[pve-access-control.git] / PVE / API2 / AccessControl.pm
1 package PVE::API2::AccessControl;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::RPCEnvironment;
8 use PVE::Cluster qw(cfs_read_file);
9 use PVE::RESTHandler;
10 use PVE::AccessControl;
11 use PVE::JSONSchema qw(get_standard_option);
12 use PVE::API2::Domains;
13 use PVE::API2::User;
14 use PVE::API2::Group;
15 use PVE::API2::Role;
16 use PVE::API2::ACL;
17
18 use 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
84 __PACKAGE__->register_method ({
85     name => 'create_ticket', 
86     path => 'ticket', 
87     method => 'POST',
88     permissions => { user => 'world' },
89     protected => 1, # else we can't access shadow files
90     description => "Create authentication ticket.",
91     parameters => {
92         additionalProperties => 0,
93         properties => {
94             username => {
95                 description => "User name",
96                 type => 'string',
97                 maxLength => 64,
98             },
99             realm =>  get_standard_option('realm', {
100                 description => "You can optionally pass the realm using this parameter. Normally the realm is simply added to the username <username>\@<relam>.",
101                 optional => 1}),
102             password => { 
103                 description => "The secret password. This can also be a valid ticket.",
104                 type => 'string',
105             },
106             path => {
107                 description => "Only create ticket if user have access 'privs' on 'path'",
108                 type => 'string',
109                 requires => 'privs',
110                 optional => 1,
111                 maxLength => 64,
112             },
113             privs => { 
114                 description => "Only create ticket if user have access 'privs' on 'path'",
115                 type => 'string' , format => 'pve-priv-list',
116                 requires => 'path',
117                 optional => 1,
118                 maxLength => 64,
119             },
120         }
121     },
122     returns => {
123         type => "object",
124         properties => {
125             ticket => { type => 'string' },
126             username => { type => 'string' },
127             CSRFPreventionToken => { type => 'string' },
128         }
129     },
130     code => sub {
131         my ($param) = @_;
132     
133         my $username = $param->{username};
134         $username .= "\@$param->{realm}" if $param->{realm};
135
136         my $rpcenv = PVE::RPCEnvironment::get();
137         my $clientip = $rpcenv->get_client_ip() || '';
138
139         my $ticket;
140         my $token;
141         eval {
142
143             if ($param->{path} && $param->{privs}) {
144                 my $privs = [ PVE::Tools::split_list($param->{privs}) ];
145                 my $path = PVE::AccessControl::normalize_path($param->{path});
146                 if (!($path && scalar(@$privs) && $rpcenv->check($username, $path, $privs))) {
147                     die "no permission ($param->{path}, $param->{privs})\n";
148                 }
149             }
150
151             my $tmp;
152             if (($tmp = PVE::AccessControl::verify_ticket($param->{password}, 1)) &&
153                 ($tmp eq 'root@pam' || $tmp eq $username)) {
154                 # got valid ticket
155                 # Note: root@pam can create tickets for other users
156                 
157                 # test if user exists and is enabled
158                 my $usercfg = cfs_read_file('user.cfg');
159                 die "no such user ('$username')\n" if !user_enabled($usercfg, $username);
160             } else {
161                 $username = PVE::AccessControl::authenticate_user($username, $param->{password});
162             }
163             $ticket = PVE::AccessControl::assemble_ticket($username);
164             $token = PVE::AccessControl::assemble_csrf_prevention_token($username);
165         };
166         if (my $err = $@) {
167             syslog('err', "authentication failure; rhost=$clientip user=$username msg=$err");
168             die $err;
169         }
170
171         PVE::Cluster::log_msg('info', 'root@pam', "successful auth for user '$username'");
172
173         return {
174             ticket => $ticket,
175             username => $username,
176             CSRFPreventionToken => $token,
177         };
178     }});
179
180 1;