diff --git a/Cargo.lock b/Cargo.lock index cf476867..2fca63ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1950,8 +1950,10 @@ dependencies = [ "futures-util", "headers", "http", + "http-body", "mas-config", "mas-data-model", + "mas-http", "mas-iana", "mas-jose", "mas-storage", @@ -1965,6 +1967,7 @@ dependencies = [ "sqlx", "thiserror", "tokio", + "tower", "tracing", "url", ] diff --git a/crates/axum-utils/Cargo.toml b/crates/axum-utils/Cargo.toml index b5074844..16c49979 100644 --- a/crates/axum-utils/Cargo.toml +++ b/crates/axum-utils/Cargo.toml @@ -15,6 +15,7 @@ data-encoding = "2.3.2" futures-util = "0.3.21" headers = "0.3.7" http = "0.2.6" +http-body = "0.4.4" mime = "0.3.16" rand = "0.8.5" serde = "1.0.136" @@ -24,6 +25,7 @@ serde_json = "1.0.79" sqlx = "0.5.13" thiserror = "1.0.30" tokio = "1.17.0" +tower = { version = "0.4.12", features = ["util"] } tracing = "0.1.34" url = "2.2.2" @@ -34,5 +36,4 @@ mas-storage = { path = "../storage" } mas-data-model = { path = "../data-model" } mas-jose = { path = "../jose" } mas-iana = { path = "../iana" } - -[features] +mas-http = { path = "../http" } diff --git a/crates/axum-utils/src/client_authorization.rs b/crates/axum-utils/src/client_authorization.rs index 7ec400d3..2f5cb23c 100644 --- a/crates/axum-utils/src/client_authorization.rs +++ b/crates/axum-utils/src/client_authorization.rs @@ -22,15 +22,17 @@ use axum::{ Form, FromRequest, RequestParts, TypedHeader, }, response::IntoResponse, + BoxError, }; use headers::{authorization::Basic, Authorization}; use http::StatusCode; use mas_config::Encrypter; use mas_data_model::{Client, JwksOrJwksUri, StorageBackend}; +use mas_http::HttpServiceExt; use mas_iana::oauth::OAuthClientAuthenticationMethod; use mas_jose::{ - DecodedJsonWebToken, DynamicJwksStore, Either, JsonWebTokenParts, JwtHeader, SharedSecret, - StaticJwksStore, + DecodedJsonWebToken, DynamicJwksStore, Either, JsonWebKeySet, JsonWebTokenParts, JwtHeader, + SharedSecret, StaticJwksStore, VerifyingKeystore, }; use mas_storage::{ oauth2::client::{lookup_client_by_client_id, ClientFetchError}, @@ -40,6 +42,7 @@ use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; use sqlx::PgExecutor; use thiserror::Error; +use tower::ServiceExt; static JWT_BEARER_CLIENT_ASSERTION: &str = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; @@ -169,10 +172,36 @@ impl Credentials { } fn jwks_key_store(jwks: &JwksOrJwksUri) -> Either { - match jwks { - JwksOrJwksUri::Jwks(key_set) => Either::Left(StaticJwksStore::new(key_set.clone())), - JwksOrJwksUri::JwksUri(_uri) => todo!(), + // Assert that the output is both a VerifyingKeystore and Send + fn assert(t: T) -> T { + t } + + let inner = match jwks { + JwksOrJwksUri::Jwks(jwks) => Either::Left(StaticJwksStore::new(jwks.clone())), + JwksOrJwksUri::JwksUri(uri) => { + let uri = uri.clone(); + + // TODO: get the client from somewhere else? + let exporter = mas_http::client("fetch-jwks") + .json::() + .map_request(move |_: ()| { + http::Request::builder() + .method("GET") + // TODO: change the Uri type in config to avoid reparsing here + .uri(uri.to_string()) + .body(http_body::Empty::new()) + .unwrap() + }) + .map_response(http::Response::into_body) + .map_err(BoxError::from) + .boxed_clone(); + + Either::Right(DynamicJwksStore::new(exporter)) + } + }; + + assert(inner) } #[derive(Debug, Error)]