From ca23823130b122a57498dee12ab451e79084b4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Sch=C3=B6nenberg?= Date: Mon, 30 Sep 2024 13:27:10 +0200 Subject: [PATCH] feat: Add Method to expose the JwkSet to the DapsClient user --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/config.rs | 49 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 68 ++++++++++----------------------------------------- 4 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index e152b18..ff48447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,7 +783,7 @@ dependencies = [ [[package]] name = "ids-daps-client" -version = "0.1.0" +version = "0.2.0" dependencies = [ "async-lock", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index f34af1d..0fd924e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ edition = "2021" name = "ids-daps-client" description = "A client to connect with the IDS DAPS." -version = "0.1.0" +version = "0.2.0" license = "Apache-2.0" repository = "https://github.com/truzzt/ids-daps-client-rs" readme = "README.md" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..db1355f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,49 @@ +use std::borrow::Cow; + +/// Configuration for the DAPS client. +#[derive(Debug, derive_builder::Builder)] +#[builder(setter(into), build_fn(validate = "Self::validate"))] +pub struct DapsConfig<'a> { + /// The URL for the request of a DAPS token. + pub(super) token_url: Cow<'a, str>, + /// The URL for the request of the certificates for validation. + pub(super) certs_url: Cow<'a, str>, + /// The local path to the private key file. + pub(super) private_key: Cow<'a, std::path::Path>, + /// The password for the private key file. + pub(super) private_key_password: Option>, + /// The scope for the DAPS token. + pub(super) scope: Cow<'a, str>, + /// The time-to-live for the certificates cache in seconds. + pub(super) certs_cache_ttl: u64, +} + +impl DapsConfigBuilder<'_> { + /// Validates the configuration. + pub fn validate(&self) -> Result<(), String> { + if self.token_url.is_none() { + return Err("Token URL is empty".to_string()); + } else if let Some(token_url) = self.token_url.clone() { + token_url + .parse::() + .map_err(|e| format!("Token URL is invalid: {e}"))?; + } + if self.certs_url.is_none() { + return Err("Certs URL is empty".to_string()); + } else if let Some(certs_url) = self.certs_url.clone() { + certs_url + .parse::() + .map_err(|e| format!("Certs URL is invalid: {e}"))?; + } + if self.private_key.is_none() { + return Err("Private key path is empty".to_string()); + } + if self.private_key_password.is_none() { + return Err("Private key password is empty".to_string()); + } + if self.scope.is_none() { + return Err("Scope is empty".to_string()); + } + Ok(()) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9410e13..255d035 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! ## Usage //! //! ``` -//! use ids_daps_client::{DapsConfigBuilder, DapsClient, ReqwestDapsClient}; +//! use ids_daps_client::{config::DapsConfigBuilder, DapsClient, ReqwestDapsClient}; //! # use testcontainers::runners::AsyncRunner; //! //! #[tokio::main] @@ -45,7 +45,7 @@ //! .expect("Failed to build DAPS-Config"); //! //! // Create DAPS client -//! let client: ReqwestDapsClient<'_> = DapsClient::new(&config); +//! let client: ReqwestDapsClient<'_> = DapsClient::new(config); //! //! // Request a DAT token //! let dat = client.request_dat().await?; @@ -65,6 +65,7 @@ mod cache; pub mod cert; +pub mod config; mod http_client; use std::borrow::Cow; @@ -140,54 +141,6 @@ pub enum DapsError { CacheError(#[from] cache::CertificatesCacheError), } -/// Configuration for the DAPS client. -#[derive(Debug, derive_builder::Builder)] -#[builder(setter(into), build_fn(validate = "Self::validate"))] -pub struct DapsConfig<'a> { - /// The URL for the request of a DAPS token. - token_url: Cow<'a, str>, - /// The URL for the request of the certificates for validation. - certs_url: Cow<'a, str>, - /// The local path to the private key file. - private_key: Cow<'a, std::path::Path>, - /// The password for the private key file. - private_key_password: Option>, - /// The scope for the DAPS token. - scope: Cow<'a, str>, - /// The time-to-live for the certificates cache in seconds. - certs_cache_ttl: u64, -} - -impl DapsConfigBuilder<'_> { - /// Validates the configuration. - pub fn validate(&self) -> Result<(), String> { - if self.token_url.is_none() { - return Err("Token URL is empty".to_string()); - } else if let Some(token_url) = self.token_url.clone() { - token_url - .parse::() - .map_err(|e| format!("Token URL is invalid: {e}"))?; - } - if self.certs_url.is_none() { - return Err("Certs URL is empty".to_string()); - } else if let Some(certs_url) = self.certs_url.clone() { - certs_url - .parse::() - .map_err(|e| format!("Certs URL is invalid: {e}"))?; - } - if self.private_key.is_none() { - return Err("Private key path is empty".to_string()); - } - if self.private_key_password.is_none() { - return Err("Private key password is empty".to_string()); - } - if self.scope.is_none() { - return Err("Scope is empty".to_string()); - } - Ok(()) - } -} - /// An alias for the DAPS client using the Reqwest HTTP client. pub type ReqwestDapsClient<'a> = DapsClient<'a, http_client::reqwest_client::ReqwestDapsClient>; @@ -218,7 +171,7 @@ where { /// Creates a new DAPS client based on the given configuration. #[must_use] - pub fn new(config: &DapsConfig<'_>) -> Self { + pub fn new(config: config::DapsConfig<'_>) -> Self { // Read sub and private key from file let (ski_aki, private_key) = cert::ski_aki_and_private_key_from_file( config.private_key.as_ref(), @@ -298,7 +251,7 @@ where iss: self.sub.to_string(), sub: self.sub.to_string(), id: self.sub.to_string(), - aud: self.scope.clone(), + aud: self.scope.to_string(), iat: now_secs, exp: now_secs + 3600, nbf: now_secs, @@ -328,6 +281,11 @@ where Ok(response.access_token) } + /// Returns the `jsonwebtoken::jwk::JwkSet` either from the DAPS or from the cache. + pub async fn get_jwks(&self) -> Result { + self.get_certs().await + } + /// Updates the certificate cache with the Certificates requested from the DAPS. async fn update_cert_cache(&self) -> Result { let jwks = self.client.get_certs(self.certs_url.as_ref()).await?; @@ -380,7 +338,7 @@ mod test { )) .start() .await - .expect("Failed to start DAPS container"); + .expect("Failed to start DAPS container. Is Docker running?"); // Retrieve the host port mapped to the container's internal port 4567 let host = container.get_host().await.expect("Failed to get host"); @@ -394,7 +352,7 @@ mod test { let token_url = format!("http://{host}:{host_port}/token"); // Create DAPS config - let config = DapsConfigBuilder::create_empty() + let config = config::DapsConfigBuilder::default() .certs_url(certs_url) .token_url(token_url) .private_key(std::path::Path::new("./testdata/connector-certificate.p12")) @@ -405,7 +363,7 @@ mod test { .expect("Failed to build DAPS-Config"); // Create DAPS client - let client: ReqwestDapsClient<'_> = DapsClient::new(&config); + let client: ReqwestDapsClient<'_> = DapsClient::new(config); // Now the test really starts... // Request a DAT token