]> git.proxmox.com Git - pve-manager.git/blob - PVE/REST.pm
update descriptions, error messages
[pve-manager.git] / PVE / REST.pm
1 package PVE::REST;
2
3 use warnings;
4 use strict;
5 use English;
6 use PVE::Cluster;
7 use PVE::SafeSyslog;
8 use PVE::Tools;
9 use JSON;
10 use LWP::UserAgent;
11 use HTTP::Request::Common;
12 use HTTP::Status qw(:constants :is status_message);
13 use HTML::Entities;
14 use PVE::Exception qw(raise raise_perm_exc);
15 use PVE::JSONSchema;
16 use PVE::AccessControl;
17 use PVE::RPCEnvironment;
18 use URI::Escape;
19
20 use Data::Dumper; # fixme: remove
21
22 my $cookie_name = 'PVEAuthCookie';
23
24 my $base_handler_class;
25
26 sub set_base_handler_class {
27 my ($class) = @_;
28
29 die "base_handler_class already defined" if $base_handler_class;
30
31 $base_handler_class = $class;
32 }
33
34 sub extract_auth_cookie {
35 my ($cookie) = @_;
36
37 return undef if !$cookie;
38
39 my $ticket = ($cookie =~ /(?:^|\s)$cookie_name=([^;]*)/)[0];
40
41 if ($ticket && $ticket =~ m/^PVE%3A/) {
42 $ticket = uri_unescape($ticket);
43 }
44
45 return $ticket;
46 }
47
48 sub create_auth_cookie {
49 my ($ticket) = @_;
50
51 my $encticket = uri_escape($ticket);
52 return "${cookie_name}=$encticket; path=/; secure;";
53 }
54
55 my $exc_to_res = sub {
56 my ($info, $err, $status) = @_;
57
58 $status = $status || HTTP_INTERNAL_SERVER_ERROR;
59
60 my $resp = { info => $info };
61 if (ref($err) eq "PVE::Exception") {
62 $resp->{status} = $err->{code} || $status;
63 $resp->{errors} = $err->{errors} if $err->{errors};
64 $resp->{message} = $err->{msg};
65 } else {
66 $resp->{status} = $status;
67 $resp->{message} = $err;
68 }
69
70 return $resp;
71 };
72
73 sub auth_handler {
74 my ($rpcenv, $clientip, $method, $rel_uri, $ticket, $token) = @_;
75
76 # set environment variables
77 $rpcenv->set_user(undef);
78 $rpcenv->set_language('C'); # fixme:
79 $rpcenv->set_client_ip($clientip);
80
81 my $require_auth = 1;
82
83 # explicitly allow some calls without auth
84 if (($rel_uri eq '/access/domains' && $method eq 'GET') ||
85 ($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST'))) {
86 $require_auth = 0;
87 }
88
89 my ($username, $age);
90
91 my $isUpload = 0;
92
93 if ($require_auth) {
94
95 die "No ticket\n" if !$ticket;
96
97 ($username, $age) = PVE::AccessControl::verify_ticket($ticket);
98
99 $rpcenv->set_user($username);
100
101 if ($method eq 'POST' && $rel_uri =~ m|^/nodes/([^/]+)/storage/([^/]+)/upload$|) {
102 my ($node, $storeid) = ($1, $2);
103 # we disable CSRF checks if $isUpload is set,
104 # to improve security we check user upload permission here
105 my $perm = { check => ['perm', "/storage/$storeid", ['Datastore.AllocateTemplate']] };
106 $rpcenv->check_api2_permissions($perm, $username, {});
107 $isUpload = 1;
108 }
109
110 # we skip CSRF check for file upload, because it is
111 # difficult to pass CSRF HTTP headers with native html forms,
112 # and it should not be necessary at all.
113 PVE::AccessControl::verify_csrf_prevention_token($username, $token)
114 if !$isUpload && ($EUID != 0) && ($method ne 'GET');
115 }
116
117 return {
118 ticket => $ticket,
119 token => $token,
120 userid => $username,
121 age => $age,
122 isUpload => $isUpload,
123 };
124 }
125
126 sub rest_handler {
127 my ($rpcenv, $clientip, $method, $rel_uri, $auth, $params) = @_;
128
129 die "no base handler - internal error" if !$base_handler_class;
130
131 my $uri_param = {};
132 my ($handler, $info) = $base_handler_class->find_handler($method, $rel_uri, $uri_param);
133 if (!$handler || !$info) {
134 return {
135 status => HTTP_NOT_IMPLEMENTED,
136 message => "Method '$method $rel_uri' not implemented",
137 };
138 }
139
140 foreach my $p (keys %{$params}) {
141 if (defined($uri_param->{$p})) {
142 return {
143 status => HTTP_BAD_REQUEST,
144 message => "Parameter verification failed - duplicate parameter '$p'",
145 };
146 }
147 $uri_param->{$p} = $params->{$p};
148 }
149
150 # check access permissions
151 eval { $rpcenv->check_api2_permissions($info->{permissions}, $auth->{userid}, $uri_param); };
152 if (my $err = $@) {
153 return &$exc_to_res($info, $err, HTTP_FORBIDDEN);
154 }
155
156 if ($info->{proxyto}) {
157 my $remip;
158 eval {
159 my $pn = $info->{proxyto};
160 my $node = $uri_param->{$pn};
161 die "proxy parameter '$pn' does not exists" if !$node;
162
163 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
164 die "unable to proxy file uploads" if $auth->{isUpload};
165 $remip = PVE::Cluster::remote_node_ip($node);
166 }
167 };
168 if (my $err = $@) {
169 return &$exc_to_res($info, $err);
170 }
171 if ($remip) {
172 return { proxy => $remip, proxy_params => $params };
173 }
174 }
175
176 if ($info->{protected} && ($EUID != 0)) {
177 return { proxy => 'localhost' , proxy_params => $params }
178 }
179
180 my $resp = {
181 info => $info, # useful to format output
182 status => HTTP_OK,
183 };
184
185 eval {
186 $resp->{data} = $handler->handle($info, $uri_param);
187
188 if (my $count = $rpcenv->get_result_attrib('total')) {
189 $resp->{total} = $count;
190 }
191 if (my $diff = $rpcenv->get_result_attrib('changes')) {
192 $resp->{changes} = $diff;
193 }
194 };
195 if (my $err = $@) {
196 return &$exc_to_res($info, $err);
197 }
198
199 return $resp;
200 }
201
202 1;