refactor: better url error checking (jwks, oidc)

This commit is contained in:
cduvray 2023-02-05 09:40:53 +01:00
parent b189caaab8
commit f1b11ecf3b
4 changed files with 43 additions and 17 deletions

View file

@ -1,6 +1,7 @@
use std::io::Read;
use jsonwebtoken::{decode, decode_header, jwk::JwkSet, DecodingKey, TokenData, Validation};
use reqwest::Url;
use serde::de::DeserializeOwned;
use crate::{
@ -104,15 +105,18 @@ where
}
}
KeySourceType::Jwks(url) => {
let key_store_manager = KeyStoreManager::new(url, refresh.unwrap_or_default());
let jwks_url = Url::parse(url).map_err(|e| InitError::JwksUrlError(e.to_string()))?;
let key_store_manager = KeyStoreManager::new(jwks_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());
let jwks_url = Url::parse(&oidc::discover_jwks(issuer_url).await?)
.map_err(|e| InitError::JwksUrlError(e.to_string()))?;
let key_store_manager = KeyStoreManager::new(jwks_url, refresh.unwrap_or_default());
Authorizer {
key_source: KeySource::KeyStoreSource(key_store_manager),
claims_checker,
@ -146,7 +150,7 @@ mod tests {
use super::{Authorizer, KeySourceType};
#[tokio::test]
async fn from_secret() {
async fn build_from_secret() {
let h = Header::new(Algorithm::HS256);
let a = Authorizer::<Value>::build(&KeySourceType::Secret("xxxxxx"), None, None)
.await
@ -156,7 +160,7 @@ mod tests {
}
#[tokio::test]
async fn from_jwks() {
async fn build_from_jwks_string() {
let jwks = r#"
{"keys": [{
"kid": "1",
@ -175,7 +179,7 @@ mod tests {
}
#[tokio::test]
async fn from_file() {
async fn build_from_file() {
let a = Authorizer::<Value>::build(&KeySourceType::RSA("../config/jwtRS256.key.pub".to_owned()), None, None)
.await
.unwrap();
@ -196,9 +200,23 @@ mod tests {
}
#[tokio::test]
async fn from_file_errors() {
async fn build_file_errors() {
let a = Authorizer::<Value>::build(&KeySourceType::RSA("./config/does-not-exist.pem".to_owned()), None, None).await;
println!("{:?}", a.as_ref().err());
assert!(a.is_err());
}
#[tokio::test]
async fn build_jwks_url_error() {
let a = Authorizer::<Value>::build(&&KeySourceType::Jwks("://xxxx".to_owned()), None, None).await;
println!("{:?}", a.as_ref().err());
assert!(a.is_err());
}
#[tokio::test]
async fn build_discovery_url_error() {
let a = Authorizer::<Value>::build(&&KeySourceType::Discovery("://xxxx".to_owned()), None, None).await;
println!("{:?}", a.as_ref().err());
assert!(a.is_err());
}
}

View file

@ -23,6 +23,9 @@ pub enum InitError {
#[error("Builder Error {0}")]
DiscoveryError(String),
#[error("Builder Error {0}")]
JwksUrlError(String),
#[error("Jwks Parsing Error {0}")]
JwksParsingError(#[from] serde_json::Error),
}
@ -74,7 +77,7 @@ fn response_500() -> Response<BoxBody> {
res
}
/// (https://datatracker.ietf.org/doc/html/rfc6750#section-3.1)
/// (https://datatracker.ietf.org/doc/html/rfc6750#section-3.1)
impl IntoResponse for AuthError {
fn into_response(self) -> Response {
let resp = match self {

View file

@ -2,6 +2,7 @@ use jsonwebtoken::{
jwk::{Jwk, JwkSet},
Algorithm, DecodingKey,
};
use reqwest::Url;
use std::{
sync::Arc,
time::{Duration, Instant},
@ -47,7 +48,7 @@ impl Default for Refresh {
#[derive(Clone)]
pub struct KeyStoreManager {
key_url: String,
key_url: Url,
/// in case of fail loading (error or key not found), minimal interval
refresh: Refresh,
keystore: Arc<Mutex<KeyStore>>,
@ -63,9 +64,9 @@ pub struct KeyStore {
}
impl KeyStoreManager {
pub(crate) fn new(url: &str, refresh: Refresh) -> KeyStoreManager {
pub(crate) fn new(key_url: Url, refresh: Refresh) -> KeyStoreManager {
KeyStoreManager {
key_url: url.to_owned(),
key_url,
refresh,
keystore: Arc::new(Mutex::new(KeyStore {
jwks: JwkSet { keys: vec![] },
@ -164,9 +165,9 @@ impl KeyStore {
}
}
async fn refresh(&mut self, key_url: &str, qparam: &[(&str, &str)]) -> Result<(), AuthError> {
async fn refresh(&mut self, key_url: &Url, qparam: &[(&str, &str)]) -> Result<(), AuthError> {
reqwest::Client::new()
.get(key_url)
.get(key_url.as_ref())
.query(qparam)
.send()
.await
@ -212,6 +213,7 @@ mod tests {
use jsonwebtoken::Algorithm;
use jsonwebtoken::{jwk::Jwk, Header};
use reqwest::Url;
use wiremock::{
matchers::{method, path},
Mock, MockServer, ResponseTemplate,
@ -330,7 +332,7 @@ mod tests {
.await;
let ksm = KeyStoreManager::new(
&mock_server.uri(),
Url::parse(&mock_server.uri()).unwrap(),
Refresh {
strategy: RefreshStrategy::Interval,
refresh_interval: Duration::from_secs(3000),
@ -359,7 +361,7 @@ mod tests {
.await;
let mut ksm = KeyStoreManager::new(
&mock_server.uri(),
Url::parse(&mock_server.uri()).unwrap(),
Refresh {
strategy: RefreshStrategy::KeyNotFound,
..Default::default()
@ -435,7 +437,7 @@ mod tests {
.await;
let ksm = KeyStoreManager::new(
&mock_server.uri(),
Url::parse(&mock_server.uri()).unwrap(),
Refresh {
strategy: RefreshStrategy::NoRefresh,
..Default::default()

View file

@ -9,7 +9,10 @@ pub struct OidcDiscovery {
}
pub async fn discover_jwks(issuer: &str) -> Result<String, InitError> {
let discovery_url = format!("{issuer}/.well-known/openid-configuration");
let discovery_url = reqwest::Url::parse(issuer)
.map_err(|e| InitError::DiscoveryError(e.to_string()))?
.join("/.well-known/openid-configuration")
.map_err(|e| InitError::DiscoveryError(e.to_string()))?;
reqwest::Client::new()
.get(discovery_url)
.send()