12 use HTTP
::Request
::Common
;
13 use HTTP
::Status
qw(:constants :is status_message);
15 use PVE
::Exception
qw(raise raise_perm_exc);
17 use PVE
::AccessControl
;
18 use PVE
::RPCEnvironment
;
21 use Data
::Dumper
; # fixme: remove
23 my $cookie_name = 'PVEAuthCookie';
25 sub extract_auth_cookie
{
28 return undef if !$cookie;
30 my $ticket = ($cookie =~ /(?:^|\s)$cookie_name=([^;]*)/)[0];
32 if ($ticket && $ticket =~ m/^PVE%3A/) {
33 $ticket = uri_unescape
($ticket);
39 sub create_auth_cookie
{
42 my $encticket = uri_escape
($ticket);
43 return "${cookie_name}=$encticket; path=/; secure;";
46 sub format_response_data
{
47 my($format, $res, $uri) = @_;
49 my $data = $res->{data
};
50 my $info = $res->{info
};
52 my ($ct, $raw, $nocomp);
54 if ($format eq 'json') {
55 $ct = 'application/json;charset=UTF-8';
56 $raw = to_json
($data, {utf8
=> 1, allow_nonref
=> 1});
57 } elsif ($format eq 'html') {
58 $ct = 'text/html;charset=UTF-8';
59 $raw = "<html><body>";
60 if (!is_success
($res->{status
})) {
61 my $msg = $res->{message
} || '';
62 $raw .= "<h1>ERROR $res->{status} $msg</h1>";
64 my $lnk = PVE
::JSONSchema
::method_get_child_link
($info);
65 if ($lnk && $data && $data->{data
} && is_success
($res->{status
})) {
67 my $href = $lnk->{href
};
68 if ($href =~ m/^\{(\S+)\}$/) {
70 $uri =~ s/\/+$//; # remove trailing slash
71 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @{$data->{data
}}) {
74 if (defined(my $value = $elem->{$prop})) {
76 if (scalar(keys %$elem) > 1) {
77 my $tv = to_json
($elem, {allow_nonref
=> 1, canonical
=> 1});
78 $raw .= "<a href='$uri/$value'>$value</a> <pre>$tv</pre><br>";
80 $raw .= "<a href='$uri/$value'>$value</a><br>";
88 $raw .= encode_entities
(to_json
($data, {allow_nonref
=> 1, pretty
=> 1}));
91 $raw .= "</body></html>";
93 } elsif ($format eq 'png') {
96 # fixme: better to revove that whole png thing ?
101 if ($data && ref($data) && ref($data->{data
}) &&
102 $data->{data
}->{filename
} && defined($data->{data
}->{image
})) {
103 $filename = $data->{data
}->{filename
};
104 $raw = $data->{data
}->{image
};
107 } elsif ($format eq 'extjs') {
108 $ct = 'application/json;charset=UTF-8';
109 $raw = to_json
($data, {utf8
=> 1, allow_nonref
=> 1});
110 } elsif ($format eq 'htmljs') {
111 # we use this for extjs file upload forms
112 $ct = 'text/html;charset=UTF-8';
113 $raw = encode_entities
(to_json
($data, {allow_nonref
=> 1}));
114 } elsif ($format eq 'spiceconfig') {
115 $ct = 'application/x-virt-viewer;charset=UTF-8';
116 if ($data && ref($data) && ref($data->{data
})) {
117 $raw = "[virt-viewer]\n";
118 while (my ($key, $value) = each %{$data->{data
}}) {
119 $raw .= "$key=$value\n" if defined($value);
123 $ct = 'text/plain;charset=UTF-8';
124 $raw = to_json
($data, {utf8
=> 1, allow_nonref
=> 1, pretty
=> 1});
127 return wantarray ?
($raw, $ct, $nocomp) : $raw;
130 sub prepare_response_data
{
131 my ($format, $res) = @_;
135 data
=> $res->{data
},
137 if (scalar(keys %{$res->{errors
}})) {
139 $new->{errors
} = $res->{errors
};
142 if ($format eq 'extjs' || $format eq 'htmljs') {
143 # HACK: extjs wants 'success' property instead of useful HTTP status codes
144 if (is_error
($res->{status
})) {
146 $new->{message
} = $res->{message
} || status_message
($res->{status
});
147 $new->{status
} = $res->{status
} || 200;
148 $res->{message
} = undef;
149 $res->{status
} = 200;
151 $new->{success
} = $success;
154 if ($success && $res->{total
}) {
155 $new->{total
} = $res->{total
};
158 if ($success && $res->{changes
}) {
159 $new->{changes
} = $res->{changes
};
165 my $exc_to_res = sub {
166 my ($err, $status) = @_;
168 $status = $status || HTTP_INTERNAL_SERVER_ERROR
;
171 if (ref($err) eq "PVE::Exception") {
172 $resp->{status
} = $err->{code
} || $status;
173 $resp->{errors
} = $err->{errors
} if $err->{errors
};
174 $resp->{message
} = $err->{msg
};
176 $resp->{status
} = $status;
177 $resp->{message
} = $err;
184 my ($rpcenv, $clientip, $method, $rel_uri, $ticket, $token) = @_;
186 # set environment variables
187 $rpcenv->set_user(undef);
188 $rpcenv->set_language('C'); # fixme:
189 $rpcenv->set_client_ip($clientip);
191 my $require_auth = 1;
193 # explicitly allow some calls without auth
194 if (($rel_uri eq '/access/domains' && $method eq 'GET') ||
195 ($rel_uri eq '/access/ticket' && $method eq 'POST')) {
199 my ($username, $age);
205 die "No ticket\n" if !$ticket;
207 ($username, $age) = PVE
::AccessControl
::verify_ticket
($ticket);
209 $rpcenv->set_user($username);
211 if ($method eq 'POST' && $rel_uri =~ m
|^/nodes/([^/]+)/storage
/([^/]+)/upload
$|) {
212 my ($node, $storeid) = ($1, $2);
213 # we disable CSRF checks if $isUpload is set,
214 # to improve security we check user upload permission here
215 my $perm = { check
=> ['perm', "/storage/$storeid", ['Datastore.AllocateTemplate']] };
216 $rpcenv->check_api2_permissions($perm, $username, {});
220 # we skip CSRF check for file upload, because it is
221 # difficult to pass CSRF HTTP headers with native html forms,
222 # and it should not be necessary at all.
223 PVE
::AccessControl
::verify_csrf_prevention_token
($username, $token)
224 if !$isUpload && ($EUID != 0) && ($method ne 'GET');
232 isUpload
=> $isUpload,
237 my ($rpcenv, $clientip, $method, $rel_uri, $auth, $params) = @_;
240 my ($handler, $info) = PVE
::API2-
>find_handler($method, $rel_uri, $uri_param);
241 if (!$handler || !$info) {
243 status
=> HTTP_NOT_IMPLEMENTED
,
244 message
=> "Method '$method $rel_uri' not implemented",
248 foreach my $p (keys %{$params}) {
249 if (defined($uri_param->{$p})) {
251 status
=> HTTP_BAD_REQUEST
,
252 message
=> "Parameter verification failed - duplicate parameter '$p'",
255 $uri_param->{$p} = $params->{$p};
258 # check access permissions
259 eval { $rpcenv->check_api2_permissions($info->{permissions
}, $auth->{userid
}, $uri_param); };
261 return &$exc_to_res($err, HTTP_FORBIDDEN
);
264 if ($info->{proxyto
}) {
267 my $pn = $info->{proxyto
};
268 my $node = $uri_param->{$pn};
269 die "proxy parameter '$pn' does not exists" if !$node;
271 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
272 die "unable to proxy file uploads" if $auth->{isUpload
};
273 $remip = PVE
::Cluster
::remote_node_ip
($node);
277 return &$exc_to_res($err);
280 return { proxy
=> $remip, proxy_params
=> $params };
284 if ($info->{protected
} && ($EUID != 0)) {
285 return { proxy
=> 'localhost' , proxy_params
=> $params }
289 info
=> $info, # useful to format output
294 $resp->{data
} = $handler->handle($info, $uri_param);
296 if (my $count = $rpcenv->get_result_attrib('total')) {
297 $resp->{total
} = $count;
299 if (my $diff = $rpcenv->get_result_attrib('changes')) {
300 $resp->{changes
} = $diff;
304 return &$exc_to_res($err);