Skip to content

Commit 3c155a6

Browse files
committed
fix: assemble oidc discovery url correctly
Also removes some of the default provider endpoints, since it doesn't make sense to have them for unknown brands.
1 parent 5110b9e commit 3c155a6

File tree

1 file changed

+86
-74
lines changed

1 file changed

+86
-74
lines changed

src/service/oauth/providers.rs

Lines changed: 86 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -152,64 +152,46 @@ async fn configure(&self, mut provider: Provider) -> Result<Provider> {
152152
.and_then(|response| check_issuer(response, &provider))?;
153153

154154
if provider.authorization_url.is_none() {
155-
response
156-
.get("authorization_endpoint")
157-
.and_then(JsonValue::as_str)
158-
.map(Url::parse)
159-
.transpose()?
160-
.or_else(|| make_url(&provider, "/authorize").ok())
161-
.map(|url| provider.authorization_url.replace(url));
155+
provider.authorization_url = match provider.brand.as_str() {
156+
| "github" => "https://api.github.com/authorize".try_into().ok(),
157+
| _ =>
158+
Some(assert_and_parse_url(provider.id(), &response, "authorization_endpoint")?),
159+
};
162160
}
163161

164162
if provider.revocation_url.is_none() {
165-
response
166-
.get("revocation_endpoint")
167-
.and_then(JsonValue::as_str)
168-
.map(Url::parse)
169-
.transpose()?
170-
.or_else(|| make_url(&provider, "/revocation").ok())
171-
.map(|url| provider.revocation_url.replace(url));
163+
provider.revocation_url = match provider.brand.as_str() {
164+
| "github" => "https://api.github.com/revocation"
165+
.try_into()
166+
.ok(),
167+
| _ => Some(assert_and_parse_url(provider.id(), &response, "revocation_endpoint")?),
168+
};
172169
}
173170

174171
if provider.introspection_url.is_none() {
175-
response
176-
.get("introspection_endpoint")
177-
.and_then(JsonValue::as_str)
178-
.map(Url::parse)
179-
.transpose()?
180-
.or_else(|| make_url(&provider, "/introspection").ok())
181-
.map(|url| provider.introspection_url.replace(url));
172+
provider.introspection_url = match provider.brand.as_str() {
173+
| "github" => "https://api.github.com/introspection"
174+
.try_into()
175+
.ok(),
176+
| _ =>
177+
Some(assert_and_parse_url(provider.id(), &response, "introspection_endpoint")?),
178+
};
182179
}
183180

184181
if provider.userinfo_url.is_none() {
185-
response
186-
.get("userinfo_endpoint")
187-
.and_then(JsonValue::as_str)
188-
.map(Url::parse)
189-
.transpose()?
190-
.or_else(|| match provider.brand.as_str() {
191-
| "github" => "https://api.github.com/user".try_into().ok(),
192-
| _ => make_url(&provider, "/userinfo").ok(),
193-
})
194-
.map(|url| provider.userinfo_url.replace(url));
182+
provider.userinfo_url = match provider.brand.as_str() {
183+
| "github" => "https://api.github.com/user".try_into().ok(),
184+
| _ => Some(assert_and_parse_url(provider.id(), &response, "userinfo_endpoint")?),
185+
};
195186
}
196187

197188
if provider.token_url.is_none() {
198-
response
199-
.get("token_endpoint")
200-
.and_then(JsonValue::as_str)
201-
.map(Url::parse)
202-
.transpose()?
203-
.or_else(|| {
204-
let path = if provider.brand == "github" {
205-
"/access_token"
206-
} else {
207-
"/token"
208-
};
209-
210-
make_url(&provider, path).ok()
211-
})
212-
.map(|url| provider.token_url.replace(url));
189+
provider.token_url = match provider.brand.as_str() {
190+
| "github" => "https://api.github.com/access_token"
191+
.try_into()
192+
.ok(),
193+
| _ => Some(assert_and_parse_url(provider.id(), &response, "token_endpoint")?),
194+
};
213195
}
214196

215197
Ok(provider)
@@ -235,25 +217,54 @@ pub async fn discover(&self, provider: &Provider) -> Result<JsonValue> {
235217
/// Compute the location of the `/.well-known/openid-configuration` based on the
236218
/// local provider config.
237219
fn discovery_url(provider: &Provider) -> Result<Url> {
238-
let default_url = provider
239-
.discovery
240-
.then(|| make_url(provider, "/.well-known/openid-configuration"))
241-
.transpose()?;
242-
243-
let Some(url) = provider
244-
.discovery_url
245-
.clone()
246-
.filter(|_| provider.discovery)
247-
.or(default_url)
248-
else {
220+
if !provider.discovery {
249221
return Err!(Config(
250222
"discovery_url",
251-
"Failed to determine URL for discovery of provider {}",
223+
"Provider has discovery disabled {}",
224+
provider.id()
225+
));
226+
}
227+
228+
if let Some(url) = &provider.discovery_url {
229+
return Ok(url.to_owned());
230+
}
231+
232+
let issuer = provider
233+
.issuer_url
234+
.as_ref()
235+
.expect("Can we assert this exists at this point?");
236+
237+
let issuer_path = issuer.path();
238+
239+
let ressource_path = ".well-known/openid-configuration";
240+
241+
let base_url = if issuer_path.ends_with('/') {
242+
issuer.to_owned()
243+
} else {
244+
let mut url = issuer.to_owned();
245+
url.set_path((issuer_path.to_owned() + "/").as_str());
246+
url
247+
};
248+
249+
let Some(base_path) = provider.base_path.as_ref() else {
250+
return Ok(base_url.join(ressource_path)?);
251+
};
252+
253+
if base_path.is_empty() {
254+
return Err!(Config(
255+
"base_path",
256+
"Provider '{}' has an empty base_path. Remove the key or add a value",
252257
provider.id()
253258
));
259+
}
260+
261+
let base_path = if base_path.ends_with("/") {
262+
base_path.to_owned()
263+
} else {
264+
base_path.to_owned() + "/"
254265
};
255266

256-
Ok(url)
267+
Ok(base_url.join((base_path + ressource_path).as_ref())?)
257268
}
258269

259270
/// Validate that the locally configured `issuer_url` matches the issuer claimed
@@ -282,20 +293,21 @@ fn check_issuer(
282293
Ok(response)
283294
}
284295

285-
/// Generate a full URL for a request to the idp based on the idp's derived
286-
/// configuration.
287-
fn make_url(provider: &Provider, path: &str) -> Result<Url> {
288-
let mut suffix = provider.base_path.clone().unwrap_or_default();
289-
290-
suffix.push_str(path);
291-
let url = provider
292-
.issuer_url
293-
.as_ref()
294-
.ok_or_else(|| {
295-
let id = &provider.client_id;
296-
err!(Config("issuer_url", "Provider {id:?} required field"))
297-
})?
298-
.join(&suffix)?;
296+
/// Assert that a url exists in the response and parse it
297+
fn assert_and_parse_url(
298+
provider_id: &str,
299+
response_map: &serde_json::Map<String, serde_json::Value>,
300+
url_name: &str,
301+
) -> Result<Url> {
302+
let Some(url_value) = response_map.get(url_name) else {
303+
return Err(tuwunel_core::Error::Err(
304+
format!(
305+
"Error building oidc provider '{}': {} is missing from openid discovery response",
306+
provider_id, url_name,
307+
)
308+
.into(),
309+
));
310+
};
299311

300-
Ok(url)
312+
Ok(url_value.as_str().unwrap_or_default().parse()?)
301313
}

0 commit comments

Comments
 (0)