From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 5ADD41FF15C for ; Fri, 17 Oct 2025 09:57:39 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5F4532168A; Fri, 17 Oct 2025 09:58:00 +0200 (CEST) Date: Fri, 17 Oct 2025 09:57:20 +0200 From: Fabian =?iso-8859-1?q?Gr=FCnbichler?= To: Proxmox Datacenter Manager development discussion References: <20251014133044.337162-1-s.sterz@proxmox.com> <20251014133044.337162-8-s.sterz@proxmox.com> In-Reply-To: <20251014133044.337162-8-s.sterz@proxmox.com> MIME-Version: 1.0 User-Agent: astroid/0.17.0 (https://github.com/astroidmail/astroid) Message-Id: <1760684951.paenarjg51.astroid@yuna.none> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1760687840435 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.047 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [mod.rs, proxmox.com, openid.rs] Subject: Re: [pdm-devel] [PATCH datacenter-manager 2/3] server: api: add support for adding openid realms and openid logins X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" On October 14, 2025 3:30 pm, Shannon Sterz wrote: > only supports the new HttpOnly authentication flow, as PDM does not > support non-HttpOnly authentication at the moment. > > Signed-off-by: Shannon Sterz > --- > Cargo.toml | 2 +- > server/Cargo.toml | 1 + > server/src/api/access/mod.rs | 2 + > server/src/api/access/openid.rs | 311 +++++++++++++++++++++++++ > server/src/api/config/access/mod.rs | 2 + > server/src/api/config/access/openid.rs | 290 +++++++++++++++++++++++ > server/src/auth/mod.rs | 6 +- > 7 files changed, 612 insertions(+), 2 deletions(-) > create mode 100644 server/src/api/access/openid.rs > create mode 100644 server/src/api/config/access/openid.rs > > diff --git a/Cargo.toml b/Cargo.toml > index f820409..39a0b23 100644 > --- a/Cargo.toml > +++ b/Cargo.toml > @@ -68,7 +68,7 @@ proxmox-uuid = "1" > > # other proxmox crates > proxmox-acme = "0.5" > -proxmox-openid = "0.10" > +proxmox-openid = "1.0.2" > > # api implementation creates > proxmox-config-digest = "1" > diff --git a/server/Cargo.toml b/server/Cargo.toml > index 0dfcb6c..94420b4 100644 > --- a/server/Cargo.toml > +++ b/server/Cargo.toml > @@ -44,6 +44,7 @@ proxmox-lang.workspace = true > proxmox-ldap.workspace = true > proxmox-log.workspace = true > proxmox-login.workspace = true > +proxmox-openid.workspace = true > proxmox-rest-server = { workspace = true, features = [ "templates" ] } > proxmox-router = { workspace = true, features = [ "cli", "server"] } > proxmox-rrd.workspace = true > diff --git a/server/src/api/access/mod.rs b/server/src/api/access/mod.rs > index 345b22f..a6874a5 100644 > --- a/server/src/api/access/mod.rs > +++ b/server/src/api/access/mod.rs > @@ -14,6 +14,7 @@ use proxmox_sortable_macro::sortable; > use pdm_api_types::{Authid, ACL_PATH_SCHEMA, PRIVILEGES, PRIV_ACCESS_AUDIT}; > > mod domains; > +mod openid; > mod tfa; > mod users; > > @@ -33,6 +34,7 @@ const SUBDIRS: SubdirMap = &sorted!([ > .post(&proxmox_auth_api::api::API_METHOD_CREATE_TICKET_HTTP_ONLY) > .delete(&proxmox_auth_api::api::API_METHOD_LOGOUT), > ), > + ("openid", &openid::ROUTER), > ("users", &users::ROUTER), > ]); > > diff --git a/server/src/api/access/openid.rs b/server/src/api/access/openid.rs > new file mode 100644 > index 0000000..5f662f0 > --- /dev/null > +++ b/server/src/api/access/openid.rs > @@ -0,0 +1,311 @@ > +//! OpenID redirect/login API > +use anyhow::{bail, format_err, Error}; > +use hyper::http::request::Parts; > +use hyper::Response; > +use pdm_api_types::{OpenIdRealmConfig, OPENID_DEFAULT_SCOPE_LIST, REALM_ID_SCHEMA}; > +use pdm_buildcfg::PDM_RUN_DIR_M; nit: these two should be listed last.. > +use proxmox_auth_api::api::ApiTicket; > +use proxmox_login::api::CreateTicketResponse; and these two below, but - this is the wrong CreateTicketResponse? should be proxmox_auth_api::types::CreateTicketResponse.. > +use serde_json::{json, Value}; > + > +use proxmox_auth_api::api::{assemble_csrf_prevention_token, AuthContext}; > +use proxmox_auth_api::ticket::Ticket; > +use proxmox_router::{ > + http_err, list_subdirs_api_method, ApiHandler, ApiMethod, ApiResponseFuture, Permission, > + Router, RpcEnvironment, SubdirMap, > +}; > +use proxmox_schema::{api, BooleanSchema, ObjectSchema, ParameterSchema, StringSchema}; nit: BooleanSchema is not used.. > +use proxmox_sortable_macro::sortable; > + > +use proxmox_openid::{OpenIdAuthenticator, OpenIdConfig}; > + > +use proxmox_access_control::types::{User, EMAIL_SCHEMA, FIRST_NAME_SCHEMA, LAST_NAME_SCHEMA}; > +use proxmox_auth_api::types::Userid; > + > +use proxmox_access_control::CachedUserInfo; and those last five are not sorted > + > +use crate::auth; > + > +fn openid_authenticator( > + realm_config: &OpenIdRealmConfig, > + redirect_url: &str, > +) -> Result { > + let scopes: Vec = realm_config > + .scopes > + .as_deref() > + .unwrap_or(OPENID_DEFAULT_SCOPE_LIST) > + .split(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c)) > + .filter(|s| !s.is_empty()) > + .map(String::from) > + .collect(); > + > + let mut acr_values = None; > + if let Some(ref list) = realm_config.acr_values { > + acr_values = Some( > + list.split(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c)) > + .filter(|s| !s.is_empty()) > + .map(String::from) > + .collect(), > + ); > + } > + > + let config = OpenIdConfig { > + issuer_url: realm_config.issuer_url.clone(), > + client_id: realm_config.client_id.clone(), > + client_key: realm_config.client_key.clone(), > + prompt: realm_config.prompt.clone(), > + scopes: Some(scopes), > + acr_values, > + }; > + OpenIdAuthenticator::discover(&config, redirect_url) > +} > + > +#[sortable] > +pub const API_METHOD_OPENID_LOGIN: ApiMethod = ApiMethod::new_full( > + &ApiHandler::AsyncHttpBodyParameters(&create_ticket_http_only), > + ParameterSchema::Object(&ObjectSchema::new( > + "Get a new ticket as an HttpOnly cookie. Supports tickets via cookies.", > + &sorted!([ > + ("state", false, &StringSchema::new("OpenId state.").schema()), > + ( > + "code", > + false, > + &StringSchema::new("OpenId authorization code.").schema(), > + ), > + ( > + "redirect-url", > + false, > + &StringSchema::new( > + "Redirection Url. The client should set this to used server url.", > + ) > + .schema(), > + ), > + ]), > + )), > +) > +.returns(::proxmox_schema::ReturnType::new( > + false, > + &ObjectSchema::new( and if we use the right CreateTicketResponse type above, we can use the API_SCHEMA it already has here :) > + "An authentication ticket with additional infos.", > + &sorted!([ > + ("username", false, &StringSchema::new("User name.").schema()), > + ( > + "ticket", > + true, > + &StringSchema::new( > + "Auth ticket, present if http-only was not provided or is false." > + ) > + .schema() > + ), > + ("ticket-info", > + true, > + &StringSchema::new( > + "Informational ticket, can only be used to check if the ticket is expired. Present if http-only was true." > + ).schema()), > + ( > + "CSRFPreventionToken", > + false, > + &StringSchema::new("Cross Site Request Forgery Prevention Token.").schema(), > + ), > + ]), > + ) > + .schema(), > +)) > +.protected(true) > +.access(None, &Permission::World) > +.reload_timezone(true); > + > +fn create_ticket_http_only( > + _parts: Parts, > + param: Value, > + _info: &ApiMethod, > + rpcenv: Box, > +) -> ApiResponseFuture { > + Box::pin(async move { > + use proxmox_rest_server::RestEnvironment; > + > + let code = param["code"] > + .as_str() > + .ok_or_else(|| format_err!("missing non-optional parameter: code"))? > + .to_owned(); > + let state = param["state"] > + .as_str() > + .ok_or_else(|| format_err!("missing non-optional parameter: state"))? > + .to_owned(); > + let redirect_url = param["redirect-url"] > + .as_str() > + .ok_or_else(|| format_err!("missing non-optional parameter: redirect-url"))? > + .to_owned(); > + > + let env: &RestEnvironment = rpcenv > + .as_any() > + .downcast_ref::() > + .ok_or_else(|| format_err!("detected wrong RpcEnvironment type"))?; > + > + let user_info = CachedUserInfo::new()?; > + let auth_context = auth::get_auth_context() > + .ok_or_else(|| format_err!("could not get authentication context"))?; > + > + let mut tested_username = None; > + > + let result = (|| { is there a reason to not use try_block! for this, like in the rest of our code base? > + let (realm, private_auth_state) = > + OpenIdAuthenticator::verify_public_auth_state(PDM_RUN_DIR_M!(), &state)?; > + > + let (domains, _digest) = pdm_config::domains::config()?; > + let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?; > + let open_id = openid_authenticator(&config, &redirect_url)?; > + let info = open_id.verify_authorization_code_simple(&code, &private_auth_state)?; > + let name_attr = config.username_claim.as_deref().unwrap_or("sub"); > + > + // Try to be compatible with previous versions > + let try_attr = match name_attr { > + "subject" => Some("sub"), > + "username" => Some("preferred_username"), > + _ => None, > + }; > + > + let unique_name = if let Some(name) = info[name_attr] > + .as_str() > + .or_else(|| try_attr.and_then(|att| info[att].as_str())) > + { > + name.to_owned() > + } else { > + bail!("missing claim '{name_attr}'"); > + }; > + > + let user_id = Userid::try_from(format!("{unique_name}@{realm}"))?; > + tested_username = Some(unique_name); > + > + if !user_info.is_active_user_id(&user_id) { > + if config.autocreate.unwrap_or(false) { > + let _lock = proxmox_access_control::user::lock_config()?; > + let (mut user_config, _digest) = proxmox_access_control::user::config()?; > + > + if let Ok(old_user) = user_config.lookup::("user", user_id.as_str()) { > + if let Some(false) = old_user.enable { > + bail!("user '{user_id}' is disabled."); > + } else { > + bail!("autocreate user failed - '{user_id}' already exists."); > + } > + } > + > + let firstname = info["given_name"] > + .as_str() > + .map(|n| n.to_string()) > + .filter(|n| FIRST_NAME_SCHEMA.parse_simple_value(n).is_ok()); > + > + let lastname = info["family_name"] > + .as_str() > + .map(|n| n.to_string()) > + .filter(|n| LAST_NAME_SCHEMA.parse_simple_value(n).is_ok()); > + > + let email = info["email"] > + .as_str() > + .map(|n| n.to_string()) > + .filter(|n| EMAIL_SCHEMA.parse_simple_value(n).is_ok()); > + > + let user = User { > + userid: user_id.clone(), > + comment: None, > + enable: None, > + expire: None, > + firstname, > + lastname, > + email, > + }; > + > + user_config.set_data(user.userid.as_str(), "user", &user)?; > + proxmox_access_control::user::save_config(&user_config)?; > + } else { > + bail!("user account '{user_id}' missing, disabled or expired."); > + } > + } > + > + let api_ticket = ApiTicket::Full(user_id.clone()); > + let ticket = Ticket::new(auth_context.auth_prefix(), &api_ticket)?; > + let token = assemble_csrf_prevention_token(auth_context.csrf_secret(), &user_id); > + env.log_auth(user_id.as_str()); > + > + Ok((user_id, ticket, token)) > + })(); > + > + let (user_id, mut ticket, token) = result.map_err(|err| { > + let msg = err.to_string(); > + env.log_failed_auth(tested_username, &msg); this is copied over from PBS, but isn't this also kinda wrong? not every error above is a failed auth.. at least if we compare error handling here with the one in the regular ticket flow, this is inconsistent.. might rather be follow-up material after closer thought though, and ensuring PBS and PDM behave the same afterwards.. > + http_err!(UNAUTHORIZED, "{msg}") > + })?; > + > + let cookie = format!( > + "{}={}; Secure; SameSite=Lax; HttpOnly; Path=/;", > + auth_context.prefixed_auth_cookie_name(), > + ticket.sign(auth_context.keyring(), None)?, > + ); > + > + let response = Response::builder() > + .header(hyper::http::header::CONTENT_TYPE, "application/json") > + .header(hyper::header::SET_COOKIE, cookie); > + > + let data = CreateTicketResponse { > + csrfprevention_token: Some(token), > + clustername: None, this is then removed > + ticket: None, > + ticket_info: Some(ticket.ticket_info()), > + username: user_id.to_string(), as well as the to_string here > + }; > + > + Ok(response.body( > + json!({"data": data, "status": 200, "success": true }) > + .to_string() > + .into(), > + )?) > + }) > +} > + > +#[api( > + protected: true, > + input: { > + properties: { > + realm: { > + schema: REALM_ID_SCHEMA, > + }, > + "redirect-url": { > + description: "Redirection Url. The client should set this to used server url.", > + type: String, String type without a schema :-/ > + }, > + }, > + }, > + returns: { > + description: "Redirection URL.", > + type: String, > + }, > + access: { > + description: "Anyone can access this (before the user is authenticated).", > + permission: &Permission::World, > + }, > +)] > +/// Create OpenID Redirect Session > +pub fn openid_auth_url( > + realm: String, > + redirect_url: String, > + _rpcenv: &mut dyn RpcEnvironment, > +) -> Result { > + let (domains, _digest) = pdm_config::domains::config()?; > + let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?; > + > + let open_id = openid_authenticator(&config, &redirect_url)?; > + > + let url = open_id.authorize_url(PDM_RUN_DIR_M!(), &realm)?; > + > + Ok(url) > +} > + > +#[sortable] > +const SUBDIRS: SubdirMap = &sorted!([ > + ("login", &Router::new().post(&API_METHOD_OPENID_LOGIN)), > + ("auth-url", &Router::new().post(&API_METHOD_OPENID_AUTH_URL)), > +]); > + > +pub const ROUTER: Router = Router::new() > + .get(&list_subdirs_api_method!(SUBDIRS)) > + .subdirs(SUBDIRS); > diff --git a/server/src/api/config/access/mod.rs b/server/src/api/config/access/mod.rs > index 7454f53..5776152 100644 > --- a/server/src/api/config/access/mod.rs > +++ b/server/src/api/config/access/mod.rs > @@ -4,12 +4,14 @@ use proxmox_sortable_macro::sortable; > > mod ad; > mod ldap; > +mod openid; > pub mod tfa; > > #[sortable] > const SUBDIRS: SubdirMap = &sorted!([ > ("tfa", &tfa::ROUTER), > ("ldap", &ldap::ROUTER), > + ("openid", &openid::ROUTER), > ("ad", &ad::ROUTER), > ]); > > diff --git a/server/src/api/config/access/openid.rs b/server/src/api/config/access/openid.rs > new file mode 100644 > index 0000000..555a1e1 > --- /dev/null > +++ b/server/src/api/config/access/openid.rs > @@ -0,0 +1,290 @@ > +use ::serde::{Deserialize, Serialize}; > +/// Configure OpenId realms > +use anyhow::Error; > +use pdm_api_types::ConfigDigest; same here as for the other module > +use serde_json::Value; > + > +use proxmox_router::{http_bail, Permission, Router, RpcEnvironment}; > +use proxmox_schema::{api, param_bail}; > + > +use pdm_api_types::{ > + OpenIdRealmConfig, OpenIdRealmConfigUpdater, PRIV_REALM_ALLOCATE, PRIV_SYS_AUDIT, > + REALM_ID_SCHEMA, > +}; > + > +use pdm_config::domains; > + > +#[api( > + input: { > + properties: {}, > + }, > + returns: { > + description: "List of configured OpenId realms.", > + type: Array, > + items: { type: OpenIdRealmConfig }, > + }, > + access: { > + permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false), > + }, > +)] > +/// List configured OpenId realms > +pub fn list_openid_realms( > + _param: Value, > + rpcenv: &mut dyn RpcEnvironment, > +) -> Result, Error> { > + let (config, digest) = domains::config()?; > + > + let list = config.convert_to_typed_array("openid")?; > + > + rpcenv["digest"] = hex::encode(digest).into(); > + > + Ok(list) > +} > + > +#[api( > + protected: true, > + input: { > + properties: { > + config: { > + type: OpenIdRealmConfig, > + flatten: true, > + }, > + }, > + }, > + access: { > + permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false), > + }, > +)] > +/// Create a new OpenId realm > +pub fn create_openid_realm(config: OpenIdRealmConfig) -> Result<(), Error> { > + let _lock = domains::lock_config()?; > + > + let (mut domains, _digest) = domains::config()?; > + > + if domains::exists(&domains, &config.realm) { > + param_bail!("realm", "realm '{}' already exists.", config.realm); > + } > + > + if let Some(true) = config.default { > + domains::unset_default_realm(&mut domains)?; > + } > + > + domains.set_data(&config.realm, "openid", &config)?; > + > + domains::save_config(&domains)?; > + > + Ok(()) > +} > + > +#[api( > + protected: true, > + input: { > + properties: { > + realm: { > + schema: REALM_ID_SCHEMA, > + }, > + digest: { > + optional: true, > + type: ConfigDigest, > + }, > + }, > + }, > + access: { > + permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false), > + }, > +)] > +/// Remove a OpenID realm configuration > +pub fn delete_openid_realm( > + realm: String, > + digest: Option, > + _rpcenv: &mut dyn RpcEnvironment, > +) -> Result<(), Error> { > + let _lock = domains::lock_config()?; > + > + let (mut domains, expected_digest) = domains::config()?; > + expected_digest.detect_modification(digest.as_ref())?; > + > + if domains.sections.remove(&realm).is_none() { > + http_bail!(NOT_FOUND, "realm '{realm}' does not exist."); > + } > + > + domains::save_config(&domains)?; > + > + Ok(()) > +} > + > +#[api( > + input: { > + properties: { > + realm: { > + schema: REALM_ID_SCHEMA, > + }, > + }, > + }, > + returns: { type: OpenIdRealmConfig }, > + access: { > + permission: &Permission::Privilege(&["access", "domains"], PRIV_SYS_AUDIT, false), > + }, > +)] > +/// Read the OpenID realm configuration > +pub fn read_openid_realm( > + realm: String, > + rpcenv: &mut dyn RpcEnvironment, > +) -> Result { > + let (domains, digest) = domains::config()?; > + > + let config = domains.lookup("openid", &realm)?; > + rpcenv["digest"] = hex::encode(digest).into(); > + > + Ok(config) > +} > + > +#[api()] > +#[derive(Serialize, Deserialize)] > +#[serde(rename_all = "kebab-case")] > +/// Deletable property name > +pub enum DeletableProperty { > + /// Delete the client key. > + ClientKey, > + /// Delete the comment property. > + Comment, > + /// Delete the default property. > + Default, > + /// Delete the autocreate property > + Autocreate, > + /// Delete the scopes property > + Scopes, > + /// Delete the prompt property > + Prompt, > + /// Delete the acr_values property > + AcrValues, > +} > + > +#[api( > + protected: true, > + input: { > + properties: { > + realm: { > + schema: REALM_ID_SCHEMA, > + }, > + update: { > + type: OpenIdRealmConfigUpdater, > + flatten: true, > + }, > + delete: { > + description: "List of properties to delete.", > + type: Array, > + optional: true, > + items: { > + type: DeletableProperty, > + } > + }, > + digest: { > + optional: true, > + type: ConfigDigest, > + }, > + }, > + }, > + returns: { type: OpenIdRealmConfig }, > + access: { > + permission: &Permission::Privilege(&["access", "domains"], PRIV_REALM_ALLOCATE, false), > + }, > +)] > +/// Update an OpenID realm configuration > +pub fn update_openid_realm( > + realm: String, > + update: OpenIdRealmConfigUpdater, > + delete: Option>, > + digest: Option, > + _rpcenv: &mut dyn RpcEnvironment, > +) -> Result<(), Error> { > + let _lock = domains::lock_config()?; > + > + let (mut domains, expected_digest) = domains::config()?; > + expected_digest.detect_modification(digest.as_ref())?; > + > + let mut config: OpenIdRealmConfig = domains.lookup("openid", &realm)?; > + > + if let Some(delete) = delete { > + for delete_prop in delete { > + match delete_prop { > + DeletableProperty::ClientKey => { > + config.client_key = None; > + } > + DeletableProperty::Comment => { > + config.comment = None; > + } > + DeletableProperty::Default => { > + config.default = None; > + } > + DeletableProperty::Autocreate => { > + config.autocreate = None; > + } > + DeletableProperty::Scopes => { > + config.scopes = None; > + } > + DeletableProperty::Prompt => { > + config.prompt = None; > + } > + DeletableProperty::AcrValues => { > + config.acr_values = None; > + } > + } > + } > + } > + > + if let Some(comment) = update.comment { > + let comment = comment.trim().to_string(); > + if comment.is_empty() { > + config.comment = None; > + } else { > + config.comment = Some(comment); > + } > + } > + > + if let Some(true) = update.default { > + domains::unset_default_realm(&mut domains)?; > + config.default = Some(true); > + } else { > + config.default = None; > + } > + > + if let Some(issuer_url) = update.issuer_url { > + config.issuer_url = issuer_url; > + } > + if let Some(client_id) = update.client_id { > + config.client_id = client_id; > + } > + > + if update.client_key.is_some() { > + config.client_key = update.client_key; > + } > + if update.autocreate.is_some() { > + config.autocreate = update.autocreate; > + } > + if update.scopes.is_some() { > + config.scopes = update.scopes; > + } > + if update.prompt.is_some() { > + config.prompt = update.prompt; > + } > + if update.acr_values.is_some() { > + config.acr_values = update.acr_values; > + } > + > + domains.set_data(&realm, "openid", &config)?; > + > + domains::save_config(&domains)?; > + > + Ok(()) > +} > + > +const ITEM_ROUTER: Router = Router::new() > + .get(&API_METHOD_READ_OPENID_REALM) > + .put(&API_METHOD_UPDATE_OPENID_REALM) > + .delete(&API_METHOD_DELETE_OPENID_REALM); > + > +pub const ROUTER: Router = Router::new() > + .get(&API_METHOD_LIST_OPENID_REALMS) > + .post(&API_METHOD_CREATE_OPENID_REALM) > + .match_all("realm", &ITEM_ROUTER); > diff --git a/server/src/auth/mod.rs b/server/src/auth/mod.rs > index 532350d..9413a83 100644 > --- a/server/src/auth/mod.rs > +++ b/server/src/auth/mod.rs > @@ -81,7 +81,11 @@ fn setup_auth_context(use_private_key: bool) { > proxmox_auth_api::set_auth_context(AUTH_CONTEXT.get().unwrap()); > } > > -struct PdmAuthContext { > +pub(crate) fn get_auth_context() -> Option<&'static PdmAuthContext> { > + AUTH_CONTEXT.get() > +} > + > +pub(crate) struct PdmAuthContext { > keyring: Keyring, > csrf_secret: &'static HMACKey, > } > -- > 2.47.3 > > > > _______________________________________________ > pdm-devel mailing list > pdm-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel > > > _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel