diff --git a/common/auth/src/client/provider/openid.rs b/common/auth/src/client/provider/openid.rs index 0c1c9b06d..66d195a78 100644 --- a/common/auth/src/client/provider/openid.rs +++ b/common/auth/src/client/provider/openid.rs @@ -50,6 +50,10 @@ pub struct OpenIdTokenProviderConfigArguments { default_value = "false" )] pub tls_insecure: bool, + /// OAuth scope(s) to request in the client_credentials token request. + /// Space-separated when multiple scopes are needed. If unset, no scope parameter is sent. + #[arg(id = "oidc_scope", long = "oidc-scope", env = "OIDC_PROVIDER_SCOPE")] + pub scope: Option, } impl OpenIdTokenProviderConfigArguments { @@ -60,6 +64,7 @@ impl OpenIdTokenProviderConfigArguments { client_secret: Some(devmode::SSO_CLIENT_SECRET.to_string()), refresh_before: Duration::from_secs(30).into(), tls_insecure: false, + scope: None, } } } @@ -89,6 +94,7 @@ pub struct OpenIdTokenProviderConfig { pub issuer_url: String, pub refresh_before: humantime::Duration, pub tls_insecure: bool, + pub scope: Option, } impl OpenIdTokenProviderConfig { @@ -99,6 +105,7 @@ impl OpenIdTokenProviderConfig { client_secret: devmode::SSO_CLIENT_SECRET.to_string(), refresh_before: Duration::from_secs(30).into(), tls_insecure: false, + scope: None, } } @@ -132,6 +139,7 @@ impl OpenIdTokenProviderConfig { issuer_url, refresh_before: arguments.refresh_before, tls_insecure: arguments.tls_insecure, + scope: arguments.scope, }) } _ => None, @@ -151,6 +159,7 @@ pub struct OpenIdTokenProvider { client: Arc, current_token: Arc>>, refresh_before: chrono::Duration, + scope: Option, } impl Debug for OpenIdTokenProvider { @@ -167,11 +176,16 @@ impl Debug for OpenIdTokenProvider { impl OpenIdTokenProvider { /// Create a new provider using the provided client. - pub fn new(client: openid::Client, refresh_before: chrono::Duration) -> Self { + pub fn new( + client: openid::Client, + refresh_before: chrono::Duration, + scope: Option, + ) -> Self { Self { client: Arc::new(client), current_token: Arc::new(RwLock::new(None)), refresh_before, + scope, } } @@ -198,6 +212,7 @@ impl OpenIdTokenProvider { Ok(Self::new( client, chrono::Duration::from_std(config.refresh_before.into())?, + config.scope, )) } @@ -243,7 +258,11 @@ impl OpenIdTokenProvider { Some(current_token) => { log::debug!("Refreshing token ... "); match current_token.as_ref().refresh_token.is_some() { - true => self.client.refresh_token(current_token, None).await?.into(), + true => self + .client + .refresh_token(current_token, self.scope.as_deref()) + .await? + .into(), false => self.initial_token().await?, } } @@ -262,7 +281,7 @@ impl OpenIdTokenProvider { async fn initial_token(&self) -> Result { Ok(self .client - .request_token_using_client_credentials(Some("openid")) + .request_token_using_client_credentials(self.scope.as_deref()) .await? .into()) } diff --git a/server/src/profile/api.rs b/server/src/profile/api.rs index 07f01c259..523f2fb30 100644 --- a/server/src/profile/api.rs +++ b/server/src/profile/api.rs @@ -260,6 +260,15 @@ pub struct EiOidcArguments { default_value = "false" )] pub tls_insecure: bool, + + /// OAuth scope(s) to request in the client_credentials token request. + /// Space-separated when multiple scopes are needed. If unset, no scope parameter is sent. + #[arg( + id = "ei_oidc_scope", + long = "ei-oidc-scope", + env = "EXPLOIT_INTELLIGENCE_OIDC_SCOPE" + )] + pub scope: Option, } impl EiOidcArguments { @@ -276,6 +285,7 @@ impl EiOidcArguments { issuer_url, refresh_before: self.refresh_before, tls_insecure: self.tls_insecure, + scope: self.scope, }) } } @@ -909,6 +919,7 @@ mod test { issuer_url: None, refresh_before: "30s".parse().unwrap(), tls_insecure: false, + scope: None, }, } } @@ -958,6 +969,7 @@ mod test { issuer_url: None, refresh_before: "30s".parse().unwrap(), tls_insecure: false, + scope: None, }; assert!(args.into_config().is_none()); } @@ -970,12 +982,28 @@ mod test { issuer_url: Some("https://idp.example.com".into()), refresh_before: "60s".parse().unwrap(), tls_insecure: true, + scope: None, }; let config = args.into_config().expect("config should be Some"); assert_eq!(config.client_id, "client"); assert_eq!(config.client_secret, "secret"); assert_eq!(config.issuer_url, "https://idp.example.com"); assert!(config.tls_insecure); + assert!(config.scope.is_none()); + } + + #[test] + fn oidc_into_config_propagates_scope() { + let args = EiOidcArguments { + client_id: Some("client".into()), + client_secret: Some("secret".into()), + issuer_url: Some("https://idp.example.com".into()), + refresh_before: "60s".parse().unwrap(), + tls_insecure: false, + scope: Some("my-api/read my-api/write".into()), + }; + let config = args.into_config().expect("config should be Some"); + assert_eq!(config.scope.as_deref(), Some("my-api/read my-api/write")); } #[test_context(TrustifyContext)]