public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH access-control/manager/proxmox v3 0/3] fix #5076: Added Open ID audiences
@ 2025-06-03  9:12 Alexander Abraham
  2025-06-03  9:12 ` [pve-devel] [PATCH proxmox v3 1/1] fix #5076: Added logic to handle OIDC audiences Alexander Abraham
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Alexander Abraham @ 2025-06-03  9:12 UTC (permalink / raw)
  To: pve-devel

This series adds support for handling Open ID audiences as
described in bug #5076. PVE's API schema was updated to
accept an optional field, an array of strings and the Rust
code was also updated to accordingly handle any incoming
audiences and compare them to the realm config's audiences.
In the realm dialogue for adding an Open ID realm, a new field
titled "Audiences" was added so that users can save any audiences
in their realm domains config file.

proxmox:

Alexander Abraham (1):
  fix #5076: Added logic to handle OIDC audiences

 proxmox-openid/src/lib.rs | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)


pve-access-control:

Alexander Abraham (1):
  fix #5076: Added an optional "audiences" field

 src/PVE/API2/OpenId.pm | 463 +++++++++++++++++++++++------------------
 src/PVE/Auth/OpenId.pm | 141 +++++++------
 2 files changed, 335 insertions(+), 269 deletions(-)


pve-manager:

Alexander Abraham (1):
  fix #5076: Added an "audiences" field for Open ID

 www/manager6/Parser.js            | 27 +++++++++++++++++++++++++++
 www/manager6/dc/AuthEditBase.js   |  8 ++++++++
 www/manager6/dc/AuthEditOpenId.js | 10 +++++++++-
 3 files changed, 44 insertions(+), 1 deletion(-)


Summary over all repositories:
  6 files changed, 397 insertions(+), 272 deletions(-)

-- 
Generated by git-murpp 0.8.1


_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [pve-devel] [PATCH proxmox v3 1/1] fix #5076: Added logic to handle OIDC audiences
  2025-06-03  9:12 [pve-devel] [PATCH access-control/manager/proxmox v3 0/3] fix #5076: Added Open ID audiences Alexander Abraham
@ 2025-06-03  9:12 ` Alexander Abraham
  2025-06-03  9:12 ` [pve-devel] [PATCH pve-access-control v3 1/1] fix #5076: Added an optional "audiences" field Alexander Abraham
  2025-06-03  9:12 ` [pve-devel] [PATCH pve-manager v3 1/1] fix #5076: Added an "audiences" field for Open ID Alexander Abraham
  2 siblings, 0 replies; 4+ messages in thread
From: Alexander Abraham @ 2025-06-03  9:12 UTC (permalink / raw)
  To: pve-devel

A field for OIDC audiences was added, logic to handle these audiences,
and the audiences supplied by an OIDC IDP are validated against
the audiences a user saves in their realm domains
configuration.

Signed-off-by: Alexander Abraham <a.abraham@proxmox.com>
---
 proxmox-openid/src/lib.rs | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/proxmox-openid/src/lib.rs b/proxmox-openid/src/lib.rs
index fe65fded..fa22638a 100644
--- a/proxmox-openid/src/lib.rs
+++ b/proxmox-openid/src/lib.rs
@@ -53,6 +53,8 @@ pub struct OpenIdConfig {
     pub prompt: Option<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub acr_values: Option<Vec<String>>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub audiences: Option<Vec<String>>,
 }
 
 pub struct OpenIdAuthenticator {
@@ -205,12 +207,26 @@ impl OpenIdAuthenticator {
             .request(http_client)
             .map_err(|err| format_err!("Failed to contact token endpoint: {}", err))?;
 
-        let id_token_verifier: CoreIdTokenVerifier = self.client.id_token_verifier();
         let id_token_claims: &CoreIdTokenClaims = token_response
             .extra_fields()
             .id_token()
             .expect("Server did not return an ID token")
-            .claims(&id_token_verifier, &private_auth_state.nonce)
+            .claims(
+                &((self.client.id_token_verifier())
+                    .require_audience_match(true)
+                    .set_other_audience_verifier_fn(|aud| {
+                        let curr_aud: &String = aud;
+                        if &self.config.client_id == curr_aud {
+                            true
+                        } else {
+                            match self.config.audiences.as_ref() {
+                                Some(confd_auds) => confd_auds.contains(curr_aud),
+                                None => false,
+                            }
+                        }
+                    })),
+                &private_auth_state.nonce,
+            )
             .map_err(|err| format_err!("Failed to verify ID token: {}", err))?;
 
         let userinfo_claims: GenericUserInfoClaims = self
-- 
2.39.5



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [pve-devel] [PATCH pve-access-control v3 1/1] fix #5076: Added an optional "audiences" field
  2025-06-03  9:12 [pve-devel] [PATCH access-control/manager/proxmox v3 0/3] fix #5076: Added Open ID audiences Alexander Abraham
  2025-06-03  9:12 ` [pve-devel] [PATCH proxmox v3 1/1] fix #5076: Added logic to handle OIDC audiences Alexander Abraham
@ 2025-06-03  9:12 ` Alexander Abraham
  2025-06-03  9:12 ` [pve-devel] [PATCH pve-manager v3 1/1] fix #5076: Added an "audiences" field for Open ID Alexander Abraham
  2 siblings, 0 replies; 4+ messages in thread
From: Alexander Abraham @ 2025-06-03  9:12 UTC (permalink / raw)
  To: pve-devel

An optional "audiences" field was added to the schema
to accept an array of strings for any audiences communicated
by using an Open ID realm in PVE.

Signed-off-by: Alexander Abraham <a.abraham@proxmox.com>
---
 src/PVE/API2/OpenId.pm | 463 +++++++++++++++++++++++------------------
 src/PVE/Auth/OpenId.pm | 141 +++++++------
 2 files changed, 335 insertions(+), 269 deletions(-)

diff --git a/src/PVE/API2/OpenId.pm b/src/PVE/API2/OpenId.pm
index 77410e6..90e047d 100644
--- a/src/PVE/API2/OpenId.pm
+++ b/src/PVE/API2/OpenId.pm
@@ -21,7 +21,7 @@ use base qw(PVE::RESTHandler);
 my $openid_state_path = "/var/lib/pve-manager";
 
 my $lookup_openid_auth = sub {
-    my ($realm, $redirect_url) = @_;
+    my ( $realm, $redirect_url ) = @_;
 
     my $cfg = cfs_read_file('domains.cfg');
     my $ids = $cfg->{ids};
@@ -29,221 +29,272 @@ my $lookup_openid_auth = sub {
     die "authentication domain '$realm' does not exist\n" if !$ids->{$realm};
 
     my $config = $ids->{$realm};
-    die "wrong realm type ($config->{type} != openid)\n" if $config->{type} ne "openid";
+    die "wrong realm type ($config->{type} != openid)\n"
+      if $config->{type} ne "openid";
 
     my $openid_config = {
-	issuer_url => $config->{'issuer-url'},
-	client_id => $config->{'client-id'},
-	client_key => $config->{'client-key'},
+        issuer_url => $config->{'issuer-url'},
+        client_id  => $config->{'client-id'},
+        client_key => $config->{'client-key'},
     };
-    $openid_config->{prompt} = $config->{'prompt'} if defined($config->{'prompt'});
+    $openid_config->{prompt} = $config->{'prompt'}
+      if defined( $config->{'prompt'} );
 
     my $scopes = $config->{'scopes'} // 'email profile';
     $openid_config->{scopes} = [ PVE::Tools::split_list($scopes) ];
 
-    if (defined(my $acr = $config->{'acr-values'})) {
-	$openid_config->{acr_values} = [ PVE::Tools::split_list($acr) ];
+    if ( defined( my $acr = $config->{'acr-values'} ) ) {
+        $openid_config->{acr_values} = [ PVE::Tools::split_list($acr) ];
     }
 
-    my $openid = PVE::RS::OpenId->discover($openid_config, $redirect_url);
-    return ($config, $openid);
+    if ( defined( my $audiences = $config->{'audiences'} ) ) {
+        $openid_config->{audiences} = $config->{'audiences'};
+    }
+
+    my $openid = PVE::RS::OpenId->discover( $openid_config, $redirect_url );
+    return ( $config, $openid );
 };
 
-__PACKAGE__->register_method ({
-    name => 'index',
-    path => '',
-    method => 'GET',
-    description => "Directory index.",
-    permissions => {
-	user => 'all',
-    },
-    parameters => {
-	additionalProperties => 0,
-	properties => {},
-    },
-    returns => {
-	type => 'array',
-	items => {
-	    type => "object",
-	    properties => {
-		subdir => { type => 'string' },
-	    },
-	},
-	links => [ { rel => 'child', href => "{subdir}" } ],
-    },
-    code => sub {
-	my ($param) = @_;
-
-	return [
-	    { subdir => 'auth-url' },
-	    { subdir => 'login' },
-	];
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'auth_url',
-    path => 'auth-url',
-    method => 'POST',
-    protected => 1,
-    description => "Get the OpenId Authorization Url for the specified realm.",
-    parameters => {
-	additionalProperties => 0,
-	properties => {
-	    realm => get_standard_option('realm'),
-	    'redirect-url' => {
-		description => "Redirection Url. The client should set this to the used server url (location.origin).",
-		type => 'string',
-		maxLength => 255,
-	    },
-	},
-    },
-    returns => {
-	type => "string",
-	description => "Redirection URL.",
-    },
-    permissions => { user => 'world' },
-    code => sub {
-	my ($param) = @_;
-
-	my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
-	local $ENV{all_proxy} = $dcconf->{http_proxy} if exists $dcconf->{http_proxy};
-
-	my $realm = extract_param($param, 'realm');
-	my $redirect_url = extract_param($param, 'redirect-url');
-
-	my ($config, $openid) = $lookup_openid_auth->($realm, $redirect_url);
-	my $url = $openid->authorize_url($openid_state_path , $realm);
-
-	return $url;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'login',
-    path => 'login',
-    method => 'POST',
-    protected => 1,
-    description => " Verify OpenID authorization code and create a ticket.",
-    parameters => {
-	additionalProperties => 0,
-	properties => {
-	    'state' => {
-		description => "OpenId state.",
-		type => 'string',
-		maxLength => 1024,
+__PACKAGE__->register_method(
+    {
+        name        => 'index',
+        path        => '',
+        method      => 'GET',
+        description => "Directory index.",
+        permissions => {
+            user => 'all',
+        },
+        parameters => {
+            additionalProperties => 0,
+            properties           => {},
+        },
+        returns => {
+            type  => 'array',
+            items => {
+                type       => "object",
+                properties => {
+                    subdir => { type => 'string' },
+                },
+            },
+            links => [ { rel => 'child', href => "{subdir}" } ],
+        },
+        code => sub {
+            my ($param) = @_;
+
+            return [ { subdir => 'auth-url' }, { subdir => 'login' }, ];
+        }
+    }
+);
+
+__PACKAGE__->register_method(
+    {
+        name        => 'auth_url',
+        path        => 'auth-url',
+        method      => 'POST',
+        protected   => 1,
+        description =>
+          "Get the OpenId Authorization Url for the specified realm.",
+        parameters => {
+            additionalProperties => 0,
+            properties           => {
+                realm          => get_standard_option('realm'),
+                'redirect-url' => {
+                    description =>
+"Redirection Url. The client should set this to the used server url (location.origin).",
+                    type      => 'string',
+                    maxLength => 255,
+                },
+            },
+        },
+        returns => {
+            type        => "string",
+            description => "Redirection URL.",
+        },
+        permissions => { user => 'world' },
+        code        => sub {
+            my ($param) = @_;
+
+            my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
+            local $ENV{all_proxy} = $dcconf->{http_proxy}
+              if exists $dcconf->{http_proxy};
+
+            my $realm        = extract_param( $param, 'realm' );
+            my $redirect_url = extract_param( $param, 'redirect-url' );
+
+            my ( $config, $openid ) =
+              $lookup_openid_auth->( $realm, $redirect_url );
+            my $url = $openid->authorize_url( $openid_state_path, $realm );
+
+            return $url;
+        }
+    }
+);
+
+__PACKAGE__->register_method(
+    {
+        name        => 'login',
+        path        => 'login',
+        method      => 'POST',
+        protected   => 1,
+        description => " Verify OpenID authorization code and create a ticket.",
+        parameters  => {
+            additionalProperties => 0,
+            properties           => {
+                'state' => {
+                    description => "OpenId state.",
+                    type        => 'string',
+                    maxLength   => 1024,
+                },
+                code => {
+                    description => "OpenId authorization code.",
+                    type        => 'string',
+                    maxLength   => 4096,
+                },
+                'redirect-url' => {
+                    description =>
+"Redirection Url. The client should set this to the used server url (location.origin).",
+                    type      => 'string',
+                    maxLength => 255,
+                },
             },
-	    code => {
-		description => "OpenId authorization code.",
-		type => 'string',
-		maxLength => 4096,
+        },
+        returns => {
+            properties => {
+                username            => { type => 'string' },
+                ticket              => { type => 'string' },
+                CSRFPreventionToken => { type => 'string' },
+                cap         => { type => 'object' },  # computed api permissions
+                clustername => { type => 'string', optional => 1 },
             },
-	    'redirect-url' => {
-		description => "Redirection Url. The client should set this to the used server url (location.origin).",
-		type => 'string',
-		maxLength => 255,
-	    },
-	},
-    },
-    returns => {
-	properties => {
-	    username => { type => 'string' },
-	    ticket => { type => 'string' },
-	    CSRFPreventionToken => { type => 'string' },
-	    cap => { type => 'object' },  # computed api permissions
-	    clustername => { type => 'string', optional => 1 },
-	},
-    },
-    permissions => { user => 'world' },
-    code => sub {
-	my ($param) = @_;
-
-	my $rpcenv = PVE::RPCEnvironment::get();
-
-	my $res;
-	eval {
-	    my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
-	    local $ENV{all_proxy} = $dcconf->{http_proxy} if exists $dcconf->{http_proxy};
-
-	    my ($realm, $private_auth_state) = PVE::RS::OpenId::verify_public_auth_state(
-		$openid_state_path, $param->{'state'});
-
-	    my $redirect_url = extract_param($param, 'redirect-url');
-
-	    my ($config, $openid) = $lookup_openid_auth->($realm, $redirect_url);
-
-	    my $info = $openid->verify_authorization_code($param->{code}, $private_auth_state);
-	    my $subject = $info->{'sub'};
-
-	    my $unique_name;
-
-	    my $user_attr = $config->{'username-claim'} // 'sub';
-	    if (defined($info->{$user_attr})) {
-		$unique_name = $info->{$user_attr};
-	    } elsif ($user_attr eq 'subject') { # stay compat with old versions
-		$unique_name = $subject;
-	    } elsif ($user_attr eq 'username') { # stay compat with old versions
-		my $username = $info->{'preferred_username'};
-		die "missing claim 'preferred_username'\n" if !defined($username);
-		$unique_name =  $username;
-	    } else {
-		# neither the attr nor fallback are defined in info..
-		die "missing configured claim '$user_attr' in returned info object\n";
-	    }
-
-	    my $username = "${unique_name}\@${realm}";
-
-	    # first, check if $username respects our naming conventions
-	    PVE::Auth::Plugin::verify_username($username);
-
-	    if ($config->{'autocreate'} && !$rpcenv->check_user_exist($username, 1)) {
-		PVE::AccessControl::lock_user_config(sub {
-		    my $usercfg = cfs_read_file("user.cfg");
-
-		    die "user '$username' already exists\n" if $usercfg->{users}->{$username};
-
-		    my $entry = { enable => 1 };
-		    if (defined(my $email = $info->{'email'})) {
-			$entry->{email} = $email;
-		    }
-		    if (defined(my $given_name = $info->{'given_name'})) {
-			$entry->{firstname} = $given_name;
-		    }
-		    if (defined(my $family_name = $info->{'family_name'})) {
-			$entry->{lastname} = $family_name;
-		    }
-
-		    $usercfg->{users}->{$username} = $entry;
-
-		    cfs_write_file("user.cfg", $usercfg);
-		}, "autocreate openid user failed");
-	    } else {
-		# test if user exists and is enabled
-		$rpcenv->check_user_enabled($username);
-	    }
-
-	    my $ticket = PVE::AccessControl::assemble_ticket($username);
-	    my $csrftoken = PVE::AccessControl::assemble_csrf_prevention_token($username);
-	    my $cap = $rpcenv->compute_api_permission($username);
-
-	    $res = {
-		ticket => $ticket,
-		username => $username,
-		CSRFPreventionToken => $csrftoken,
-		cap => $cap,
-	    };
-
-	    my $clinfo = PVE::Cluster::get_clinfo();
-	    if ($clinfo->{cluster}->{name} && $rpcenv->check($username, '/', ['Sys.Audit'], 1)) {
-		$res->{clustername} = $clinfo->{cluster}->{name};
-	    }
-	};
-	if (my $err = $@) {
-	    my $clientip = $rpcenv->get_client_ip() || '';
-	    syslog('err', "openid authentication failure; rhost=$clientip msg=$err");
-	    # do not return any info to prevent user enumeration attacks
-	    die PVE::Exception->new("authentication failure\n", code => 401);
-	}
-
-	PVE::Cluster::log_msg('info', 'root@pam', "successful openid auth for user '$res->{username}'");
-
-	return $res;
-    }});
+        },
+        permissions => { user => 'world' },
+        code        => sub {
+            my ($param) = @_;
+
+            my $rpcenv = PVE::RPCEnvironment::get();
+
+            my $res;
+            eval {
+                my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
+                local $ENV{all_proxy} = $dcconf->{http_proxy}
+                  if exists $dcconf->{http_proxy};
+
+                my ( $realm, $private_auth_state ) =
+                  PVE::RS::OpenId::verify_public_auth_state( $openid_state_path,
+                    $param->{'state'} );
+
+                my $redirect_url = extract_param( $param, 'redirect-url' );
+
+                my ( $config, $openid ) =
+                  $lookup_openid_auth->( $realm, $redirect_url );
+                my $info = $openid->verify_authorization_code( $param->{code},
+                    $private_auth_state );
+                my $subject = $info->{'sub'};
+
+                my $unique_name;
+
+                my $user_attr = $config->{'username-claim'} // 'sub';
+                if ( defined( $info->{$user_attr} ) ) {
+                    $unique_name = $info->{$user_attr};
+                }
+                elsif ( $user_attr eq 'subject' )
+                {    # stay compat with old versions
+                    $unique_name = $subject;
+                }
+                elsif ( $user_attr eq 'username' )
+                {    # stay compat with old versions
+                    my $username = $info->{'preferred_username'};
+                    die "missing claim 'preferred_username'\n"
+                      if !defined($username);
+                    $unique_name = $username;
+                }
+                else {
+                    # neither the attr nor fallback are defined in info..
+                    die
+"missing configured claim '$user_attr' in returned info object\n";
+                }
+
+                my $username = "${unique_name}\@${realm}";
+
+                # first, check if $username respects our naming conventions
+                PVE::Auth::Plugin::verify_username($username);
+
+                if ( $config->{'autocreate'}
+                    && !$rpcenv->check_user_exist( $username, 1 ) )
+                {
+                    PVE::AccessControl::lock_user_config(
+                        sub {
+                            my $usercfg = cfs_read_file("user.cfg");
+
+                            die "user '$username' already exists\n"
+                              if $usercfg->{users}->{$username};
+
+                            my $entry = { enable => 1 };
+                            if ( defined( my $email = $info->{'email'} ) ) {
+                                $entry->{email} = $email;
+                            }
+                            if (
+                                defined(
+                                    my $given_name = $info->{'given_name'}
+                                )
+                              )
+                            {
+                                $entry->{firstname} = $given_name;
+                            }
+                            if (
+                                defined(
+                                    my $family_name = $info->{'family_name'}
+                                )
+                              )
+                            {
+                                $entry->{lastname} = $family_name;
+                            }
+
+                            $usercfg->{users}->{$username} = $entry;
+
+                            cfs_write_file( "user.cfg", $usercfg );
+                        },
+                        "autocreate openid user failed"
+                    );
+                }
+                else {
+                    # test if user exists and is enabled
+                    $rpcenv->check_user_enabled($username);
+                }
+
+                my $ticket = PVE::AccessControl::assemble_ticket($username);
+                my $csrftoken =
+                  PVE::AccessControl::assemble_csrf_prevention_token($username);
+                my $cap = $rpcenv->compute_api_permission($username);
+
+                $res = {
+                    ticket              => $ticket,
+                    username            => $username,
+                    CSRFPreventionToken => $csrftoken,
+                    cap                 => $cap,
+                };
+
+                my $clinfo = PVE::Cluster::get_clinfo();
+                if (   $clinfo->{cluster}->{name}
+                    && $rpcenv->check( $username, '/', ['Sys.Audit'], 1 ) )
+                {
+                    $res->{clustername} = $clinfo->{cluster}->{name};
+                }
+            };
+            if ( my $err = $@ ) {
+                my $clientip = $rpcenv->get_client_ip() || '';
+                syslog( 'err',
+                    "openid authentication failure; rhost=$clientip msg=$err" );
+
+                # do not return any info to prevent user enumeration attacks
+                die PVE::Exception->new( "authentication failure\n",
+                    code => 401 );
+            }
+
+            PVE::Cluster::log_msg( 'info', 'root@pam',
+                "successful openid auth for user '$res->{username}'" );
+
+            return $res;
+        }
+    }
+);
diff --git a/src/PVE/Auth/OpenId.pm b/src/PVE/Auth/OpenId.pm
index c8e4db9..243f6f0 100755
--- a/src/PVE/Auth/OpenId.pm
+++ b/src/PVE/Auth/OpenId.pm
@@ -5,7 +5,8 @@ use warnings;
 
 use PVE::Tools;
 use PVE::Auth::Plugin;
-use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Cluster
+  qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
 
 use base qw(PVE::Auth::Plugin);
 
@@ -15,77 +16,91 @@ sub type {
 
 sub properties {
     return {
-	"issuer-url" => {
-	    description => "OpenID Issuer Url",
-	    type => 'string',
-	    maxLength => 256,
-	},
-	"client-id" => {
-	    description => "OpenID Client ID",
-	    type => 'string',
-	    maxLength => 256,
-	},
-	"client-key" => {
-	    description => "OpenID Client Key",
-	    type => 'string',
-	    optional => 1,
-	    maxLength => 256,
-	},
-	autocreate => {
-	    description => "Automatically create users if they do not exist.",
-	    optional => 1,
-	    type => 'boolean',
-	    default => 0,
-	},
-	"username-claim" => {
-	    description => "OpenID claim used to generate the unique username.",
-	    type => 'string',
-	    optional => 1,
-	},
-	prompt => {
-	    description => "Specifies whether the Authorization Server prompts the End-User for"
-	        ." reauthentication and consent.",
-	    type => 'string',
-	    pattern => '(?:none|login|consent|select_account|\S+)', # \S+ is the extension variant
-	    optional => 1,
-	},
-	scopes => {
-	    description => "Specifies the scopes (user details) that should be authorized and"
-	        ." returned, for example 'email' or 'profile'.",
-	    type => 'string', # format => 'some-safe-id-list', # FIXME: TODO
-	    default => "email profile",
-	    optional => 1,
-	},
-	'acr-values' => {
-	    description => "Specifies the Authentication Context Class Reference values that the"
-		."Authorization Server is being requested to use for the Auth Request.",
-	    type => 'string',
-	    pattern => '^[^\x00-\x1F\x7F <>#"]*$', # Prohibit characters not allowed in URI RFC 2396.
-	    optional => 1,
-	},
-   };
+        "issuer-url" => {
+            description => "OpenID Issuer Url",
+            type        => 'string',
+            maxLength   => 256,
+        },
+        "client-id" => {
+            description => "OpenID Client ID",
+            type        => 'string',
+            maxLength   => 256,
+        },
+        "client-key" => {
+            description => "OpenID Client Key",
+            type        => 'string',
+            optional    => 1,
+            maxLength   => 256,
+        },
+        autocreate => {
+            description => "Automatically create users if they do not exist.",
+            optional    => 1,
+            type        => 'boolean',
+            default     => 0,
+        },
+        "username-claim" => {
+            description => "OpenID claim used to generate the unique username.",
+            type        => 'string',
+            optional    => 1,
+        },
+        prompt => {
+            description =>
+"Specifies whether the Authorization Server prompts the End-User for"
+              . " reauthentication and consent.",
+            type    => 'string',
+            pattern => '(?:none|login|consent|select_account|\S+)'
+            ,    # \S+ is the extension variant
+            optional => 1,
+        },
+        scopes => {
+            description =>
+"Specifies the scopes (user details) that should be authorized and"
+              . " returned, for example 'email' or 'profile'.",
+            type     => 'string', # format => 'some-safe-id-list', # FIXME: TODO
+            default  => "email profile",
+            optional => 1,
+        },
+        'acr-values' => {
+            description =>
+"Specifies the Authentication Context Class Reference values that the"
+              . "Authorization Server is being requested to use for the Auth Request.",
+            type    => 'string',
+            pattern => '^[^\x00-\x1F\x7F <>#"]*$'
+            ,    # Prohibit characters not allowed in URI RFC 2396.
+            optional => 1,
+        },
+        'audiences' => {
+            description =>
+"Specifies the authentication claims neccessary for checking the privileges the requesting user has.",
+            type    => 'array',
+            'items' => {
+                type     => 'string',
+                pattern  => '^[a-zA-Z0-9-_+.]+$',
+                optional => 1
+            }
+        },
+    };
 }
 
 sub options {
     return {
-	"issuer-url" => {},
-	"client-id" => {},
-	"client-key" => { optional => 1 },
-	autocreate => { optional => 1 },
-	"username-claim" => { optional => 1, fixed => 1 },
-	prompt => { optional => 1 },
-	scopes => { optional => 1 },
-	"acr-values" => { optional => 1 },
-	default => { optional => 1 },
-	comment => { optional => 1 },
+        "issuer-url"     => {},
+        "client-id"      => {},
+        "client-key"     => { optional => 1 },
+        autocreate       => { optional => 1 },
+        "username-claim" => { optional => 1, fixed => 1 },
+        prompt           => { optional => 1 },
+        scopes           => { optional => 1 },
+        "acr-values"     => { optional => 1 },
+        "audiences"      => { optional => 1 },
+        default          => { optional => 1 },
+        comment          => { optional => 1 },
     };
 }
 
 sub authenticate_user {
-    my ($class, $config, $realm, $username, $password) = @_;
-
+    my ( $class, $config, $realm, $username, $password ) = @_;
     die "OpenID realm does not allow password verification.\n";
 }
 
-
 1;
-- 
2.39.5



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [pve-devel] [PATCH pve-manager v3 1/1] fix #5076: Added an "audiences" field for Open ID
  2025-06-03  9:12 [pve-devel] [PATCH access-control/manager/proxmox v3 0/3] fix #5076: Added Open ID audiences Alexander Abraham
  2025-06-03  9:12 ` [pve-devel] [PATCH proxmox v3 1/1] fix #5076: Added logic to handle OIDC audiences Alexander Abraham
  2025-06-03  9:12 ` [pve-devel] [PATCH pve-access-control v3 1/1] fix #5076: Added an optional "audiences" field Alexander Abraham
@ 2025-06-03  9:12 ` Alexander Abraham
  2 siblings, 0 replies; 4+ messages in thread
From: Alexander Abraham @ 2025-06-03  9:12 UTC (permalink / raw)
  To: pve-devel

A field for audiences for OpenId was added for users to supply
Open ID audiences as a space-separated array of strings in their
realm configuration. This array of audiences is then saved in the
realm domains config file.

Signed-off-by: Alexander Abraham <a.abraham@proxmox.com>
---
 www/manager6/Parser.js            | 27 +++++++++++++++++++++++++++
 www/manager6/dc/AuthEditBase.js   |  8 ++++++++
 www/manager6/dc/AuthEditOpenId.js | 10 +++++++++-
 3 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/www/manager6/Parser.js b/www/manager6/Parser.js
index 07eb9b17..4868777e 100644
--- a/www/manager6/Parser.js
+++ b/www/manager6/Parser.js
@@ -1,9 +1,36 @@
 // Some configuration values are complex strings - so we need parsers/generators for them.
 Ext.define('PVE.Parser', {
+
  statics: {
 
     // this class only contains static functions
+    checkKeys: function(obj, subject) {
+      var result = false;
+      for (const [key, _] of Object.entries(obj)) {
+        if (key === subject) {
+          result = true;
+        } else {
+          // Do nothing.
+        }
+      }
+      return result;
+    },
 
+    parseOpenIdAudiences: function(audiences) {
+      var result = [];
+      var container = [];
+      for (var i = 0; i < audiences.length; i++) {
+        var current = audiences[i];
+        if (current === ' ') {
+          result.push(container.join(''));
+          container = [];
+        } else {
+          container.push(current);
+        }
+      }
+      result.push(container.join(''));
+      return result;
+    },
     printACME: function(value) {
 	if (Ext.isArray(value.domains)) {
 	    value.domains = value.domains.join(';');
diff --git a/www/manager6/dc/AuthEditBase.js b/www/manager6/dc/AuthEditBase.js
index e18fbc3b..0110e191 100644
--- a/www/manager6/dc/AuthEditBase.js
+++ b/www/manager6/dc/AuthEditBase.js
@@ -14,6 +14,14 @@ Ext.define('PVE.panel.AuthBase', {
 	    delete values.port;
 	}
 
+        var audiences = [];
+        if (PVE.Parser.checkKeys(values, "audiences")) {
+          audiences = PVE.Parser.parseOpenIdAudiences(values.audiences);
+          console.log(audiences);
+          delete values.audiences;
+          values.audiences = audiences;
+        }
+
 	if (me.isCreate) {
 	    values.type = me.type;
 	}
diff --git a/www/manager6/dc/AuthEditOpenId.js b/www/manager6/dc/AuthEditOpenId.js
index 544c0de5..0f4b07a9 100644
--- a/www/manager6/dc/AuthEditOpenId.js
+++ b/www/manager6/dc/AuthEditOpenId.js
@@ -111,6 +111,15 @@ Ext.define('PVE.panel.OpenIDInputPanel', {
 		deleteEmpty: '{!isCreate}',
 	    },
 	},
+	{
+	    xtype: 'proxmoxtextfield',
+	    name: 'audiences',
+	    fieldLabel: gettext('Audiences'),
+	    submitEmpty: false,
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	},
     ],
 
     initComponent: function() {
@@ -123,4 +132,3 @@ Ext.define('PVE.panel.OpenIDInputPanel', {
 	me.callParent();
     },
 });
-
-- 
2.39.5



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-06-03  9:13 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-03  9:12 [pve-devel] [PATCH access-control/manager/proxmox v3 0/3] fix #5076: Added Open ID audiences Alexander Abraham
2025-06-03  9:12 ` [pve-devel] [PATCH proxmox v3 1/1] fix #5076: Added logic to handle OIDC audiences Alexander Abraham
2025-06-03  9:12 ` [pve-devel] [PATCH pve-access-control v3 1/1] fix #5076: Added an optional "audiences" field Alexander Abraham
2025-06-03  9:12 ` [pve-devel] [PATCH pve-manager v3 1/1] fix #5076: Added an "audiences" field for Open ID Alexander Abraham

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal