From b189caaab8eea5f9a907243c95564b99d5aae5a6 Mon Sep 17 00:00:00 2001 From: cduvray Date: Wed, 1 Feb 2023 22:08:28 +0100 Subject: [PATCH] refactor: Authorizer::build --- .editorconfig | 9 +++ jwt-authorizer/docs/README.md | 13 ++-- jwt-authorizer/src/authorizer.rs | 116 ++++++++++++++++++++----------- jwt-authorizer/src/error.rs | 3 + jwt-authorizer/src/layer.rs | 42 +++-------- 5 files changed, 102 insertions(+), 81 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d741e40 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/jwt-authorizer/docs/README.md b/jwt-authorizer/docs/README.md index 0abbfc0..0b2d6ed 100644 --- a/jwt-authorizer/docs/README.md +++ b/jwt-authorizer/docs/README.md @@ -15,11 +15,13 @@ JWT authoriser Layer for Axum. ## Usage Example ```rust - use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims}; - use axum::{routing::get, Router}; - use serde::Deserialize; +# use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims}; +# use axum::{routing::get, Router}; +# use serde::Deserialize; - // Authorized entity, struct deserializable from JWT claims +# async { + + // struct representing the authorized caller, deserializable from JWT claims #[derive(Debug, Deserialize, Clone)] struct User { sub: String, @@ -39,10 +41,9 @@ JWT authoriser Layer for Axum. Ok(format!("Welcome: {}", user.sub)) } - # async { axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()).await.expect("server failed"); - # }; +# }; ``` ## ClaimsChecker diff --git a/jwt-authorizer/src/authorizer.rs b/jwt-authorizer/src/authorizer.rs index 875ded0..5866db8 100644 --- a/jwt-authorizer/src/authorizer.rs +++ b/jwt-authorizer/src/authorizer.rs @@ -6,7 +6,7 @@ use serde::de::DeserializeOwned; use crate::{ error::{AuthError, InitError}, jwks::{key_store_manager::KeyStoreManager, KeySource}, - Refresh, + oidc, Refresh, }; pub trait ClaimsChecker { @@ -51,6 +51,7 @@ pub enum KeySourceType { ED(String), Secret(&'static str), Jwks(String), + JwksString(String), // TODO: expose JwksString in JwtAuthorizer or remove it Discovery(String), } @@ -58,44 +59,65 @@ impl Authorizer where C: DeserializeOwned + Clone + Send + Sync, { - // TODO: expose it in JwtAuthorizer - pub fn from_jwks(jwks: &str, claims_checker: Option>) -> Result, AuthError> { - let set: JwkSet = serde_json::from_str(jwks)?; - let k = DecodingKey::from_jwk(&set.keys[0])?; - - Ok(Authorizer { - key_source: KeySource::DecodingKeySource(k), - claims_checker, - }) - } - - pub(crate) fn from( + pub(crate) async fn build( key_source_type: &KeySourceType, claims_checker: Option>, + refresh: Option, ) -> Result, InitError> { - let key = match key_source_type { - KeySourceType::RSA(path) => DecodingKey::from_rsa_pem(&read_data(path.as_str())?)?, - KeySourceType::EC(path) => DecodingKey::from_ec_der(&read_data(path.as_str())?), - KeySourceType::ED(path) => DecodingKey::from_ed_der(&read_data(path.as_str())?), - KeySourceType::Secret(secret) => DecodingKey::from_secret(secret.as_bytes()), - _ => panic!("bug: use from_jwks_url() or from_oidc() to initialise Authorizer"), // should never hapen - }; - - Ok(Authorizer { - key_source: KeySource::DecodingKeySource(key), - claims_checker, - }) - } - - pub(crate) fn from_jwks_url( - url: &str, - claims_checker: Option>, - refresh: Refresh, - ) -> Result, InitError> { - let key_store_manager = KeyStoreManager::new(url, refresh); - Ok(Authorizer { - key_source: KeySource::KeyStoreSource(key_store_manager), - claims_checker, + Ok(match key_source_type { + KeySourceType::RSA(path) => { + let key = DecodingKey::from_rsa_pem(&read_data(path.as_str())?)?; + Authorizer { + key_source: KeySource::DecodingKeySource(key), + claims_checker, + } + } + KeySourceType::EC(path) => { + let key = DecodingKey::from_ec_der(&read_data(path.as_str())?); + Authorizer { + key_source: KeySource::DecodingKeySource(key), + claims_checker, + } + } + KeySourceType::ED(path) => { + let key = DecodingKey::from_ed_der(&read_data(path.as_str())?); + Authorizer { + key_source: KeySource::DecodingKeySource(key), + claims_checker, + } + } + KeySourceType::Secret(secret) => { + let key = DecodingKey::from_secret(secret.as_bytes()); + Authorizer { + key_source: KeySource::DecodingKeySource(key), + claims_checker, + } + } + KeySourceType::JwksString(jwks_str) => { + // TODO: expose it in JwtAuthorizer or remove + let set: JwkSet = serde_json::from_str(jwks_str)?; + // TODO: replace [0] by kid/alg search + let k = DecodingKey::from_jwk(&set.keys[0])?; + Authorizer { + key_source: KeySource::DecodingKeySource(k), + claims_checker, + } + } + KeySourceType::Jwks(url) => { + let key_store_manager = KeyStoreManager::new(url, refresh.unwrap_or_default()); + Authorizer { + key_source: KeySource::KeyStoreSource(key_store_manager), + claims_checker, + } + } + KeySourceType::Discovery(issuer_url) => { + let jwks_url = oidc::discover_jwks(issuer_url).await?; + let key_store_manager = KeyStoreManager::new(&jwks_url, refresh.unwrap_or_default()); + Authorizer { + key_source: KeySource::KeyStoreSource(key_store_manager), + claims_checker, + } + } }) } @@ -126,7 +148,9 @@ mod tests { #[tokio::test] async fn from_secret() { let h = Header::new(Algorithm::HS256); - let a = Authorizer::::from(&KeySourceType::Secret("xxxxxx"), None).unwrap(); + let a = Authorizer::::build(&KeySourceType::Secret("xxxxxx"), None, None) + .await + .unwrap(); let k = a.key_source.get_key(h); assert!(k.await.is_ok()); } @@ -143,29 +167,37 @@ mod tests { "e": "AQAB" }]} "#; - let a = Authorizer::::from_jwks(jwks, None).unwrap(); + let a = Authorizer::::build(&KeySourceType::JwksString(jwks.to_owned()), None, None) + .await + .unwrap(); let k = a.key_source.get_key(Header::new(Algorithm::RS256)); assert!(k.await.is_ok()); } #[tokio::test] async fn from_file() { - let a = Authorizer::::from(&KeySourceType::RSA("../config/jwtRS256.key.pub".to_owned()), None).unwrap(); + let a = Authorizer::::build(&KeySourceType::RSA("../config/jwtRS256.key.pub".to_owned()), None, None) + .await + .unwrap(); let k = a.key_source.get_key(Header::new(Algorithm::RS256)); assert!(k.await.is_ok()); - let a = Authorizer::::from(&KeySourceType::EC("../config/ec256-public.pem".to_owned()), None).unwrap(); + let a = Authorizer::::build(&KeySourceType::EC("../config/ec256-public.pem".to_owned()), None, None) + .await + .unwrap(); let k = a.key_source.get_key(Header::new(Algorithm::ES256)); assert!(k.await.is_ok()); - let a = Authorizer::::from(&KeySourceType::ED("../config/ed25519-public.pem".to_owned()), None).unwrap(); + let a = Authorizer::::build(&KeySourceType::ED("../config/ed25519-public.pem".to_owned()), None, None) + .await + .unwrap(); let k = a.key_source.get_key(Header::new(Algorithm::EdDSA)); assert!(k.await.is_ok()); } #[tokio::test] async fn from_file_errors() { - let a = Authorizer::::from(&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()), None); + let a = Authorizer::::build(&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()), None, None).await; println!("{:?}", a.as_ref().err()); assert!(a.is_err()); } diff --git a/jwt-authorizer/src/error.rs b/jwt-authorizer/src/error.rs index 75538ac..9136931 100644 --- a/jwt-authorizer/src/error.rs +++ b/jwt-authorizer/src/error.rs @@ -22,6 +22,9 @@ pub enum InitError { #[error("Builder Error {0}")] DiscoveryError(String), + + #[error("Jwks Parsing Error {0}")] + JwksParsingError(#[from] serde_json::Error), } #[derive(Debug, Error)] diff --git a/jwt-authorizer/src/layer.rs b/jwt-authorizer/src/layer.rs index c24faf1..13d2418 100644 --- a/jwt-authorizer/src/layer.rs +++ b/jwt-authorizer/src/layer.rs @@ -17,7 +17,7 @@ use tower_service::Service; use crate::authorizer::{Authorizer, FnClaimsChecker, KeySourceType}; use crate::error::InitError; use crate::jwks::key_store_manager::Refresh; -use crate::{oidc, AuthError, RefreshStrategy}; +use crate::{AuthError, RefreshStrategy}; /// Authorizer Layer builder /// @@ -27,7 +27,7 @@ pub struct JwtAuthorizer where C: Clone + DeserializeOwned, { - key_source_type: Option, + key_source_type: KeySourceType, refresh: Option, claims_checker: Option>, } @@ -40,7 +40,7 @@ where /// Builds Authorizer Layer from a OpenId Connect discover metadata pub fn from_oidc(issuer: &str) -> JwtAuthorizer { JwtAuthorizer { - key_source_type: Some(KeySourceType::Discovery(issuer.to_string())), + key_source_type: KeySourceType::Discovery(issuer.to_string()), refresh: Default::default(), claims_checker: None, } @@ -49,7 +49,7 @@ where /// Builds Authorizer Layer from a JWKS endpoint pub fn from_jwks_url(url: &'static str) -> JwtAuthorizer { JwtAuthorizer { - key_source_type: Some(KeySourceType::Jwks(url.to_owned())), + key_source_type: KeySourceType::Jwks(url.to_owned()), refresh: Default::default(), claims_checker: None, } @@ -58,7 +58,7 @@ where /// Builds Authorizer Layer from a RSA PEM file pub fn from_rsa_pem(path: &'static str) -> JwtAuthorizer { JwtAuthorizer { - key_source_type: Some(KeySourceType::RSA(path.to_owned())), + key_source_type: KeySourceType::RSA(path.to_owned()), refresh: Default::default(), claims_checker: None, } @@ -67,7 +67,7 @@ where /// Builds Authorizer Layer from a EC PEM file pub fn from_ec_pem(path: &'static str) -> JwtAuthorizer { JwtAuthorizer { - key_source_type: Some(KeySourceType::EC(path.to_owned())), + key_source_type: KeySourceType::EC(path.to_owned()), refresh: Default::default(), claims_checker: None, } @@ -76,7 +76,7 @@ where /// Builds Authorizer Layer from a EC PEM file pub fn from_ed_pem(path: &'static str) -> JwtAuthorizer { JwtAuthorizer { - key_source_type: Some(KeySourceType::ED(path.to_owned())), + key_source_type: KeySourceType::ED(path.to_owned()), refresh: Default::default(), claims_checker: None, } @@ -85,7 +85,7 @@ where /// Builds Authorizer Layer from a secret phrase pub fn from_secret(secret: &'static str) -> JwtAuthorizer { JwtAuthorizer { - key_source_type: Some(KeySourceType::Secret(secret)), + key_source_type: KeySourceType::Secret(secret), refresh: Default::default(), claims_checker: None, } @@ -122,31 +122,7 @@ where /// Build axum layer pub async fn layer(&self) -> Result, InitError> { - let auth = if let Some(ref key_source_type) = self.key_source_type { - match key_source_type { - KeySourceType::RSA(_) | KeySourceType::EC(_) | KeySourceType::ED(_) | KeySourceType::Secret(_) => { - Arc::new(Authorizer::from(key_source_type, self.claims_checker.clone())?) - } - KeySourceType::Jwks(url) => Arc::new(Authorizer::from_jwks_url( - url.as_str(), - self.claims_checker.clone(), - self.refresh.unwrap_or_default(), - )?), - KeySourceType::Discovery(issuer_url) => { - let jwks_url = oidc::discover_jwks(issuer_url).await?; - Arc::new(Authorizer::from_jwks_url( - &jwks_url, - self.claims_checker.clone(), - self.refresh.unwrap_or_default(), - )?) - } - } - } else { - return Err(InitError::BuilderError( - "No key source to build the layer user from_*() to specify the key source!".to_owned(), - )); - }; - + let auth = Arc::new(Authorizer::build(&self.key_source_type, self.claims_checker.clone(), self.refresh).await?); Ok(AsyncAuthorizationLayer::new(auth)) } }