[pve-devel] [PATCH v2 http-server 2/4] anyevent: move auth and request handling into separate subroutine
Max Carrara
m.carrara at proxmox.com
Fri Mar 3 18:29:49 CET 2023
The part responsible for authentication and subsequent request
handling is moved into the new `authenticate_and_handle_request`
subroutine.
If `authenticate_and_handle_request` doesn't return early, it returns
`1` for further control flow purposes.
Some minor things are formatted or renamed for readability's sake.
Signed-off-by: Max Carrara <m.carrara at proxmox.com>
---
src/PVE/APIServer/AnyEvent.pm | 324 +++++++++++++++++++---------------
1 file changed, 180 insertions(+), 144 deletions(-)
diff --git a/src/PVE/APIServer/AnyEvent.pm b/src/PVE/APIServer/AnyEvent.pm
index 294c035..636502b 100644
--- a/src/PVE/APIServer/AnyEvent.pm
+++ b/src/PVE/APIServer/AnyEvent.pm
@@ -1314,156 +1314,13 @@ sub unshift_read_header {
my $r = $reqstate->{request};
if ($line eq '') {
- my $path = uri_unescape($r->uri->path());
- my $method = $r->method();
-
$r->push_header($state->{key}, $state->{val})
if $state->{key};
- my $base_uri = $self->{base_uri};
-
- my $len = $r->header('Content-Length');
- my $host_header = $r->header('Host');
-
$self->process_header($reqstate) or return;
# header processing complete - authenticate now
+ $self->authenticate_and_handle_request($reqstate) or return;
- my $auth = {};
- if ($self->{spiceproxy}) {
- my $connect_str = $host_header;
- my ($vmid, $node, $port) = $self->verify_spice_connect_url($connect_str);
- if (!(defined($vmid) && $node && $port)) {
- $self->error($reqstate, HTTP_UNAUTHORIZED, "invalid ticket");
- return;
- }
- $self->handle_spice_proxy_request($reqstate, $connect_str, $vmid, $node, $port);
- return;
- } elsif ($path =~ m/^\Q$base_uri\E/) {
- my $token = $r->header('CSRFPreventionToken');
- my $cookie = $r->header('Cookie');
- my $auth_header = $r->header('Authorization');
-
- # prefer actual cookie
- my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $self->{cookie_name});
-
- # fallback to cookie in 'Authorization' header
- $ticket = PVE::APIServer::Formatter::extract_auth_value($auth_header, $self->{cookie_name})
- if !$ticket;
-
- # finally, fallback to API token if no ticket has been provided so far
- my $api_token;
- $api_token = PVE::APIServer::Formatter::extract_auth_value($auth_header, $self->{apitoken_name})
- if !$ticket;
-
- my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
- if (!$format) {
- $self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no such uri");
- return;
- }
-
- eval {
- $auth = $self->auth_handler($method, $rel_uri, $ticket, $token, $api_token,
- $reqstate->{peer_host});
- };
- if (my $err = $@) {
- # HACK: see Note 1
- Net::SSLeay::ERR_clear_error();
- # always delay unauthorized calls by 3 seconds
- my $delay = 3;
-
- if (ref($err) eq "PVE::Exception") {
-
- $err->{code} ||= HTTP_INTERNAL_SERVER_ERROR,
- my $resp = HTTP::Response->new($err->{code}, $err->{msg});
- $self->response($reqstate, $resp, undef, 0, $delay);
-
- } elsif (my $formatter = PVE::APIServer::Formatter::get_login_formatter($format)) {
- my ($raw, $ct, $nocomp) =
- $formatter->($path, $auth, $self->{formatter_config});
- my $resp;
- if (ref($raw) && (ref($raw) eq 'HTTP::Response')) {
- $resp = $raw;
- } else {
- $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, "Login Required");
- $resp->header("Content-Type" => $ct);
- $resp->content($raw);
- }
- $self->response($reqstate, $resp, undef, $nocomp, $delay);
- } else {
- my $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, $err);
- $self->response($reqstate, $resp, undef, 0, $delay);
- }
- return;
- }
- }
-
- $reqstate->{log}->{userid} = $auth->{userid};
-
- if ($len) {
-
- if (!($method eq 'PUT' || $method eq 'POST')) {
- $self->error($reqstate, 501, "Unexpected content for method '$method'");
- return;
- }
-
- my $ctype = $r->header('Content-Type');
- my ($ct, $boundary) = $ctype ? parse_content_type($ctype) : ();
-
- if ($auth->{isUpload} && !$self->{trusted_env}) {
- die "upload 'Content-Type '$ctype' not implemented\n"
- if !($boundary && $ct && ($ct eq 'multipart/form-data'));
-
- die "upload without content length header not supported" if !$len;
-
- die "upload without content length header not supported" if !$len;
-
- $self->dprint("start upload $path $ct $boundary");
-
- my $tmpfilename = get_upload_filename();
- my $outfh = IO::File->new($tmpfilename, O_RDWR|O_CREAT|O_EXCL, 0600) ||
- die "unable to create temporary upload file '$tmpfilename'";
-
- $reqstate->{keep_alive} = 0;
-
- my $boundlen = length($boundary) + 8; # \015?\012--$boundary--\015?\012
-
- my $state = {
- size => $len,
- boundary => $boundary,
- ctx => Digest::MD5->new,
- boundlen => $boundlen,
- maxheader => 2048 + $boundlen, # should be large enough
- params => decode_urlencoded($r->url->query()),
- phase => 0,
- read => 0,
- post_size => 0,
- starttime => [gettimeofday],
- outfh => $outfh,
- };
- $reqstate->{tmpfilename} = $tmpfilename;
- $reqstate->{hdl}->on_read(sub {
- $self->file_upload_multipart($reqstate, $auth, $method, $path, $state);
- });
- return;
- }
-
- if ($len > $limit_max_post) {
- $self->error($reqstate, 501, "for data too large");
- return;
- }
-
- if (!$ct || $ct eq 'application/x-www-form-urlencoded' || $ct eq 'application/json') {
- $reqstate->{hdl}->unshift_read(chunk => $len, sub {
- my ($hdl, $data) = @_;
- $r->content($data);
- $self->handle_request($reqstate, $auth, $method, $path);
- });
- } else {
- $self->error($reqstate, 506, "upload 'Content-Type '$ctype' not implemented");
- }
- } else {
- $self->handle_request($reqstate, $auth, $method, $path);
- }
} elsif ($line =~ /^([^:\s]+)\s*:\s*(.*)/) {
$r->push_header($state->{key}, $state->{val}) if $state->{key};
($state->{key}, $state->{val}) = ($1, $2);
@@ -1531,6 +1388,185 @@ sub process_header {
return 1;
}
+sub authenticate_and_handle_request {
+ my ($self, $reqstate) = @_;
+
+ my $request = $reqstate->{request};
+ my $method = $request->method();
+
+ my $path = uri_unescape($request->uri->path());
+ my $base_uri = $self->{base_uri};
+
+ my $auth = {};
+
+ if ($self->{spiceproxy}) {
+ my $connect_str = $request->header('Host');
+ my ($vmid, $node, $port) = $self->verify_spice_connect_url($connect_str);
+
+ if (!(defined($vmid) && $node && $port)) {
+ $self->error($reqstate, HTTP_UNAUTHORIZED, "invalid ticket");
+ return;
+ }
+
+ $self->handle_spice_proxy_request($reqstate, $connect_str, $vmid, $node, $port);
+ return;
+
+ } elsif ($path =~ m/^\Q$base_uri\E/) {
+ my $token = $request->header('CSRFPreventionToken');
+ my $cookie = $request->header('Cookie');
+ my $auth_header = $request->header('Authorization');
+
+ # prefer actual cookie
+ my $ticket = PVE::APIServer::Formatter::extract_auth_value(
+ $cookie,
+ $self->{cookie_name}
+ );
+
+ # fallback to cookie in 'Authorization' header
+ if (!$ticket) {
+ $ticket = PVE::APIServer::Formatter::extract_auth_value(
+ $auth_header,
+ $self->{cookie_name}
+ );
+ }
+
+ # finally, fallback to API token if no ticket has been provided so far
+ my $api_token;
+ if (!$ticket) {
+ $api_token = PVE::APIServer::Formatter::extract_auth_value(
+ $auth_header,
+ $self->{apitoken_name}
+ );
+ }
+
+ my ($rel_uri, $format) = &$split_abs_uri($path, $self->{base_uri});
+ if (!$format) {
+ $self->error($reqstate, HTTP_NOT_IMPLEMENTED, "no such uri");
+ return;
+ }
+
+ eval {
+ $auth = $self->auth_handler(
+ $method,
+ $rel_uri,
+ $ticket,
+ $token,
+ $api_token,
+ $reqstate->{peer_host}
+ );
+ };
+ if (my $err = $@) {
+ # HACK: see Note 1
+ Net::SSLeay::ERR_clear_error();
+ # always delay unauthorized calls by 3 seconds
+ my $delay = 3;
+
+ if (ref($err) eq "PVE::Exception") {
+
+ $err->{code} ||= HTTP_INTERNAL_SERVER_ERROR,
+ my $resp = HTTP::Response->new($err->{code}, $err->{msg});
+ $self->response($reqstate, $resp, undef, 0, $delay);
+
+ } elsif (my $formatter = PVE::APIServer::Formatter::get_login_formatter($format)) {
+ my ($raw, $ct, $nocomp) =
+ $formatter->($path, $auth, $self->{formatter_config});
+
+ my $resp;
+ if (ref($raw) && (ref($raw) eq 'HTTP::Response')) {
+ $resp = $raw;
+
+ } else {
+ $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, "Login Required");
+ $resp->header("Content-Type" => $ct);
+ $resp->content($raw);
+ }
+
+ $self->response($reqstate, $resp, undef, $nocomp, $delay);
+
+ } else {
+ my $resp = HTTP::Response->new(HTTP_UNAUTHORIZED, $err);
+ $self->response($reqstate, $resp, undef, 0, $delay);
+ }
+
+ return;
+ }
+ }
+
+ $reqstate->{log}->{userid} = $auth->{userid};
+ my $len = $request->header('Content-Length');
+
+ if ($len) {
+
+ if (!($method eq 'PUT' || $method eq 'POST')) {
+ $self->error($reqstate, 501, "Unexpected content for method '$method'");
+ return;
+ }
+
+ my $ctype = $request->header('Content-Type');
+ my ($ct, $boundary) = $ctype ? parse_content_type($ctype) : ();
+
+ if ($auth->{isUpload} && !$self->{trusted_env}) {
+ die "upload 'Content-Type '$ctype' not implemented\n"
+ if !($boundary && $ct && ($ct eq 'multipart/form-data'));
+
+ die "upload without content length header not supported" if !$len;
+
+ die "upload without content length header not supported" if !$len;
+
+ $self->dprint("start upload $path $ct $boundary");
+
+ my $tmpfilename = get_upload_filename();
+ my $outfh = IO::File->new($tmpfilename, O_RDWR|O_CREAT|O_EXCL, 0600) ||
+ die "unable to create temporary upload file '$tmpfilename'";
+
+ $reqstate->{keep_alive} = 0;
+
+ my $boundlen = length($boundary) + 8; # \015?\012--$boundary--\015?\012
+
+ my $state = {
+ size => $len,
+ boundary => $boundary,
+ ctx => Digest::MD5->new,
+ boundlen => $boundlen,
+ maxheader => 2048 + $boundlen, # should be large enough
+ params => decode_urlencoded($request->url->query()),
+ phase => 0,
+ read => 0,
+ post_size => 0,
+ starttime => [gettimeofday],
+ outfh => $outfh,
+ };
+ $reqstate->{tmpfilename} = $tmpfilename;
+ $reqstate->{hdl}->on_read(sub {
+ $self->file_upload_multipart($reqstate, $auth, $method, $path, $state);
+ });
+
+ return;
+ }
+
+ if ($len > $limit_max_post) {
+ $self->error($reqstate, 501, "for data too large");
+ return;
+ }
+
+ if (!$ct || $ct eq 'application/x-www-form-urlencoded' || $ct eq 'application/json') {
+ $reqstate->{hdl}->unshift_read(chunk => $len, sub {
+ my ($hdl, $data) = @_;
+ $request->content($data);
+ $self->handle_request($reqstate, $auth, $method, $path);
+ });
+
+ } else {
+ $self->error($reqstate, 506, "upload 'Content-Type '$ctype' not implemented");
+ }
+
+ } else {
+ $self->handle_request($reqstate, $auth, $method, $path);
+ }
+
+ return 1;
+}
+
sub push_request_header {
my ($self, $reqstate) = @_;
--
2.30.2
More information about the pve-devel
mailing list