1use core::fmt::Debug;
2use std::collections::HashMap;
3
4use crate::client::Client;
5use crate::helpers::{convert_json_to, now, validate_url, webfinger_normalize};
6use crate::http::request_async;
7use crate::jwks::Jwks;
8use crate::types::http_client::HttpMethod;
9use crate::types::{
10 ClientMetadata, ClientOptions, Fapi, HttpRequest, HttpResponse, IssuerMetadata, MtlsEndpoints,
11 OidcClientError, OidcHttpClient, OidcReturnType, WebFingerResponse,
12};
13
14use serde_json::Value;
15use url::Url;
16
17use super::keystore::KeyStore;
18
19#[derive(Debug)]
21pub struct Issuer {
22 pub(crate) issuer: String,
23 pub(crate) authorization_endpoint: Option<String>,
24 pub(crate) device_authorization_endpoint: Option<String>,
25 pub(crate) token_endpoint: Option<String>,
26 pub(crate) jwks_uri: Option<String>,
27 pub(crate) userinfo_endpoint: Option<String>,
28 pub(crate) revocation_endpoint: Option<String>,
29 pub(crate) claims_parameter_supported: Option<bool>,
30 pub(crate) grant_types_supported: Option<Vec<String>>,
31 pub(crate) request_parameter_supported: Option<bool>,
32 pub(crate) request_uri_parameter_supported: Option<bool>,
33 pub(crate) require_request_uri_registration: Option<bool>,
34 pub(crate) response_modes_supported: Option<Vec<String>>,
35 pub(crate) claim_types_supported: Vec<String>,
36 pub(crate) token_endpoint_auth_methods_supported: Option<Vec<String>>,
37 pub(crate) token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
38 pub(crate) introspection_endpoint_auth_methods_supported: Option<Vec<String>>,
39 pub(crate) introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
40 pub(crate) revocation_endpoint_auth_methods_supported: Option<Vec<String>>,
41 pub(crate) revocation_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
42 pub(crate) end_session_endpoint: Option<String>,
43 pub(crate) other_fields: HashMap<String, Value>,
44 pub(crate) keystore: Option<KeyStore>,
45 pub(crate) mtls_endpoint_aliases: Option<MtlsEndpoints>,
46 pub(crate) introspection_endpoint: Option<String>,
47 pub(crate) registration_endpoint: Option<String>,
48 pub(crate) authorization_response_iss_parameter_supported: Option<bool>,
49 pub(crate) dpop_signing_alg_values_supported: Option<Vec<String>>,
50 pub(crate) pushed_authorization_request_endpoint: Option<String>,
51 pub(crate) require_pushed_authorization_requests: bool,
52 pub(crate) backchannel_token_delivery_modes_supported: Option<Vec<String>>,
53 pub(crate) backchannel_authentication_endpoint: Option<String>,
54 pub(crate) backchannel_authentication_request_signing_alg_values_supported: Option<Vec<String>>,
55 pub(crate) backchannel_user_code_parameter_supported: bool,
56 pub(crate) now: fn() -> u64,
57}
58
59impl Default for Issuer {
60 fn default() -> Self {
61 Self {
62 claims_parameter_supported: Some(false),
63 grant_types_supported: Some(vec![
64 String::from("authorization_code"),
65 String::from("implicit"),
66 ]),
67 request_parameter_supported: Some(false),
68 request_uri_parameter_supported: Some(true),
69 require_request_uri_registration: Some(false),
70 response_modes_supported: Some(vec![String::from("query"), String::from("fragment")]),
71 claim_types_supported: vec![String::from("normal")],
72 token_endpoint_auth_methods_supported: Some(vec!["client_secret_basic".to_string()]),
73 introspection_endpoint_auth_methods_supported: None,
74 issuer: "".to_string(),
75 authorization_endpoint: None,
76 token_endpoint: None,
77 jwks_uri: None,
78 userinfo_endpoint: None,
79 revocation_endpoint: None,
80 revocation_endpoint_auth_methods_supported: None,
81 token_endpoint_auth_signing_alg_values_supported: None,
82 introspection_endpoint_auth_signing_alg_values_supported: None,
83 revocation_endpoint_auth_signing_alg_values_supported: None,
84 end_session_endpoint: None,
85 other_fields: Default::default(),
86 keystore: None,
87 mtls_endpoint_aliases: None,
88 introspection_endpoint: None,
89 authorization_response_iss_parameter_supported: None,
90 registration_endpoint: None,
91 dpop_signing_alg_values_supported: None,
92 pushed_authorization_request_endpoint: None,
93 require_pushed_authorization_requests: false,
94 device_authorization_endpoint: None,
95 backchannel_token_delivery_modes_supported: None,
96 backchannel_authentication_endpoint: None,
97 backchannel_authentication_request_signing_alg_values_supported: None,
98 backchannel_user_code_parameter_supported: false,
99 now,
100 }
101 }
102}
103
104impl Issuer {
106 fn from(metadata: IssuerMetadata) -> Self {
107 let token_endpoint_auth_methods_supported =
108 match metadata.token_endpoint_auth_methods_supported {
109 None => Some(vec!["client_secret_basic".to_string()]),
110 Some(v) => Some(v),
111 };
112
113 let introspection_endpoint_auth_methods_supported =
114 match metadata.introspection_endpoint_auth_methods_supported {
115 None => token_endpoint_auth_methods_supported.clone(),
116 Some(v) => Some(v),
117 };
118
119 let introspection_endpoint_auth_signing_alg_values_supported =
120 match metadata.introspection_endpoint_auth_signing_alg_values_supported {
121 None => metadata
122 .token_endpoint_auth_signing_alg_values_supported
123 .clone(),
124 Some(v) => Some(v),
125 };
126
127 let revocation_endpoint_auth_methods_supported =
128 match metadata.revocation_endpoint_auth_methods_supported {
129 None => token_endpoint_auth_methods_supported.clone(),
130 Some(v) => Some(v),
131 };
132
133 let revocation_endpoint_auth_signing_alg_values_supported =
134 match metadata.revocation_endpoint_auth_signing_alg_values_supported {
135 None => metadata
136 .token_endpoint_auth_signing_alg_values_supported
137 .clone(),
138 Some(v) => Some(v),
139 };
140
141 let jwks_uri = metadata.jwks_uri.clone();
142
143 Self {
144 issuer: metadata.issuer,
145 authorization_endpoint: metadata.authorization_endpoint,
146 device_authorization_endpoint: metadata.device_authorization_endpoint,
147 token_endpoint: metadata.token_endpoint,
148 jwks_uri: metadata.jwks_uri,
149 userinfo_endpoint: metadata.userinfo_endpoint,
150 revocation_endpoint: metadata.revocation_endpoint,
151 token_endpoint_auth_methods_supported,
152 introspection_endpoint_auth_methods_supported,
153 introspection_endpoint_auth_signing_alg_values_supported,
154 revocation_endpoint_auth_methods_supported,
155 revocation_endpoint_auth_signing_alg_values_supported,
156 end_session_endpoint: metadata.end_session_endpoint,
157 registration_endpoint: metadata.registration_endpoint,
158 introspection_endpoint: metadata.introspection_endpoint,
159 token_endpoint_auth_signing_alg_values_supported: metadata
160 .token_endpoint_auth_signing_alg_values_supported,
161 mtls_endpoint_aliases: metadata.mtls_endpoint_aliases,
162 authorization_response_iss_parameter_supported: metadata
163 .authorization_response_iss_parameter_supported,
164 dpop_signing_alg_values_supported: metadata.dpop_signing_alg_values_supported,
165 pushed_authorization_request_endpoint: metadata.pushed_authorization_request_endpoint,
166 require_pushed_authorization_requests: metadata.require_pushed_authorization_requests,
167 other_fields: metadata.other_fields,
168 keystore: Some(KeyStore::new(jwks_uri)),
169 backchannel_token_delivery_modes_supported: metadata
170 .backchannel_token_delivery_modes_supported,
171 backchannel_authentication_endpoint: metadata.backchannel_authentication_endpoint,
172 backchannel_authentication_request_signing_alg_values_supported: metadata
173 .backchannel_authentication_request_signing_alg_values_supported,
174 backchannel_user_code_parameter_supported: metadata
175 .backchannel_user_code_parameter_supported
176 .unwrap_or(false),
177 ..Issuer::default()
178 }
179 }
180
181 pub fn new(metadata: IssuerMetadata) -> Self {
193 let introspection_endpoint_auth_methods_supported =
194 match metadata.introspection_endpoint_auth_methods_supported {
195 None => metadata.token_endpoint_auth_methods_supported.clone(),
196 Some(v) => Some(v),
197 };
198
199 let introspection_endpoint_auth_signing_alg_values_supported =
200 match metadata.introspection_endpoint_auth_signing_alg_values_supported {
201 None => metadata
202 .token_endpoint_auth_signing_alg_values_supported
203 .clone(),
204 Some(v) => Some(v),
205 };
206
207 let revocation_endpoint_auth_methods_supported =
208 match metadata.revocation_endpoint_auth_methods_supported {
209 None => metadata.token_endpoint_auth_methods_supported.clone(),
210 Some(v) => Some(v),
211 };
212
213 let revocation_endpoint_auth_signing_alg_values_supported =
214 match metadata.revocation_endpoint_auth_signing_alg_values_supported {
215 None => metadata
216 .token_endpoint_auth_signing_alg_values_supported
217 .clone(),
218 Some(v) => Some(v),
219 };
220
221 let jwks_uri = metadata.jwks_uri.clone();
222
223 Self {
224 issuer: metadata.issuer,
225 authorization_endpoint: metadata.authorization_endpoint,
226 token_endpoint: metadata.token_endpoint,
227 jwks_uri: metadata.jwks_uri,
228 userinfo_endpoint: metadata.userinfo_endpoint,
229 revocation_endpoint: metadata.revocation_endpoint,
230 claims_parameter_supported: None,
231 grant_types_supported: None,
232 request_parameter_supported: None,
233 request_uri_parameter_supported: None,
234 require_request_uri_registration: None,
235 response_modes_supported: None,
236 claim_types_supported: vec![],
237 token_endpoint_auth_methods_supported: metadata.token_endpoint_auth_methods_supported,
238 introspection_endpoint_auth_methods_supported,
239 token_endpoint_auth_signing_alg_values_supported: metadata
240 .token_endpoint_auth_signing_alg_values_supported,
241 introspection_endpoint_auth_signing_alg_values_supported,
242 revocation_endpoint_auth_methods_supported,
243 revocation_endpoint_auth_signing_alg_values_supported,
244 other_fields: metadata.other_fields,
245 keystore: Some(KeyStore::new(jwks_uri)),
246 mtls_endpoint_aliases: metadata.mtls_endpoint_aliases,
247 introspection_endpoint: metadata.introspection_endpoint,
248 registration_endpoint: metadata.registration_endpoint,
249 end_session_endpoint: metadata.end_session_endpoint,
250 authorization_response_iss_parameter_supported: metadata
251 .authorization_response_iss_parameter_supported,
252 dpop_signing_alg_values_supported: metadata.dpop_signing_alg_values_supported,
253 pushed_authorization_request_endpoint: metadata.pushed_authorization_request_endpoint,
254 require_pushed_authorization_requests: metadata.require_pushed_authorization_requests,
255 device_authorization_endpoint: metadata.device_authorization_endpoint,
256 backchannel_token_delivery_modes_supported: metadata
257 .backchannel_token_delivery_modes_supported,
258 backchannel_authentication_endpoint: metadata.backchannel_authentication_endpoint,
259 backchannel_authentication_request_signing_alg_values_supported: metadata
260 .backchannel_authentication_request_signing_alg_values_supported,
261 backchannel_user_code_parameter_supported: metadata
262 .backchannel_user_code_parameter_supported
263 .unwrap_or(false),
264 now,
265 }
266 }
267}
268
269impl Issuer {
271 pub async fn discover_async<T>(http_client: &T, issuer: &str) -> OidcReturnType<Issuer>
280 where
281 T: OidcHttpClient,
282 {
283 let mut url = match validate_url(issuer) {
284 Ok(parsed) => parsed,
285 Err(err) => return Err(err),
286 };
287
288 let mut path: String = url.path().to_string();
289 if path.ends_with('/') {
290 path.pop();
291 }
292
293 if path.ends_with(".well-known") {
294 path.push_str("/openid-configuration");
295 } else if !path.contains(".well-known") {
296 path.push_str("/.well-known/openid-configuration");
297 }
298
299 url.set_path(&path);
300
301 let mut headers = HashMap::new();
302 headers.insert("accept".to_string(), vec!["application/json".to_string()]);
303
304 let req = HttpRequest::new().url(url).headers(headers);
305
306 let res = request_async(req, http_client).await?;
307
308 let issuer_metadata = match convert_json_to::<IssuerMetadata>(res.body.as_ref().unwrap()) {
309 Ok(metadata) => metadata,
310 Err(_) => {
311 return Err(Box::new(OidcClientError::new_op_error(
312 "invalid_issuer_metadata".to_string(),
313 None,
314 None,
315 Some(res),
316 )));
317 }
318 };
319
320 Ok(Issuer::from(issuer_metadata))
321 }
322}
323
324impl Issuer {
326 pub async fn webfinger_async<T>(http_client: &T, input: &str) -> OidcReturnType<Issuer>
334 where
335 T: OidcHttpClient,
336 {
337 let req = Self::build_webfinger_request(input)?;
338
339 let res = request_async(req, http_client).await?;
340
341 let expected_issuer = Self::process_webfinger_response(res)?;
342
343 let issuer_result = Issuer::discover_async(http_client, &expected_issuer).await;
344
345 Self::process_webfinger_issuer_result(issuer_result, expected_issuer)
346 }
347
348 fn build_webfinger_request(input: &str) -> OidcReturnType<HttpRequest> {
349 let resource = webfinger_normalize(input);
350
351 let mut host: Option<String> = None;
352
353 if resource.starts_with("acct:") {
354 let split: Vec<&str> = resource.split('@').collect();
355 host = split.last().map(|s| s.to_string());
356 } else if resource.starts_with("https://") {
357 let url = validate_url(&resource)?;
358
359 if let Some(host_str) = url.host_str() {
360 host = match url.port() {
361 Some(port) => Some(host_str.to_string() + &format!(":{port}")),
362 None => Some(host_str.to_string()),
363 }
364 }
365 }
366
367 if host.is_none() {
368 return Err(Box::new(OidcClientError::new_type_error(
369 "given input was invalid",
370 None,
371 )));
372 }
373
374 let mut web_finger_url =
375 Url::parse(&format!("https://{}/.well-known/webfinger", host.unwrap())).unwrap();
376
377 let mut headers = HashMap::new();
378 headers.insert("accept".to_string(), vec!["application/json".to_string()]);
379
380 web_finger_url.set_query(Some(&format!(
381 "resource={}&rel=http%3A%2F%2Fopenid.net%2Fspecs%2Fconnect%2F1.0%2Fissuer",
382 urlencoding::encode(&resource)
383 )));
384
385 Ok(HttpRequest::new()
386 .url(web_finger_url)
387 .method(HttpMethod::GET)
388 .headers(headers)
389 .expect_bearer(false)
390 .expect_status_code(200)
391 .expect_body(true))
392 }
393
394 fn process_webfinger_response(response: HttpResponse) -> OidcReturnType<String> {
395 let webfinger_response =
396 match convert_json_to::<WebFingerResponse>(response.body.as_ref().unwrap()) {
397 Ok(res) => res,
398 Err(_) => {
399 return Err(Box::new(OidcClientError::new_op_error(
400 "invalid webfinger response".to_string(),
401 None,
402 None,
403 Some(response),
404 )));
405 }
406 };
407
408 let location_link_result = webfinger_response
409 .links
410 .iter()
411 .find(|x| x.rel == "http://openid.net/specs/connect/1.0/issuer" && x.href.is_some());
412
413 let expected_issuer = match location_link_result {
414 Some(link) => link.href.as_ref().unwrap(),
415 None => {
416 return Err(Box::new(OidcClientError::new_rp_error(
417 "no issuer found in webfinger response",
418 Some(response),
419 )));
420 }
421 };
422
423 if !expected_issuer.starts_with("https://") {
424 return Err(Box::new(OidcClientError::new_op_error(
425 "invalid_location".to_string(),
426 Some(format!("invalid issuer location {expected_issuer}")),
427 None,
428 Some(response),
429 )));
430 }
431
432 Ok(expected_issuer.to_string())
433 }
434
435 fn process_webfinger_issuer_result(
436 issuer_result: OidcReturnType<Issuer>,
437 expected_issuer: String,
438 ) -> OidcReturnType<Issuer> {
439 let mut response = None;
440
441 let issuer = match issuer_result {
442 Ok(i) => i,
443 Err(err) => {
444 response = match err.as_ref() {
445 OidcClientError::Error(_, response) => response.as_ref(),
446 OidcClientError::TypeError(_, response) => response.as_ref(),
447 OidcClientError::RPError(_, response) => response.as_ref(),
448 OidcClientError::OPError(_, response) => response.as_ref(),
449 };
450
451 if let Some(error_res) = response {
452 if error_res.status_code == 404 {
453 return Err(Box::new(OidcClientError::new_op_error(
454 "no_issuer".to_string(),
455 Some(format!("invalid issuer location {expected_issuer}")),
456 None,
457 Some(error_res.clone()),
458 )));
459 }
460 }
461
462 return Err(err);
463 }
464 };
465
466 if issuer.issuer != expected_issuer {
467 return Err(Box::new(OidcClientError::new_op_error(
468 "issuer_mismatch".to_string(),
469 Some(format!(
470 "discovered issuer mismatch, expected {expected_issuer}, got: {}",
471 issuer.issuer
472 )),
473 None,
474 response.map(|r| r.to_owned()),
475 )));
476 }
477
478 Ok(issuer)
479 }
480}
481
482impl Issuer {
484 pub fn client(
496 &self,
497 metadata: ClientMetadata,
498 jwks: Option<Jwks>,
499 client_options: Option<ClientOptions>,
500 fapi: Option<Fapi>,
501 ) -> OidcReturnType<Client> {
502 Client::jwks_only_private_keys_validation(jwks.as_ref())?;
503
504 Client::from_internal(metadata, Some(self), jwks, client_options, fapi)
505 }
506}
507
508impl Clone for Issuer {
509 fn clone(&self) -> Self {
510 Self {
511 issuer: self.issuer.clone(),
512 authorization_endpoint: self.authorization_endpoint.clone(),
513 token_endpoint: self.token_endpoint.clone(),
514 jwks_uri: self.jwks_uri.clone(),
515 userinfo_endpoint: self.userinfo_endpoint.clone(),
516 revocation_endpoint: self.revocation_endpoint.clone(),
517 claims_parameter_supported: self.claims_parameter_supported,
518 grant_types_supported: self.grant_types_supported.clone(),
519 request_parameter_supported: self.request_parameter_supported,
520 request_uri_parameter_supported: self.request_uri_parameter_supported,
521 require_request_uri_registration: self.require_request_uri_registration,
522 response_modes_supported: self.response_modes_supported.clone(),
523 claim_types_supported: self.claim_types_supported.clone(),
524 token_endpoint_auth_methods_supported: self
525 .token_endpoint_auth_methods_supported
526 .clone(),
527 token_endpoint_auth_signing_alg_values_supported: self
528 .token_endpoint_auth_signing_alg_values_supported
529 .clone(),
530 introspection_endpoint_auth_methods_supported: self
531 .introspection_endpoint_auth_methods_supported
532 .clone(),
533 introspection_endpoint_auth_signing_alg_values_supported: self
534 .introspection_endpoint_auth_signing_alg_values_supported
535 .clone(),
536 revocation_endpoint_auth_methods_supported: self
537 .revocation_endpoint_auth_methods_supported
538 .clone(),
539 revocation_endpoint_auth_signing_alg_values_supported: self
540 .revocation_endpoint_auth_signing_alg_values_supported
541 .clone(),
542 other_fields: self.other_fields.clone(),
543 keystore: self.keystore.clone(),
544 mtls_endpoint_aliases: self.mtls_endpoint_aliases.clone(),
545 introspection_endpoint: self.introspection_endpoint.clone(),
546 registration_endpoint: self.registration_endpoint.clone(),
547 end_session_endpoint: self.end_session_endpoint.clone(),
548 authorization_response_iss_parameter_supported: self
549 .authorization_response_iss_parameter_supported,
550 dpop_signing_alg_values_supported: self.dpop_signing_alg_values_supported.clone(),
551 pushed_authorization_request_endpoint: self
552 .pushed_authorization_request_endpoint
553 .clone(),
554 require_pushed_authorization_requests: self.require_pushed_authorization_requests,
555 device_authorization_endpoint: self.device_authorization_endpoint.clone(),
556 backchannel_token_delivery_modes_supported: self
557 .backchannel_token_delivery_modes_supported
558 .clone(),
559 backchannel_authentication_endpoint: self.backchannel_authentication_endpoint.clone(),
560 backchannel_authentication_request_signing_alg_values_supported: self
561 .backchannel_authentication_request_signing_alg_values_supported
562 .clone(),
563 backchannel_user_code_parameter_supported: self
564 .backchannel_user_code_parameter_supported,
565 now,
566 }
567 }
568}
569
570impl Issuer {
571 pub fn get_metadata(&self) -> IssuerMetadata {
573 IssuerMetadata {
574 issuer: self.issuer.clone(),
575 authorization_endpoint: self.authorization_endpoint.clone(),
576 device_authorization_endpoint: self.device_authorization_endpoint.clone(),
577 token_endpoint: self.token_endpoint.clone(),
578 jwks_uri: self.jwks_uri.clone(),
579 userinfo_endpoint: self.userinfo_endpoint.clone(),
580 revocation_endpoint: self.revocation_endpoint.clone(),
581 end_session_endpoint: self.end_session_endpoint.clone(),
582 registration_endpoint: self.registration_endpoint.clone(),
583 introspection_endpoint: self.introspection_endpoint.clone(),
584 token_endpoint_auth_methods_supported: self
585 .token_endpoint_auth_methods_supported
586 .clone(),
587 token_endpoint_auth_signing_alg_values_supported: self
588 .token_endpoint_auth_signing_alg_values_supported
589 .clone(),
590 introspection_endpoint_auth_methods_supported: self
591 .introspection_endpoint_auth_methods_supported
592 .clone(),
593 introspection_endpoint_auth_signing_alg_values_supported: self
594 .introspection_endpoint_auth_signing_alg_values_supported
595 .clone(),
596 revocation_endpoint_auth_methods_supported: self
597 .revocation_endpoint_auth_methods_supported
598 .clone(),
599 revocation_endpoint_auth_signing_alg_values_supported: self
600 .revocation_endpoint_auth_signing_alg_values_supported
601 .clone(),
602 mtls_endpoint_aliases: self.mtls_endpoint_aliases.clone(),
603 authorization_response_iss_parameter_supported: self
604 .authorization_response_iss_parameter_supported,
605 dpop_signing_alg_values_supported: self.dpop_signing_alg_values_supported.clone(),
606 pushed_authorization_request_endpoint: self
607 .pushed_authorization_request_endpoint
608 .clone(),
609 require_pushed_authorization_requests: self.require_pushed_authorization_requests,
610 backchannel_token_delivery_modes_supported: self
611 .backchannel_token_delivery_modes_supported
612 .clone(),
613 backchannel_authentication_endpoint: self.backchannel_authentication_endpoint.clone(),
614 backchannel_authentication_request_signing_alg_values_supported: self
615 .backchannel_authentication_request_signing_alg_values_supported
616 .clone(),
617 backchannel_user_code_parameter_supported: Some(
618 self.backchannel_user_code_parameter_supported,
619 ),
620 other_fields: self.other_fields.clone(),
621 }
622 }
623
624 pub async fn get_jwks<T>(&mut self, http_client: &T) -> Option<Jwks>
626 where
627 T: OidcHttpClient,
628 {
629 if let Some(ks) = &mut self.keystore {
630 return ks.get_keystore_async(false, http_client).await.ok();
631 }
632
633 None
634 }
635}
636
637#[cfg(test)]
638#[path = "../tests/issuer/mod.rs"]
639mod issuer_tests;