Skip to content

Commit d9b0b79

Browse files
loispostulactron
authored andcommitted
feat: switch to a metadata_source enum
1 parent 7ceb665 commit d9b0b79

File tree

2 files changed

+105
-162
lines changed

2 files changed

+105
-162
lines changed

src/agent/client/openid.rs

Lines changed: 77 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
InnerConfig, LogoutOptions, OAuth2Error,
44
client::{Client, LoginContext, expires},
55
},
6-
config::openid,
6+
config::openid::{self, MetadataSource, MetadataUrls},
77
context::{Authentication, OAuth2Context},
88
};
99
use async_trait::async_trait;
@@ -36,7 +36,7 @@ const DEFAULT_POST_LOGOUT_DIRECT_NAME: &str = "post_logout_redirect_uri";
3636
/// An OpenID Connect based client implementation
3737
#[derive(Clone, Debug)]
3838
pub struct OpenIdClient {
39-
/// The http clietn
39+
/// The http client
4040
http_client: openidconnect::reqwest::Client,
4141
/// The client
4242
client: ExtendedClient,
@@ -96,21 +96,47 @@ impl Client for OpenIdClient {
9696
);
9797

9898
async fn from_config(config: Self::Configuration) -> Result<Self, OAuth2Error> {
99-
match (
100-
&config.jwks_url,
101-
&config.auth_url,
102-
&config.token_url,
103-
&config.user_info_url,
104-
) {
105-
(Some(_), Some(_), Some(_), Some(_)) => {
106-
log::debug!("Constructing client from feeded urls");
107-
Self::from_urls(config).await
99+
let openid::Config {
100+
client_id,
101+
issuer_url,
102+
metadata_source,
103+
end_session_url,
104+
after_logout_url,
105+
post_logout_redirect_name,
106+
additional_trusted_audiences,
107+
require_issuer_match,
108+
} = config;
109+
110+
let http_client = openidconnect::reqwest::ClientBuilder::new()
111+
// Following redirects opens the client up to SSRF vulnerabilities.
112+
// .redirect(openidconnect::reqwest::redirect::Policy::none())
113+
.build()
114+
.map_err(|err| {
115+
OAuth2Error::Configuration(format!("Failed to build HTTP client: {err}"))
116+
})?;
117+
118+
let issuer = IssuerUrl::new(issuer_url)
119+
.map_err(|err| OAuth2Error::Configuration(format!("invalid issuer URL: {err}")))?;
120+
121+
let (client, end_session_url) = match metadata_source {
122+
MetadataSource::Discovery => {
123+
Self::build_client_from_discovery(&http_client, issuer, client_id, end_session_url)
124+
.await?
108125
}
109-
_ => {
110-
log::debug!("Constructing client from provider metadata");
111-
Self::from_metadata(config).await
126+
MetadataSource::Manual(urls) => {
127+
Self::build_client_from_urls(&http_client, issuer, client_id, end_session_url, urls)
128+
.await?
112129
}
113-
}
130+
};
131+
Ok(Self {
132+
http_client,
133+
client,
134+
end_session_url,
135+
after_logout_url,
136+
post_logout_redirect_name,
137+
additional_trusted_audiences,
138+
require_issuer_match,
139+
})
114140
}
115141

116142
fn set_redirect_uri(mut self, url: Url) -> Self {
@@ -284,29 +310,13 @@ impl OpenIdClient {
284310
window().location().href().ok()
285311
}
286312
}
287-
async fn from_metadata(config: openid::Config) -> Result<Self, OAuth2Error> {
288-
let openid::Config {
289-
client_id,
290-
issuer_url,
291-
end_session_url,
292-
after_logout_url,
293-
post_logout_redirect_name,
294-
additional_trusted_audiences,
295-
require_issuer_match,
296-
..
297-
} = config;
298-
299-
let http_client = openidconnect::reqwest::ClientBuilder::new()
300-
// we can't control redirect here, as we're using WASM request, which basically is the browser
301-
.build()
302-
.map_err(|err| {
303-
OAuth2Error::Configuration(format!("Failed to build HTTP client: {err}"))
304-
})?;
305-
306-
let issuer = IssuerUrl::new(issuer_url)
307-
.map_err(|err| OAuth2Error::Configuration(format!("invalid issuer URL: {err}")))?;
308-
309-
let metadata = ExtendedProviderMetadata::discover_async(issuer, &http_client)
313+
async fn build_client_from_discovery(
314+
http_client: &openidconnect::reqwest::Client,
315+
issuer: IssuerUrl,
316+
client_id: String,
317+
end_session_url: Option<String>,
318+
) -> Result<(ExtendedClient, Option<Url>), OAuth2Error> {
319+
let metadata = ExtendedProviderMetadata::discover_async(issuer, http_client)
310320
.await
311321
.map_err(|err| {
312322
OAuth2Error::Configuration(format!("Failed to discover client: {err}"))
@@ -328,7 +338,6 @@ impl OpenIdClient {
328338
OAuth2Error::Configuration("Provider missing required auth info endpoint".into())
329339
})?
330340
.clone();
331-
332341
let end_session_url = end_session_url
333342
.map(|url| Url::parse(&url))
334343
.transpose()
@@ -337,92 +346,38 @@ impl OpenIdClient {
337346
})?
338347
.or_else(|| metadata.additional_metadata().end_session_endpoint.clone());
339348

340-
let client = CoreClient::from_provider_metadata(metadata, ClientId::new(client_id), None)
341-
.set_auth_uri(auth_uri)
342-
.set_token_uri(token_uri)
343-
.set_user_info_url(user_info_uri);
344-
Ok(Self {
345-
http_client,
346-
client,
349+
Ok((
350+
CoreClient::from_provider_metadata(metadata, ClientId::new(client_id), None)
351+
.set_auth_uri(auth_uri)
352+
.set_token_uri(token_uri)
353+
.set_user_info_url(user_info_uri),
347354
end_session_url,
348-
after_logout_url,
349-
post_logout_redirect_name,
350-
additional_trusted_audiences,
351-
require_issuer_match,
352-
})
355+
))
353356
}
354357

355-
async fn from_urls(config: openid::Config) -> Result<Self, OAuth2Error> {
356-
let openid::Config {
357-
client_id,
358-
issuer_url,
359-
end_session_url,
360-
after_logout_url,
361-
post_logout_redirect_name,
362-
additional_trusted_audiences,
363-
require_issuer_match,
364-
jwks_url,
365-
auth_url,
366-
token_url,
367-
user_info_url,
368-
} = config;
369-
370-
let http_client = openidconnect::reqwest::ClientBuilder::new()
371-
// Following redirects opens the client up to SSRF vulnerabilities.
372-
// .redirect(openidconnect::reqwest::redirect::Policy::none())
373-
.build()
374-
.map_err(|err| {
375-
OAuth2Error::Configuration(format!("Failed to build HTTP client: {err}"))
376-
})?;
377-
378-
let issuer = IssuerUrl::new(issuer_url)
379-
.map_err(|err| OAuth2Error::Configuration(format!("invalid issuer URL: {err}")))?;
380-
381-
let auth_uri = auth_url
382-
.map(AuthUrl::new)
383-
.transpose()
384-
.map_err(|err| OAuth2Error::Configuration(format!("invalid auth URL: {err}")))?
385-
.ok_or_else(|| {
386-
OAuth2Error::Configuration(
387-
"Cannot build Auth2 client without metadata: missing auth url".to_string(),
388-
)
389-
})?;
358+
async fn build_client_from_urls(
359+
http_client: &openidconnect::reqwest::Client,
360+
issuer: IssuerUrl,
361+
client_id: String,
362+
end_session_url: Option<String>,
363+
urls: MetadataUrls,
364+
) -> Result<(ExtendedClient, Option<Url>), OAuth2Error> {
365+
let auth_uri = AuthUrl::new(urls.auth)
366+
.map_err(|err| OAuth2Error::Configuration(format!("invalid auth URL: {err}")))?;
390367

391-
let token_uri = token_url
392-
.map(TokenUrl::new)
393-
.transpose()
394-
.map_err(|err| OAuth2Error::Configuration(format!("invalid token URL: {err}")))?
395-
.ok_or_else(|| {
396-
OAuth2Error::Configuration(
397-
"Cannot build Auth2 client without metadata: missing token url".to_string(),
398-
)
399-
})?;
368+
let token_uri = TokenUrl::new(urls.token)
369+
.map_err(|err| OAuth2Error::Configuration(format!("invalid token URL: {err}")))?;
400370

401-
let jwks_uri = jwks_url
402-
.map(JsonWebKeySetUrl::new)
403-
.transpose()
404-
.map_err(|err| OAuth2Error::Configuration(format!("invalid jwks URL: {err}")))?
405-
.ok_or_else(|| {
406-
OAuth2Error::Configuration(
407-
"Cannot build Auth2 client without metadata: missing jwks url".to_string(),
408-
)
409-
})?;
371+
let jwks_uri = JsonWebKeySetUrl::new(urls.jwks)
372+
.map_err(|err| OAuth2Error::Configuration(format!("invalid jwks URL: {err}")))?;
410373

411-
let jwks = JsonWebKeySet::fetch_async(&jwks_uri, &http_client)
374+
let jwks = JsonWebKeySet::fetch_async(&jwks_uri, http_client)
412375
.await
413376
.map_err(|err| OAuth2Error::Configuration(format!("Could not fetch jwks: {err}")))?;
414377

415-
let user_info_url = user_info_url
416-
.map(UserInfoUrl::new)
417-
.transpose()
418-
.map_err(|err| {
419-
OAuth2Error::Configuration(format!("Unable to parse user_info_url: {err}"))
420-
})?
421-
.ok_or_else(|| {
422-
OAuth2Error::Configuration(
423-
"Cannot build Auth2 client without metadata: missing user_info url".to_string(),
424-
)
425-
})?;
378+
let user_info_uri = UserInfoUrl::new(urls.user_info).map_err(|err| {
379+
OAuth2Error::Configuration(format!("Unable to parse user_info_url: {err}"))
380+
})?;
426381

427382
let end_session_url = end_session_url
428383
.map(|url| Url::parse(&url))
@@ -431,19 +386,12 @@ impl OpenIdClient {
431386
OAuth2Error::Configuration(format!("Unable to parse end_session_url: {err}"))
432387
})?;
433388

434-
let client = CoreClient::new(ClientId::new(client_id), issuer, jwks)
435-
.set_auth_uri(auth_uri)
436-
.set_token_uri(token_uri)
437-
.set_user_info_url(user_info_url);
438-
439-
Ok(Self {
440-
http_client,
441-
client,
389+
Ok((
390+
CoreClient::new(ClientId::new(client_id), issuer, jwks)
391+
.set_auth_uri(auth_uri)
392+
.set_token_uri(token_uri)
393+
.set_user_info_url(user_info_uri),
442394
end_session_url,
443-
after_logout_url,
444-
post_logout_redirect_name,
445-
additional_trusted_audiences,
446-
require_issuer_match,
447-
})
395+
))
448396
}
449397
}

src/config.rs

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ use serde::{Deserialize, Serialize};
66
pub mod openid {
77
use super::*;
88

9+
/// OpenID Metadata Urls
10+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
11+
pub struct MetadataUrls {
12+
/// The OpenID connect auth URL.
13+
pub auth: String,
14+
/// The OpenID token auth URL.
15+
pub token: String,
16+
/// The OpenID user info auth URL.
17+
pub user_info: String,
18+
/// The OpenID jwks url.
19+
pub jwks: String,
20+
}
21+
/// OpenID Metadata Source
22+
/// Defines how to fetch the openid metadata (auth_url, token_url, user_info_url, jwks_url)
23+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
24+
pub enum MetadataSource {
25+
#[default]
26+
/// Using the discovery information living at `issuer_url/.well_known/openid-configuration` https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
27+
Discovery,
28+
/// Manual configuration
29+
Manual(MetadataUrls),
30+
}
931
/// OpenID Connect client configuration
1032
///
1133
/// ## Non-exhaustive
@@ -19,21 +41,13 @@ pub mod openid {
1941
pub client_id: String,
2042
/// The OpenID connect issuer URL.
2143
pub issuer_url: String,
22-
/// If the following urls are set, the client will be built from them
23-
/// The OpenID connect auth URL.
24-
pub auth_url: Option<String>,
25-
/// The OpenID token auth URL.
26-
pub token_url: Option<String>,
27-
/// The OpenID user info auth URL.
28-
pub user_info_url: Option<String>,
29-
/// The OpenID jwks url.
30-
pub jwks_url: Option<String>,
44+
/// How to fetch the metadata
45+
pub metadata_source: MetadataSource,
3146
/// An override for the end session URL.
3247
pub end_session_url: Option<String>,
3348
/// The URL to navigate to after the logout has been completed.
3449
pub after_logout_url: Option<String>,
3550
/// The name of the query parameter for the post logout redirect.
36-
///
3751
/// The defaults to `post_logout_redirect_uri` for OpenID RP initiated logout.
3852
/// However, e.g. older Keycloak instances, require this to be `redirect_uri`.
3953
pub post_logout_redirect_name: Option<String>,
@@ -52,10 +66,7 @@ pub mod openid {
5266
client_id: client_id.into(),
5367
issuer_url: issuer_url.into(),
5468

55-
auth_url: None,
56-
token_url: None,
57-
user_info_url: None,
58-
jwks_url: None,
69+
metadata_source: MetadataSource::Discovery,
5970

6071
end_session_url: None,
6172
after_logout_url: None,
@@ -124,25 +135,9 @@ pub mod openid {
124135
self
125136
}
126137

127-
/// Set the provider auth url, skip metadata
128-
pub fn with_auth_url(mut self, auth_url: impl Into<String>) -> Self {
129-
self.auth_url = Some(auth_url.into());
130-
self
131-
}
132-
133-
/// Set the provider token url, skip metadata
134-
pub fn with_token_url(mut self, token_url: impl Into<String>) -> Self {
135-
self.token_url = Some(token_url.into());
136-
self
137-
}
138-
/// Set the provider user_info url, skip metadata
139-
pub fn with_user_info_url(mut self, user_info_url: impl Into<String>) -> Self {
140-
self.user_info_url = Some(user_info_url.into());
141-
self
142-
}
143-
/// Set the provider jwks url, skip metadata
144-
pub fn with_jwks_url(mut self, jwks_url: impl Into<String>) -> Self {
145-
self.jwks_url = Some(jwks_url.into());
138+
/// Set the metadata source
139+
pub fn with_metadata_source(mut self, metadata_source: MetadataSource) -> Self {
140+
self.metadata_source = metadata_source;
146141
self
147142
}
148143
}

0 commit comments

Comments
 (0)