diff --git a/crates/config/src/constants/oidc.rs b/crates/config/src/constants/oidc.rs index b7d3c12c7..99b13d7ce 100644 --- a/crates/config/src/constants/oidc.rs +++ b/crates/config/src/constants/oidc.rs @@ -28,6 +28,7 @@ pub const OIDC_GROUPS_CLAIM: &str = "groups_claim"; pub const OIDC_ROLES_CLAIM: &str = "roles_claim"; pub const OIDC_EMAIL_CLAIM: &str = "email_claim"; pub const OIDC_USERNAME_CLAIM: &str = "username_claim"; +pub const OIDC_HIDE_FROM_UI: &str = "hide_from_ui"; // Environment variable names for OIDC pub const ENV_IDENTITY_OPENID_ENABLE: &str = "RUSTFS_IDENTITY_OPENID_ENABLE"; @@ -46,9 +47,10 @@ pub const ENV_IDENTITY_OPENID_GROUPS_CLAIM: &str = "RUSTFS_IDENTITY_OPENID_GROUP pub const ENV_IDENTITY_OPENID_ROLES_CLAIM: &str = "RUSTFS_IDENTITY_OPENID_ROLES_CLAIM"; pub const ENV_IDENTITY_OPENID_EMAIL_CLAIM: &str = "RUSTFS_IDENTITY_OPENID_EMAIL_CLAIM"; pub const ENV_IDENTITY_OPENID_USERNAME_CLAIM: &str = "RUSTFS_IDENTITY_OPENID_USERNAME_CLAIM"; +pub const ENV_IDENTITY_OPENID_HIDE_FROM_UI: &str = "RUSTFS_IDENTITY_OPENID_HIDE_FROM_UI"; /// List of all environment variable keys for an OIDC provider. -pub const ENV_IDENTITY_OPENID_KEYS: &[&str; 16] = &[ +pub const ENV_IDENTITY_OPENID_KEYS: &[&str; 17] = &[ ENV_IDENTITY_OPENID_ENABLE, ENV_IDENTITY_OPENID_CONFIG_URL, ENV_IDENTITY_OPENID_CLIENT_ID, @@ -65,6 +67,7 @@ pub const ENV_IDENTITY_OPENID_KEYS: &[&str; 16] = &[ ENV_IDENTITY_OPENID_ROLES_CLAIM, ENV_IDENTITY_OPENID_EMAIL_CLAIM, ENV_IDENTITY_OPENID_USERNAME_CLAIM, + ENV_IDENTITY_OPENID_HIDE_FROM_UI, ]; /// A list of all valid configuration keys for an OIDC provider. @@ -85,6 +88,7 @@ pub const IDENTITY_OPENID_KEYS: &[&str] = &[ OIDC_ROLES_CLAIM, OIDC_EMAIL_CLAIM, OIDC_USERNAME_CLAIM, + OIDC_HIDE_FROM_UI, crate::COMMENT_KEY, ]; diff --git a/crates/iam/src/oidc.rs b/crates/iam/src/oidc.rs index 726797023..6b51881ee 100644 --- a/crates/iam/src/oidc.rs +++ b/crates/iam/src/oidc.rs @@ -295,6 +295,7 @@ pub struct OidcProviderConfig { pub roles_claim: String, pub email_claim: String, pub username_claim: String, + pub hide_from_ui: bool, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] @@ -422,7 +423,7 @@ impl OidcSys { !self.configs.is_empty() } - /// Return provider summaries for the console UI. + /// List all providers (including hidden ones). Used by site-replication and admin config. pub fn list_providers(&self) -> Vec { self.configs .values() @@ -433,6 +434,18 @@ impl OidcSys { .collect() } + /// List only visible providers (excludes those with `hide_from_ui = true`). + pub fn list_visible_providers(&self) -> Vec { + self.configs + .values() + .filter(|c| !c.hide_from_ui) + .map(|c| OidcProviderSummary { + provider_id: c.id.clone(), + display_name: c.display_name.clone(), + }) + .collect() + } + /// Build the PKCE authorization URL for a provider, store state in the state store. pub async fn authorize_url( &self, @@ -918,6 +931,19 @@ impl OidcSys { configs } + /// Parse a string as an `EnableState` boolean. + /// Returns `default_if_empty` when the input is empty, and `default_on_error` + /// when parsing fails. + fn parse_enable_state(value: &str, default_if_empty: bool, default_on_error: bool) -> bool { + if value.is_empty() { + return default_if_empty; + } + value + .parse::() + .map(|s| s.is_enabled()) + .unwrap_or(default_on_error) + } + /// Parse a single provider's config from env vars with the given suffix. fn parse_single_provider(env_suffix: &str, id: &str) -> Option { let get_env = |base: &str| -> String { std::env::var(format!("{base}{env_suffix}")).unwrap_or_default() }; @@ -930,11 +956,7 @@ impl OidcSys { return None; } - let enabled = enable_val.is_empty() - || enable_val - .parse::() - .map(|s| s.is_enabled()) - .unwrap_or(false); + let enabled = Self::parse_enable_state(&enable_val, true, false); let scopes_str = get_env(ENV_IDENTITY_OPENID_SCOPES); let scopes = if scopes_str.is_empty() { @@ -951,12 +973,7 @@ impl OidcSys { .map(|s| s.to_string()) .collect(); - let redirect_uri_dynamic_str = get_env(ENV_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC); - let redirect_uri_dynamic = redirect_uri_dynamic_str.is_empty() - || redirect_uri_dynamic_str - .parse::() - .map(|s| s.is_enabled()) - .unwrap_or(true); + let redirect_uri_dynamic = Self::parse_enable_state(&get_env(ENV_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC), true, true); let claim_name = { let v = get_env(ENV_IDENTITY_OPENID_CLAIM_NAME); @@ -1003,6 +1020,7 @@ impl OidcSys { let v = get_env(ENV_IDENTITY_OPENID_CLIENT_SECRET); if v.is_empty() { None } else { Some(v) } }; + let hide_from_ui = Self::parse_enable_state(&get_env(ENV_IDENTITY_OPENID_HIDE_FROM_UI), false, false); Some(OidcProviderConfig { id: id.to_string(), @@ -1022,6 +1040,7 @@ impl OidcSys { roles_claim, email_claim, username_claim, + hide_from_ui, }) } @@ -1031,12 +1050,7 @@ impl OidcSys { return None; } - let enabled = kvs - .lookup(ENABLE_KEY) - .unwrap_or_else(|| EnableState::Off.to_string()) - .parse::() - .map(|s| s.is_enabled()) - .unwrap_or(false); + let enabled = Self::parse_enable_state(&kvs.lookup(ENABLE_KEY).unwrap_or_default(), false, false); let scopes_str = kvs.get(OIDC_SCOPES); let scopes = if scopes_str.is_empty() { @@ -1053,12 +1067,8 @@ impl OidcSys { .map(|s| s.to_string()) .collect(); - let redirect_uri_dynamic = kvs - .lookup(OIDC_REDIRECT_URI_DYNAMIC) - .unwrap_or_else(|| EnableState::On.to_string()) - .parse::() - .map(|s| s.is_enabled()) - .unwrap_or(true); + let redirect_uri_dynamic = + Self::parse_enable_state(&kvs.lookup(OIDC_REDIRECT_URI_DYNAMIC).unwrap_or_default(), true, true); let claim_name = kvs .lookup(OIDC_CLAIM_NAME) @@ -1078,6 +1088,7 @@ impl OidcSys { let display_name = kvs.lookup(OIDC_DISPLAY_NAME).unwrap_or_else(|| id.to_string()); let redirect_uri = kvs.lookup(OIDC_REDIRECT_URI).filter(|v| !v.is_empty()); let client_secret = kvs.lookup(OIDC_CLIENT_SECRET).filter(|v| !v.is_empty()); + let hide_from_ui = Self::parse_enable_state(&kvs.lookup(OIDC_HIDE_FROM_UI).unwrap_or_default(), false, false); Some(OidcProviderConfig { id: id.to_string(), @@ -1097,6 +1108,7 @@ impl OidcSys { roles_claim, email_claim, username_claim, + hide_from_ui, }) } @@ -1582,6 +1594,7 @@ mod tests { roles_claim: String::new(), email_claim: "email".to_string(), username_claim: "username".to_string(), + hide_from_ui: false, } } @@ -1961,9 +1974,198 @@ mod tests { roles_claim: String::new(), email_claim: "email".to_string(), username_claim: "preferred_username".to_string(), + hide_from_ui: false, } } + #[test] + fn test_parse_enable_state_on() { + assert!(OidcSys::parse_enable_state("on", false, false)); + } + + #[test] + fn test_parse_enable_state_off() { + assert!(!OidcSys::parse_enable_state("off", true, true)); + } + + #[test] + fn test_parse_enable_state_empty_returns_default() { + assert!(OidcSys::parse_enable_state("", true, false)); + assert!(!OidcSys::parse_enable_state("", false, true)); + } + + #[test] + fn test_parse_enable_state_invalid_returns_error_default() { + assert!(!OidcSys::parse_enable_state("garbage", true, false)); + assert!(OidcSys::parse_enable_state("garbage", false, true)); + } + + #[test] + fn test_list_visible_providers_hides_hidden_provider() { + let visible = test_config("dex"); + let mut hidden = test_config("kubernetes"); + hidden.hide_from_ui = true; + + let sys = make_test_sys(vec![visible, hidden]); + let listed = sys.list_visible_providers(); + + assert_eq!(listed.len(), 1); + assert!(listed.iter().any(|p| p.provider_id == "dex")); + assert!(!listed.iter().any(|p| p.provider_id == "kubernetes")); + } + + #[test] + fn test_hidden_provider_still_resolvable_for_sts() { + let visible = test_config("dex"); + let mut hidden = test_config("kubernetes"); + hidden.hide_from_ui = true; + + let sys = make_test_sys(vec![visible, hidden]); + + assert!(sys.get_provider_config("kubernetes").is_some()); + assert!(sys.get_provider_config("dex").is_some()); + } + + #[test] + fn test_list_providers_includes_hidden_for_replication() { + let visible = test_config("dex"); + let mut hidden = test_config("kubernetes"); + hidden.hide_from_ui = true; + + let sys = make_test_sys(vec![visible, hidden]); + + // Unfiltered list returns all (used by site-replication) + assert_eq!(sys.list_providers().len(), 2); + // UI-filtered list hides the hidden one + assert_eq!(sys.list_visible_providers().len(), 1); + } + + #[test] + fn test_list_providers_all_visible_by_default() { + let a = test_config("okta"); + let b = test_config("dex"); + + let sys = make_test_sys(vec![a, b]); + let listed = sys.list_visible_providers(); + + assert_eq!(listed.len(), 2); + } + + #[test] + fn test_list_visible_providers_all_hidden() { + let mut a = test_config("k8s-a"); + a.hide_from_ui = true; + let mut b = test_config("k8s-b"); + b.hide_from_ui = true; + + let sys = make_test_sys(vec![a, b]); + let listed = sys.list_visible_providers(); + + assert!(listed.is_empty()); + assert!(sys.has_providers()); + } + + #[test] + fn test_hide_from_ui_default_is_false() { + let config = test_config("default"); + assert!(!config.hide_from_ui); + } + + #[test] + fn test_parse_persisted_hide_from_ui_off_is_false() { + let mut cfg = ServerConfig::new(); + let mut kvs = KVS(vec![ + rustfs_ecstore::config::KV { + key: ENABLE_KEY.to_string(), + value: EnableState::On.to_string(), + hidden_if_empty: false, + }, + rustfs_ecstore::config::KV { + key: OIDC_CONFIG_URL.to_string(), + value: "https://example.com/.well-known/openid-configuration".to_string(), + hidden_if_empty: false, + }, + rustfs_ecstore::config::KV { + key: OIDC_CLIENT_ID.to_string(), + value: "console".to_string(), + hidden_if_empty: false, + }, + ]); + kvs.insert(OIDC_HIDE_FROM_UI.to_string(), EnableState::Off.to_string()); + + cfg.0 + .entry(IDENTITY_OPENID_SUB_SYS.to_string()) + .or_default() + .insert(DEFAULT_DELIMITER.to_string(), kvs); + + let parsed = OidcSys::parse_persisted_configs(&cfg); + assert_eq!(parsed.len(), 1); + assert!(!parsed[0].hide_from_ui); + } + + #[test] + fn test_parse_persisted_hide_from_ui_missing_defaults_false() { + let mut cfg = ServerConfig::new(); + let kvs = KVS(vec![ + rustfs_ecstore::config::KV { + key: ENABLE_KEY.to_string(), + value: EnableState::On.to_string(), + hidden_if_empty: false, + }, + rustfs_ecstore::config::KV { + key: OIDC_CONFIG_URL.to_string(), + value: "https://example.com/.well-known/openid-configuration".to_string(), + hidden_if_empty: false, + }, + rustfs_ecstore::config::KV { + key: OIDC_CLIENT_ID.to_string(), + value: "console".to_string(), + hidden_if_empty: false, + }, + ]); + + cfg.0 + .entry(IDENTITY_OPENID_SUB_SYS.to_string()) + .or_default() + .insert(DEFAULT_DELIMITER.to_string(), kvs); + + let parsed = OidcSys::parse_persisted_configs(&cfg); + assert_eq!(parsed.len(), 1); + assert!(!parsed[0].hide_from_ui); + } + + #[test] + fn test_parse_persisted_hide_from_ui() { + let mut cfg = ServerConfig::new(); + let mut kvs = KVS(vec![ + rustfs_ecstore::config::KV { + key: ENABLE_KEY.to_string(), + value: EnableState::On.to_string(), + hidden_if_empty: false, + }, + rustfs_ecstore::config::KV { + key: OIDC_CONFIG_URL.to_string(), + value: "https://example.com/.well-known/openid-configuration".to_string(), + hidden_if_empty: false, + }, + rustfs_ecstore::config::KV { + key: OIDC_CLIENT_ID.to_string(), + value: "console".to_string(), + hidden_if_empty: false, + }, + ]); + kvs.insert(OIDC_HIDE_FROM_UI.to_string(), EnableState::On.to_string()); + + cfg.0 + .entry(IDENTITY_OPENID_SUB_SYS.to_string()) + .or_default() + .insert(DEFAULT_DELIMITER.to_string(), kvs); + + let parsed = OidcSys::parse_persisted_configs(&cfg); + assert_eq!(parsed.len(), 1); + assert!(parsed[0].hide_from_ui); + } + #[test] fn test_map_claims_to_policies_with_provider() { let mut config = test_config("okta"); @@ -2056,6 +2258,7 @@ mod tests { roles_claim: String::new(), email_claim: "email".to_string(), username_claim: "preferred_username".to_string(), + hide_from_ui: false, }; assert_eq!(config.id, "test"); diff --git a/rustfs/src/admin/console.rs b/rustfs/src/admin/console.rs index ad70be8f8..f1feba9ac 100644 --- a/rustfs/src/admin/console.rs +++ b/rustfs/src/admin/console.rs @@ -129,7 +129,7 @@ impl Config { // Collect OIDC provider info if available let oidc = rustfs_iam::get_oidc() .map(|sys| { - sys.list_providers() + sys.list_visible_providers() .into_iter() .map(|p| OidcProviderInfo { provider_id: p.provider_id, diff --git a/rustfs/src/admin/handlers/oidc.rs b/rustfs/src/admin/handlers/oidc.rs index 49a19d95e..53e5ffa91 100644 --- a/rustfs/src/admin/handlers/oidc.rs +++ b/rustfs/src/admin/handlers/oidc.rs @@ -23,8 +23,8 @@ use matchit::Params; use rustfs_config::oidc::{ IDENTITY_OPENID_SUB_SYS, OIDC_CLAIM_NAME, OIDC_CLAIM_PREFIX, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL, OIDC_DEFAULT_CLAIM_NAME, OIDC_DEFAULT_EMAIL_CLAIM, OIDC_DEFAULT_GROUPS_CLAIM, OIDC_DEFAULT_ROLES_CLAIM, OIDC_DEFAULT_SCOPES, - OIDC_DEFAULT_USERNAME_CLAIM, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_OTHER_AUDIENCES, OIDC_REDIRECT_URI, - OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_ROLES_CLAIM, OIDC_SCOPES, OIDC_USERNAME_CLAIM, + OIDC_DEFAULT_USERNAME_CLAIM, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_HIDE_FROM_UI, OIDC_OTHER_AUDIENCES, + OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_ROLES_CLAIM, OIDC_SCOPES, OIDC_USERNAME_CLAIM, }; use rustfs_config::{DEFAULT_DELIMITER, ENABLE_KEY, EnableState, MAX_ADMIN_REQUEST_BODY_SIZE}; use rustfs_ecstore::config::com::{read_config_without_migrate, save_server_config}; @@ -143,6 +143,7 @@ struct OidcConfigView { roles_claim: String, email_claim: String, username_claim: String, + hide_from_ui: bool, } #[derive(Debug, Serialize)] @@ -180,6 +181,7 @@ struct OidcConfigUpsertRequest { roles_claim: String, email_claim: String, username_claim: String, + hide_from_ui: bool, } impl Default for OidcConfigUpsertRequest { @@ -201,6 +203,7 @@ impl Default for OidcConfigUpsertRequest { roles_claim: OIDC_DEFAULT_ROLES_CLAIM.to_string(), email_claim: OIDC_DEFAULT_EMAIL_CLAIM.to_string(), username_claim: OIDC_DEFAULT_USERNAME_CLAIM.to_string(), + hide_from_ui: false, } } } @@ -225,6 +228,7 @@ struct OidcConfigValidateRequest { roles_claim: String, email_claim: String, username_claim: String, + hide_from_ui: bool, } impl Default for OidcConfigValidateRequest { @@ -247,6 +251,7 @@ impl Default for OidcConfigValidateRequest { roles_claim: OIDC_DEFAULT_ROLES_CLAIM.to_string(), email_claim: OIDC_DEFAULT_EMAIL_CLAIM.to_string(), username_claim: OIDC_DEFAULT_USERNAME_CLAIM.to_string(), + hide_from_ui: false, } } } @@ -260,7 +265,7 @@ impl Operation for ListOidcProvidersHandler { async fn call(&self, _req: S3Request, _params: Params<'_, '_>) -> S3Result> { let oidc_sys = rustfs_iam::get_oidc().ok_or_else(|| s3_error!(InternalError, "OIDC not initialized"))?; - let providers = oidc_sys.list_providers(); + let providers = oidc_sys.list_visible_providers(); let json_body = serde_json::to_vec(&providers) .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("serialize error: {e}")))?; @@ -302,6 +307,7 @@ impl Operation for GetOidcConfigHandler { roles_claim: provider.config.roles_claim.clone(), email_claim: provider.config.email_claim.clone(), username_claim: provider.config.username_claim, + hide_from_ui: provider.config.hide_from_ui, }) .collect(); @@ -852,60 +858,61 @@ fn validate_provider_config_fields(config: &rustfs_iam::oidc::OidcProviderConfig Ok(()) } +fn or_default(value: &str, default: &str) -> String { + if value.trim().is_empty() { + default.to_string() + } else { + value.trim().to_string() + } +} + +/// Normalize an `OidcProviderConfig` by trimming strings and applying defaults. +fn normalize_provider_config(mut config: rustfs_iam::oidc::OidcProviderConfig) -> rustfs_iam::oidc::OidcProviderConfig { + config.config_url = config.config_url.trim().to_string(); + config.client_id = config.client_id.trim().to_string(); + config.scopes = normalize_scopes(&config.scopes); + config.redirect_uri = normalize_optional(config.redirect_uri); + config.claim_name = or_default(&config.claim_name, OIDC_DEFAULT_CLAIM_NAME); + config.claim_prefix = config.claim_prefix.trim().to_string(); + config.role_policy = config.role_policy.trim().to_string(); + config.display_name = or_default(&config.display_name, &config.id); + config.groups_claim = or_default(&config.groups_claim, OIDC_DEFAULT_GROUPS_CLAIM); + config.roles_claim = or_default(&config.roles_claim, OIDC_DEFAULT_ROLES_CLAIM); + config.email_claim = or_default(&config.email_claim, OIDC_DEFAULT_EMAIL_CLAIM); + config.username_claim = or_default(&config.username_claim, OIDC_DEFAULT_USERNAME_CLAIM); + config +} + fn build_provider_config_from_upsert( provider_id: &str, request: OidcConfigUpsertRequest, existing_secret: Option, ) -> S3Result { - let scopes = normalize_scopes(&request.scopes); let client_secret = match request.client_secret { Some(value) if !value.trim().is_empty() => Some(value), _ => existing_secret.filter(|value| !value.trim().is_empty()), }; - let config = rustfs_iam::oidc::OidcProviderConfig { + let config = normalize_provider_config(rustfs_iam::oidc::OidcProviderConfig { id: provider_id.to_string(), enabled: request.enabled, - config_url: request.config_url.trim().to_string(), - client_id: request.client_id.trim().to_string(), + config_url: request.config_url, + client_id: request.client_id, client_secret, - scopes, + scopes: request.scopes, other_audiences: request.other_audiences, - redirect_uri: normalize_optional(request.redirect_uri), + redirect_uri: request.redirect_uri, redirect_uri_dynamic: request.redirect_uri_dynamic, - claim_name: if request.claim_name.trim().is_empty() { - OIDC_DEFAULT_CLAIM_NAME.to_string() - } else { - request.claim_name.trim().to_string() - }, - claim_prefix: request.claim_prefix.trim().to_string(), - role_policy: request.role_policy.trim().to_string(), - display_name: if request.display_name.trim().is_empty() { - provider_id.to_string() - } else { - request.display_name.trim().to_string() - }, - groups_claim: if request.groups_claim.trim().is_empty() { - OIDC_DEFAULT_GROUPS_CLAIM.to_string() - } else { - request.groups_claim.trim().to_string() - }, - roles_claim: if request.roles_claim.trim().is_empty() { - OIDC_DEFAULT_ROLES_CLAIM.to_string() - } else { - request.roles_claim.trim().to_string() - }, - email_claim: if request.email_claim.trim().is_empty() { - OIDC_DEFAULT_EMAIL_CLAIM.to_string() - } else { - request.email_claim.trim().to_string() - }, - username_claim: if request.username_claim.trim().is_empty() { - OIDC_DEFAULT_USERNAME_CLAIM.to_string() - } else { - request.username_claim.trim().to_string() - }, - }; + claim_name: request.claim_name, + claim_prefix: request.claim_prefix, + role_policy: request.role_policy, + display_name: request.display_name, + groups_claim: request.groups_claim, + roles_claim: request.roles_claim, + email_claim: request.email_claim, + username_claim: request.username_claim, + hide_from_ui: request.hide_from_ui, + }); validate_provider_config_fields(&config)?; Ok(config) @@ -915,49 +922,26 @@ fn build_provider_config_from_validate( request: OidcConfigValidateRequest, provider_id: &str, ) -> S3Result { - let config = rustfs_iam::oidc::OidcProviderConfig { + let config = normalize_provider_config(rustfs_iam::oidc::OidcProviderConfig { id: provider_id.to_string(), enabled: request.enabled, - config_url: request.config_url.trim().to_string(), - client_id: request.client_id.trim().to_string(), + config_url: request.config_url, + client_id: request.client_id, client_secret: request.client_secret.filter(|value| !value.trim().is_empty()), - scopes: normalize_scopes(&request.scopes), + scopes: request.scopes, other_audiences: request.other_audiences, - redirect_uri: normalize_optional(request.redirect_uri), + redirect_uri: request.redirect_uri, redirect_uri_dynamic: request.redirect_uri_dynamic, - claim_name: if request.claim_name.trim().is_empty() { - OIDC_DEFAULT_CLAIM_NAME.to_string() - } else { - request.claim_name.trim().to_string() - }, - claim_prefix: request.claim_prefix.trim().to_string(), - role_policy: request.role_policy.trim().to_string(), - display_name: if request.display_name.trim().is_empty() { - provider_id.to_string() - } else { - request.display_name.trim().to_string() - }, - groups_claim: if request.groups_claim.trim().is_empty() { - OIDC_DEFAULT_GROUPS_CLAIM.to_string() - } else { - request.groups_claim.trim().to_string() - }, - roles_claim: if request.roles_claim.trim().is_empty() { - OIDC_DEFAULT_ROLES_CLAIM.to_string() - } else { - request.roles_claim.trim().to_string() - }, - email_claim: if request.email_claim.trim().is_empty() { - OIDC_DEFAULT_EMAIL_CLAIM.to_string() - } else { - request.email_claim.trim().to_string() - }, - username_claim: if request.username_claim.trim().is_empty() { - OIDC_DEFAULT_USERNAME_CLAIM.to_string() - } else { - request.username_claim.trim().to_string() - }, - }; + claim_name: request.claim_name, + claim_prefix: request.claim_prefix, + role_policy: request.role_policy, + display_name: request.display_name, + groups_claim: request.groups_claim, + roles_claim: request.roles_claim, + email_claim: request.email_claim, + username_claim: request.username_claim, + hide_from_ui: request.hide_from_ui, + }); validate_provider_config_fields(&config)?; Ok(config) @@ -1008,6 +992,15 @@ fn upsert_persisted_provider_config(config: &mut ServerConfig, provider_config: set_kvs_value(&mut kvs, OIDC_ROLES_CLAIM, provider_config.roles_claim.clone()); set_kvs_value(&mut kvs, OIDC_EMAIL_CLAIM, provider_config.email_claim.clone()); set_kvs_value(&mut kvs, OIDC_USERNAME_CLAIM, provider_config.username_claim.clone()); + set_kvs_value( + &mut kvs, + OIDC_HIDE_FROM_UI, + if provider_config.hide_from_ui { + EnableState::On.to_string() + } else { + EnableState::Off.to_string() + }, + ); config .0 @@ -1285,6 +1278,7 @@ mod tests { roles_claim: OIDC_DEFAULT_ROLES_CLAIM.to_string(), email_claim: OIDC_DEFAULT_EMAIL_CLAIM.to_string(), username_claim: OIDC_DEFAULT_USERNAME_CLAIM.to_string(), + hide_from_ui: false, }; upsert_persisted_provider_config(&mut persisted_config, &provider_config); @@ -1292,4 +1286,49 @@ mod tests { assert!(oidc_restart_required_from_active_config(&persisted_config, Some(&active_config))); assert!(!oidc_restart_required_from_active_config(&persisted_config, Some(&persisted_config))); } + + #[test] + fn test_upsert_persists_hide_from_ui_on() { + let mut config = ServerConfig::new(); + let mut provider_config = rustfs_iam::oidc::OidcProviderConfig { + id: "kubernetes".to_string(), + enabled: true, + config_url: "https://example.com/.well-known/openid-configuration".to_string(), + client_id: "test".to_string(), + client_secret: None, + scopes: vec!["openid".to_string()], + other_audiences: vec![], + redirect_uri: None, + redirect_uri_dynamic: true, + claim_name: "sub".to_string(), + claim_prefix: String::new(), + role_policy: String::new(), + display_name: "Kubernetes".to_string(), + groups_claim: OIDC_DEFAULT_GROUPS_CLAIM.to_string(), + roles_claim: OIDC_DEFAULT_ROLES_CLAIM.to_string(), + email_claim: OIDC_DEFAULT_EMAIL_CLAIM.to_string(), + username_claim: OIDC_DEFAULT_USERNAME_CLAIM.to_string(), + hide_from_ui: true, + }; + + upsert_persisted_provider_config(&mut config, &provider_config); + + let kvs = config + .0 + .get(IDENTITY_OPENID_SUB_SYS) + .and_then(|m| m.get("kubernetes")) + .expect("provider KVS should exist"); + assert_eq!(kvs.get(OIDC_HIDE_FROM_UI), EnableState::On.to_string()); + + // Flip to false and verify + provider_config.hide_from_ui = false; + upsert_persisted_provider_config(&mut config, &provider_config); + + let kvs = config + .0 + .get(IDENTITY_OPENID_SUB_SYS) + .and_then(|m| m.get("kubernetes")) + .expect("provider KVS should exist"); + assert_eq!(kvs.get(OIDC_HIDE_FROM_UI), EnableState::Off.to_string()); + } }