]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/HTTPServer.pm
expose font-logos via API server and load in UI
[pve-manager.git] / PVE / HTTPServer.pm
index ce8957258d2b0806f9ef0fc9858a22a69409c502..dabdf7f3e5ce64f345da8bcc4da6f54d4de6e91d 100755 (executable)
@@ -7,10 +7,11 @@ use PVE::SafeSyslog;
 use PVE::INotify;
 use PVE::Tools;
 use PVE::APIServer::AnyEvent;
-use PVE::Exception qw(raise_param_exc raise);
+use PVE::Exception qw(raise_param_exc raise_perm_exc raise);
 
 use PVE::RPCEnvironment;
 use PVE::AccessControl;
+use PVE::CertCache;
 use PVE::Cluster;
 use PVE::API2Tools;
 
@@ -24,11 +25,12 @@ sub new {
     my ($this, %args) = @_;
 
     my $class = ref($this) || $this;
-
     my $self = $class->SUPER::new(%args);
-    
+
     $self->{rpcenv} = PVE::RPCEnvironment->init(
-       $self->{trusted_env} ? 'priv' : 'pub', atfork =>  sub { $self-> atfork_handler() });
+       $self->{trusted_env} ? 'priv' : 'pub',
+       atfork => sub { $self->atfork_handler() },
+    );
 
     return $self;
 }
@@ -37,7 +39,6 @@ sub verify_spice_connect_url {
     my ($self, $connect_str) = @_;
 
     my $rpcenv = $self->{rpcenv};
-
     $rpcenv->init_request();
 
     my ($vmid, $node, $port) = PVE::AccessControl::verify_spice_connect_url($connect_str);
@@ -47,12 +48,11 @@ sub verify_spice_connect_url {
 
 sub generate_csrf_prevention_token {
     my ($username) = @_;
-
     return PVE::AccessControl::assemble_csrf_prevention_token($username);
 }
 
 sub auth_handler {
-    my ($self, $method, $rel_uri, $ticket, $token, $peer_host) = @_;
+    my ($self, $method, $rel_uri, $ticket, $token, $api_token, $peer_host) = @_;
 
     my $rpcenv = $self->{rpcenv};
 
@@ -68,7 +68,9 @@ sub auth_handler {
 
     # explicitly allow some calls without auth
     if (($rel_uri eq '/access/domains' && $method eq 'GET') ||
-       ($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST'))) {
+       ($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST')) ||
+       ($rel_uri eq '/access/openid/login' &&  $method eq 'POST') ||
+       ($rel_uri eq '/access/openid/auth-url' &&  $method eq 'POST')) {
        $require_auth = 0;
     }
 
@@ -77,36 +79,41 @@ sub auth_handler {
     my $isUpload = 0;
 
     if ($require_auth) {
+       if ($api_token) {
+           # the token-ID `<user>@<realm>!<tokenname>` is the user for token based authentication
+           $username = PVE::AccessControl::verify_token($api_token);
+       } else {
+           die "No ticket\n" if !$ticket;
 
-       die "No ticket\n" if !$ticket;
-
-       ($username, $age, my $tfa_info) = PVE::AccessControl::verify_ticket($ticket);
+           ($username, $age, my $tfa_info) = PVE::AccessControl::verify_ticket($ticket);
+           $rpcenv->check_user_enabled($username);
 
-       if (defined($tfa_info)) {
-           if (defined(my $challenge = $tfa_info->{challenge})) {
-               $rpcenv->set_u2f_challenge($challenge);
+           if (defined($tfa_info)) {
+               if (defined(my $challenge = $tfa_info->{challenge})) {
+                   $rpcenv->set_u2f_challenge($challenge);
+               }
+               die "No ticket\n" if ($rel_uri ne '/access/tfa' || $method ne 'POST');
            }
-           die "No ticket\n"
-               if ($rel_uri ne '/access/tfa' || $method ne 'POST');
        }
 
        $rpcenv->set_user($username);
 
        if ($method eq 'POST' && $rel_uri =~ m|^/nodes/([^/]+)/storage/([^/]+)/upload$|) {
            my ($node, $storeid) = ($1, $2);
-           # we disable CSRF checks if $isUpload is set,
-           # to improve security we check user upload permission here
+           # CSRF check are omitted if $isUpload is set, so check user upload permission here
            my $perm = { check => ['perm', "/storage/$storeid", ['Datastore.AllocateTemplate']] };
            $rpcenv->check_api2_permissions($perm, $username, {});
            $isUpload = 1;
        }
 
-       # we skip CSRF check for file upload, because it is
-       # difficult to pass CSRF HTTP headers with native html forms,
-       # and it should not be necessary at all.
-       my $euid = $>;
-       PVE::AccessControl::verify_csrf_prevention_token($username, $token)
-           if !$isUpload && ($euid != 0) && ($method ne 'GET');
+       # Skip CSRF check for file upload (difficult to pass CSRF header with native html forms).
+       # Also skip the check with API tokens, as one of the design goals of API tokens was to
+       # provide stateless API access without requiring round-trips to get such CSRF tokens.
+       # CSRF-prevention also does not make much sense outside of the browser context.
+       if ($method ne 'GET' && !($api_token || $isUpload)) {
+           my $euid = $>;
+           PVE::AccessControl::verify_csrf_prevention_token($username, $token) if $euid != 0;
+       }
     }
 
     return {
@@ -115,6 +122,7 @@ sub auth_handler {
        userid => $username,
        age => $age,
        isUpload => $isUpload,
+       api_token => $api_token,
     };
 }
 
@@ -135,13 +143,18 @@ sub rest_handler {
        ($handler, $info) = PVE::API2->find_handler($method, $rel_uri, $uri_param);
        return if !$handler || !$info;
 
-       foreach my $p (keys %{$params}) {
-           if (defined($uri_param->{$p})) {
-               raise_param_exc({$p =>  "duplicate parameter (already defined in URI)"});
+       for my $p (sort keys %{$params}) {
+           if (defined($uri_param->{$p}) && $uri_param->{$p} ne $params->{$p}) {
+               raise_param_exc({
+                   $p => "duplicate parameter (already defined in URI) with conflicting values!"
+               });
            }
            $uri_param->{$p} = $params->{$p};
        }
 
+       raise_perm_exc("URI '$rel_uri' not available with API token, need proper ticket.\n")
+           if $auth->{api_token} && !$info->{allowtoken};
+
        # check access permissions
        $rpcenv->check_api2_permissions($info->{permissions}, $auth->{userid}, $uri_param);
 
@@ -198,21 +211,18 @@ sub rest_handler {
 
 sub check_cert_fingerprint {
     my ($self, $cert) = @_;
-
-    return PVE::Cluster::check_cert_fingerprint($cert);
+    return PVE::CertCache::check_cert_fingerprint($cert);
 }
 
 sub initialize_cert_cache {
     my ($self, $node) = @_;
-
-    PVE::Cluster::initialize_cert_cache($node);
+    PVE::CertCache::initialize_cert_cache($node);
 }
 
 sub remote_node_ip {
     my ($self, $node) = @_;
 
     my $remip = PVE::Cluster::remote_node_ip($node);
-
     die "unable to get remote IP address for node '$node'\n" if !$remip;
 
     return $remip;