1use std::collections::{BTreeMap, BTreeSet};
4use std::fmt::Display;
5
6use base64::{engine::general_purpose::STANDARD, Engine as _};
7use serde::{Deserialize, Serialize};
8use serde_with::base64::{Base64, UrlSafe};
9use serde_with::formats::SpaceSeparator;
10use serde_with::{
11 formats, rust::deserialize_ignore_any, serde_as, skip_serializing_none, StringWithSeparator,
12};
13use url::Url;
14use uuid::Uuid;
15
16pub const OAUTH2_DEVICE_CODE_EXPIRY_SECONDS: u64 = 300;
18pub const OAUTH2_DEVICE_CODE_INTERVAL_SECONDS: u64 = 5;
20pub const OAUTH2_TOKEN_TYPE_ACCESS_TOKEN: &str = "urn:ietf:params:oauth:token-type:access_token";
22
23#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
24pub enum CodeChallengeMethod {
25 S256,
29}
30
31#[serde_as]
32#[derive(Serialize, Deserialize, Debug, Clone)]
33pub struct PkceRequest {
34 #[serde_as(as = "Base64<UrlSafe, formats::Unpadded>")]
35 pub code_challenge: Vec<u8>,
36 pub code_challenge_method: CodeChallengeMethod,
37}
38
39#[serde_as]
42#[skip_serializing_none]
43#[derive(Serialize, Deserialize, Debug, Clone)]
44pub struct AuthorisationRequest {
45 pub response_type: ResponseType,
47 pub response_mode: Option<ResponseMode>,
56 pub client_id: String,
57 pub state: Option<String>,
58 #[serde(flatten)]
59 pub pkce_request: Option<PkceRequest>,
60 pub redirect_uri: Url,
61 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
62 pub scope: BTreeSet<String>,
63 pub nonce: Option<String>,
65 #[serde(flatten)]
67 pub oidc_ext: AuthorisationRequestOidc,
68 pub max_age: Option<i64>,
70 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, Prompt>")]
71 #[serde(default)]
72 pub prompt: Vec<Prompt>,
73
74 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
75 #[serde(default)]
76 pub ui_locales: Vec<String>,
77
78 #[serde(flatten)]
79 pub unknown_keys: BTreeMap<String, serde_json::value::Value>,
80}
81
82impl AuthorisationRequest {
83 pub const fn get_response_mode(&self) -> Option<ResponseMode> {
91 match (self.response_mode, self.response_type) {
92 (None, ResponseType::IdToken) => Some(ResponseMode::Fragment),
96 (Some(ResponseMode::Query), ResponseType::IdToken) => None,
97
98 (None, ResponseType::Code) => Some(ResponseMode::Query),
100 (None, ResponseType::Token) => Some(ResponseMode::Fragment),
102
103 (Some(ResponseMode::Query), ResponseType::Token) => None,
108
109 (Some(m), _) => Some(m),
111 }
112 }
113}
114
115#[skip_serializing_none]
118#[derive(Serialize, Deserialize, Debug, Clone, Default)]
119pub struct AuthorisationRequestOidc {
120 pub display: Option<String>,
121 pub prompt: Option<String>,
122 pub ui_locales: Option<()>,
123 pub claims_locales: Option<()>,
124 pub id_token_hint: Option<String>,
125 pub login_hint: Option<String>,
126 pub acr: Option<String>,
127}
128
129#[derive(Serialize, Deserialize, Debug, Clone)]
133pub enum AuthorisationResponse {
134 ConsentRequested {
135 client_name: String,
137 scopes: BTreeSet<String>,
139 pii_scopes: BTreeSet<String>,
141 consent_token: String,
145 },
146 Permitted,
147}
148
149#[serde_as]
150#[skip_serializing_none]
151#[derive(Serialize, Deserialize, Debug)]
152#[serde(tag = "grant_type", rename_all = "snake_case")]
153pub enum GrantTypeReq {
154 AuthorizationCode {
155 code: String,
157 redirect_uri: Url,
159 code_verifier: Option<String>,
160 },
161 ClientCredentials {
162 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
163 scope: Option<BTreeSet<String>>,
164 },
165 RefreshToken {
166 refresh_token: String,
167 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
168 scope: Option<BTreeSet<String>>,
169 },
170 #[serde(rename = "urn:ietf:params:oauth:grant-type:token-exchange")]
171 TokenExchange {
172 subject_token: String,
173 subject_token_type: String,
174 requested_token_type: Option<String>,
175 audience: Option<String>,
176 resource: Option<String>,
177 actor_token: Option<String>,
178 actor_token_type: Option<String>,
179 #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, String>>")]
180 scope: Option<BTreeSet<String>>,
181 },
182 #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
184 DeviceCode {
185 device_code: String,
186 scope: Option<BTreeSet<String>>,
188 },
189}
190
191#[skip_serializing_none]
193#[derive(Serialize, Deserialize, Debug)]
194pub struct AccessTokenRequest {
195 #[serde(flatten)]
196 pub grant_type: GrantTypeReq,
197 #[serde(flatten)]
200 pub client_post_auth: ClientPostAuth,
201}
202
203impl From<GrantTypeReq> for AccessTokenRequest {
204 fn from(req: GrantTypeReq) -> AccessTokenRequest {
205 AccessTokenRequest {
206 grant_type: req,
207 client_post_auth: ClientPostAuth::default(),
208 }
209 }
210}
211
212#[derive(Serialize, Debug, Clone, Deserialize)]
213#[skip_serializing_none]
214pub struct OAuth2RFC9068Token<V>
215where
216 V: Clone,
217{
218 pub iss: String,
220 pub sub: Uuid,
222 pub aud: String,
224 pub exp: i64,
226 pub nbf: i64,
228 pub iat: i64,
230 pub jti: Uuid,
232 pub client_id: String,
233 #[serde(flatten)]
234 pub extensions: V,
235}
236
237#[serde_as]
239#[skip_serializing_none]
240#[derive(Serialize, Deserialize, Debug, Clone)]
241pub struct OAuth2RFC9068TokenExtensions {
242 pub auth_time: Option<i64>,
243 pub acr: Option<String>,
244 pub amr: Option<Vec<String>>,
245
246 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
247 pub scope: BTreeSet<String>,
248
249 pub nonce: Option<String>,
250
251 pub session_id: Uuid,
252 pub parent_session_id: Option<Uuid>,
253}
254
255#[derive(Serialize, Deserialize, Debug, PartialEq)]
256pub enum IssuedTokenType {
257 AccessToken,
258 RefreshToken,
259 IdToken,
260 Saml1,
261 Saml2,
262}
263
264#[serde_as]
266#[skip_serializing_none]
267#[derive(Serialize, Deserialize, Debug)]
268pub struct AccessTokenResponse {
269 pub access_token: String,
270 pub token_type: AccessTokenType,
271 pub issued_token_type: Option<IssuedTokenType>,
273 pub expires_in: u32,
275 pub refresh_token: Option<String>,
276 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
279 pub scope: BTreeSet<String>,
280 pub id_token: Option<String>,
282}
283
284#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
286#[serde(try_from = "&str")]
287pub enum AccessTokenType {
288 Bearer,
289 PoP,
290 #[serde(rename = "N_A")]
291 NA,
292 DPoP,
293}
294
295impl TryFrom<&str> for AccessTokenType {
296 type Error = String;
297
298 fn try_from(s: &str) -> Result<Self, Self::Error> {
299 match s.to_lowercase().as_str() {
300 "bearer" => Ok(AccessTokenType::Bearer),
301 "pop" => Ok(AccessTokenType::PoP),
302 "n_a" => Ok(AccessTokenType::NA),
303 "dpop" => Ok(AccessTokenType::DPoP),
304 _ => Err(format!("Unknown AccessTokenType: {s}")),
305 }
306 }
307}
308
309#[skip_serializing_none]
312#[derive(Serialize, Deserialize, Debug)]
313pub struct TokenRevokeRequest {
314 pub token: String,
315 pub token_type_hint: Option<String>,
318
319 #[serde(flatten)]
320 pub client_post_auth: ClientPostAuth,
321}
322
323#[skip_serializing_none]
324#[derive(Serialize, Deserialize, Debug, Default)]
325pub struct ClientPostAuth {
327 pub client_id: Option<String>,
328 pub client_secret: Option<String>,
329}
330
331impl From<(String, Option<String>)> for ClientPostAuth {
332 fn from((client_id, client_secret): (String, Option<String>)) -> Self {
333 ClientPostAuth {
334 client_id: Some(client_id),
335 client_secret,
336 }
337 }
338}
339
340impl From<(&str, Option<&str>)> for ClientPostAuth {
341 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
342 ClientPostAuth {
343 client_id: Some(client_id.to_string()),
344 client_secret: client_secret.map(|s| s.to_string()),
345 }
346 }
347}
348
349#[skip_serializing_none]
350#[derive(Serialize, Deserialize, Debug, Default)]
351pub struct ClientAuth {
353 pub client_id: String,
354 pub client_secret: Option<String>,
355}
356
357impl From<(&str, Option<&str>)> for ClientAuth {
358 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
359 ClientAuth {
360 client_id: client_id.to_string(),
361 client_secret: client_secret.map(|s| s.to_string()),
362 }
363 }
364}
365
366#[skip_serializing_none]
368#[derive(Serialize, Deserialize, Debug)]
369pub struct AccessTokenIntrospectRequest {
370 pub token: String,
371 pub token_type_hint: Option<String>,
374
375 #[serde(flatten)]
378 pub client_post_auth: ClientPostAuth,
379}
380
381impl From<String> for AccessTokenIntrospectRequest {
382 fn from(token: String) -> Self {
383 Self {
384 token,
385 token_type_hint: None,
386 client_post_auth: ClientPostAuth::default(),
387 }
388 }
389}
390
391#[serde_as]
394#[skip_serializing_none]
395#[derive(Serialize, Deserialize, Debug, Default)]
396pub struct AccessTokenIntrospectResponse {
397 pub active: bool,
398 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
399 pub scope: BTreeSet<String>,
400 pub client_id: Option<String>,
401 pub username: Option<String>,
402 pub token_type: Option<AccessTokenType>,
403 pub exp: Option<i64>,
404 pub iat: Option<i64>,
405 pub nbf: Option<i64>,
406 pub sub: Option<String>,
407 pub aud: Option<String>,
408 pub iss: Option<String>,
409 pub jti: Uuid,
411}
412
413impl AccessTokenIntrospectResponse {
414 pub fn inactive(session_id: Uuid) -> Self {
415 AccessTokenIntrospectResponse {
416 active: false,
417 scope: BTreeSet::default(),
418 client_id: None,
419 username: None,
420 token_type: None,
421 exp: None,
422 iat: None,
423 nbf: None,
424 sub: None,
425 aud: None,
426 iss: None,
427 jti: session_id,
428 }
429 }
430}
431
432#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
433#[serde(rename_all = "snake_case")]
434pub enum ResponseType {
435 Code,
438 Token,
441 IdToken,
443}
444
445#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
446#[serde(rename_all = "snake_case")]
447pub enum ResponseMode {
448 Query,
449 Fragment,
450 FormPost,
451 #[serde(other, deserialize_with = "deserialize_ignore_any")]
452 Invalid,
453}
454
455fn response_modes_supported_default() -> Vec<ResponseMode> {
456 vec![ResponseMode::Query, ResponseMode::Fragment]
457}
458
459#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
460#[serde(rename_all = "snake_case")]
461pub enum Prompt {
462 None,
471 Login,
472 Consent,
473 SelectAccount,
474 #[serde(untagged)]
475 Invalid(String),
476}
477
478impl Display for Prompt {
479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480 let s = match self {
481 Prompt::None => "none",
482 Prompt::Login => "login",
483 Prompt::Consent => "consent",
484 Prompt::SelectAccount => "select_account",
485 Prompt::Invalid(str) => &format!("invalid({})", str),
486 };
487 write!(f, "{s}")
488 }
489}
490
491impl std::str::FromStr for Prompt {
492 type Err = std::convert::Infallible;
493
494 fn from_str(s: &str) -> Result<Self, Self::Err> {
495 Ok(match s {
496 "none" => Prompt::None,
497 "login" => Prompt::Login,
498 "consent" => Prompt::Consent,
499 "select_account" => Prompt::SelectAccount,
500 other => Prompt::Invalid(other.to_string()),
501 })
502 }
503}
504
505#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
506#[serde(rename_all = "snake_case")]
507pub enum GrantType {
508 #[serde(rename = "authorization_code")]
509 AuthorisationCode,
510 Implicit,
511 #[serde(rename = "urn:ietf:params:oauth:grant-type:token-exchange")]
512 TokenExchange,
513 ClientCredentials,
514 RefreshToken,
515 #[serde(rename = "urn:ietf:params:oauth:grant-type:jwt-bearer")]
516 JwtBearer,
517}
518
519fn grant_types_supported_default() -> Vec<GrantType> {
520 vec![
521 GrantType::AuthorisationCode,
522 GrantType::Implicit,
523 GrantType::TokenExchange,
524 ]
525}
526
527#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
528#[serde(rename_all = "snake_case")]
529pub enum SubjectType {
530 Pairwise,
531 Public,
532}
533
534#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
535pub enum PkceAlg {
536 S256,
537}
538
539#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
540#[serde(rename_all = "UPPERCASE")]
541pub enum IdTokenSignAlg {
543 ES256,
545 RS256,
546}
547
548#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
549#[serde(rename_all = "snake_case")]
550pub enum EndpointAuthMethod {
551 None,
552 ClientSecretPost,
553 ClientSecretBasic,
554 ClientSecretJwt,
555 PrivateKeyJwt,
556}
557
558fn token_endpoint_auth_methods_supported_default() -> Vec<EndpointAuthMethod> {
559 vec![EndpointAuthMethod::ClientSecretBasic]
560}
561
562#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
563#[serde(rename_all = "snake_case")]
564pub enum DisplayValue {
565 Page,
566 Popup,
567 Touch,
568 Wap,
569}
570
571#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
572#[serde(rename_all = "snake_case")]
573pub enum ClaimType {
575 Normal,
576 Aggregated,
577 Distributed,
578}
579
580fn claim_types_supported_default() -> Vec<ClaimType> {
581 vec![ClaimType::Normal]
582}
583
584fn claims_parameter_supported_default() -> bool {
585 false
586}
587
588fn request_parameter_supported_default() -> bool {
589 false
590}
591
592fn request_uri_parameter_supported_default() -> bool {
593 false
594}
595
596fn require_request_uri_parameter_supported_default() -> bool {
597 false
598}
599
600#[derive(Serialize, Deserialize, Debug)]
601pub struct OidcWebfingerRel {
602 pub rel: String,
603 pub href: String,
604}
605
606#[skip_serializing_none]
609#[derive(Serialize, Deserialize, Debug)]
610pub struct OidcWebfingerResponse {
611 pub subject: String,
612 pub links: Vec<OidcWebfingerRel>,
613}
614
615#[skip_serializing_none]
618#[derive(Serialize, Deserialize, Debug)]
619pub struct OidcDiscoveryResponse {
620 pub issuer: Url,
621 pub authorization_endpoint: Url,
622 pub token_endpoint: Url,
623 pub userinfo_endpoint: Option<Url>,
624 pub jwks_uri: Url,
625 pub registration_endpoint: Option<Url>,
626 pub scopes_supported: Option<Vec<String>>,
627 pub response_types_supported: Vec<ResponseType>,
629 #[serde(default = "response_modes_supported_default")]
631 pub response_modes_supported: Vec<ResponseMode>,
632 #[serde(default = "grant_types_supported_default")]
634 pub grant_types_supported: Vec<GrantType>,
635 pub acr_values_supported: Option<Vec<String>>,
636 pub subject_types_supported: Vec<SubjectType>,
638 pub id_token_signing_alg_values_supported: Vec<IdTokenSignAlg>,
639 pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
640 pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
641 pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
642 pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
643 pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
644 pub request_object_signing_alg_values_supported: Option<Vec<String>>,
645 pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
646 pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
647 #[serde(default = "token_endpoint_auth_methods_supported_default")]
649 pub token_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
650 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
651 pub display_values_supported: Option<Vec<DisplayValue>>,
653 #[serde(default = "claim_types_supported_default")]
655 pub claim_types_supported: Vec<ClaimType>,
656 pub claims_supported: Option<Vec<String>>,
657 pub service_documentation: Option<Url>,
658 pub claims_locales_supported: Option<Vec<String>>,
659 pub ui_locales_supported: Option<Vec<String>>,
660 #[serde(default = "claims_parameter_supported_default")]
662 pub claims_parameter_supported: bool,
663
664 pub op_policy_uri: Option<Url>,
665 pub op_tos_uri: Option<Url>,
666
667 #[serde(default = "request_parameter_supported_default")]
669 pub request_parameter_supported: bool,
670 #[serde(default = "request_uri_parameter_supported_default")]
671 pub request_uri_parameter_supported: bool,
672 #[serde(default = "require_request_uri_parameter_supported_default")]
673 pub require_request_uri_registration: bool,
674
675 pub code_challenge_methods_supported: Vec<PkceAlg>,
676
677 pub revocation_endpoint: Option<Url>,
685 pub revocation_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
686
687 pub introspection_endpoint: Option<Url>,
689 pub introspection_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
690 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
691
692 pub device_authorization_endpoint: Option<Url>,
694}
695
696#[skip_serializing_none]
698#[derive(Serialize, Deserialize, Debug)]
699pub struct Oauth2Rfc8414MetadataResponse {
700 pub issuer: Url,
701 pub authorization_endpoint: Url,
702 pub token_endpoint: Url,
703
704 pub jwks_uri: Option<Url>,
705
706 pub registration_endpoint: Option<Url>,
708
709 pub scopes_supported: Option<Vec<String>>,
710
711 pub response_types_supported: Vec<ResponseType>,
713 #[serde(default = "response_modes_supported_default")]
714 pub response_modes_supported: Vec<ResponseMode>,
715 #[serde(default = "grant_types_supported_default")]
716 pub grant_types_supported: Vec<GrantType>,
717
718 #[serde(default = "token_endpoint_auth_methods_supported_default")]
719 pub token_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
720
721 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
722
723 pub service_documentation: Option<Url>,
724 pub ui_locales_supported: Option<Vec<String>>,
725
726 pub op_policy_uri: Option<Url>,
727 pub op_tos_uri: Option<Url>,
728
729 pub revocation_endpoint: Option<Url>,
731 pub revocation_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
732
733 pub introspection_endpoint: Option<Url>,
735 pub introspection_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
736 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
737
738 pub code_challenge_methods_supported: Vec<PkceAlg>,
740}
741
742#[skip_serializing_none]
743#[derive(Serialize, Deserialize, Debug, Default)]
744pub struct ErrorResponse {
745 pub error: String,
746 pub error_description: Option<String>,
747 pub error_uri: Option<Url>,
748}
749
750#[derive(Debug, Serialize, Deserialize)]
751pub struct DeviceAuthorizationResponse {
753 device_code: String,
755 user_code: String,
757 verification_uri: Url,
758 verification_uri_complete: Url,
759 expires_in: u64,
760 interval: u64,
761}
762
763impl DeviceAuthorizationResponse {
764 pub fn new(verification_uri: Url, device_code: [u8; 16], user_code: String) -> Self {
765 let mut verification_uri_complete = verification_uri.clone();
766 verification_uri_complete
767 .query_pairs_mut()
768 .append_pair("user_code", &user_code);
769
770 let device_code = STANDARD.encode(device_code);
771
772 Self {
773 verification_uri_complete,
774 device_code,
775 user_code,
776 verification_uri,
777 expires_in: OAUTH2_DEVICE_CODE_EXPIRY_SECONDS,
778 interval: OAUTH2_DEVICE_CODE_INTERVAL_SECONDS,
779 }
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use super::{AccessTokenRequest, GrantTypeReq, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN};
786 use std::collections::BTreeSet;
787 use url::Url;
788
789 #[test]
790 fn test_oauth2_access_token_req() {
791 let atr: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
792 code: "demo code".to_string(),
793 redirect_uri: Url::parse("http://[::1]").unwrap(),
794 code_verifier: None,
795 }
796 .into();
797
798 println!("{:?}", serde_json::to_string(&atr).expect("JSON failure"));
799 }
800
801 #[test]
802 fn test_oauth2_access_token_type_serde() {
803 for testcase in ["bearer", "Bearer", "BeArEr"] {
804 let at: super::AccessTokenType =
805 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
806 assert_eq!(at, super::AccessTokenType::Bearer);
807 }
808
809 for testcase in ["dpop", "dPoP", "DPOP", "DPoP"] {
810 let at: super::AccessTokenType =
811 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
812 assert_eq!(at, super::AccessTokenType::DPoP);
813 }
814
815 {
816 let testcase = "cheese";
817 let at = serde_json::from_str::<super::AccessTokenType>(&format!("\"{testcase}\""));
818 assert!(at.is_err())
819 }
820 }
821
822 #[test]
823 fn test_token_exchange_grant_serialization() {
824 let scopes: BTreeSet<String> = ["groups", "openid"]
825 .into_iter()
826 .map(str::to_string)
827 .collect();
828
829 let atr = AccessTokenRequest {
830 grant_type: GrantTypeReq::TokenExchange {
831 subject_token: "subject".to_string(),
832 subject_token_type: OAUTH2_TOKEN_TYPE_ACCESS_TOKEN.to_string(),
833 requested_token_type: None,
834 audience: Some("test_resource_server".to_string()),
835 resource: None,
836 actor_token: None,
837 actor_token_type: None,
838 scope: Some(scopes.clone()),
839 },
840 client_post_auth: Default::default(),
841 };
842
843 let json = serde_json::to_string(&atr).expect("JSON failure");
844 let de: AccessTokenRequest = serde_json::from_str(&json).expect("Roundtrip failure");
845
846 match de.grant_type {
847 GrantTypeReq::TokenExchange {
848 subject_token,
849 subject_token_type,
850 requested_token_type,
851 audience,
852 actor_token,
853 actor_token_type,
854 scope: descope,
855 ..
856 } => {
857 assert_eq!(subject_token, "subject");
858 assert_eq!(subject_token_type, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN);
859 assert_eq!(requested_token_type, None);
860 assert_eq!(audience.as_deref(), Some("test_resource_server"));
861 assert_eq!(actor_token, None);
862 assert_eq!(actor_token_type, None);
863 assert_eq!(descope, Some(scopes));
864 }
865 _ => panic!("Wrong grant type"),
866 }
867 }
868
869 #[test]
870 fn test_authorisation_request_prompt_single_value() {
871 let qs = "response_type=code\
872 &client_id=test_client\
873 &redirect_uri=http%3A%2F%2Flocalhost\
874 &scope=openid\
875 &prompt=login";
876
877 let req: super::AuthorisationRequest =
878 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
879
880 assert_eq!(req.prompt.len(), 1);
881 assert!(req.prompt.contains(&super::Prompt::Login));
882 }
883
884 #[test]
885 fn test_authorisation_request_prompt_multiple_values() {
886 let qs = "response_type=code\
887 &client_id=test_client\
888 &redirect_uri=http%3A%2F%2Flocalhost\
889 &scope=openid\
890 &prompt=login%20consent";
891
892 let req: super::AuthorisationRequest =
893 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
894
895 assert_eq!(req.prompt.len(), 2);
896 assert!(req.prompt.contains(&super::Prompt::Login));
897 assert!(req.prompt.contains(&super::Prompt::Consent));
898 }
899
900 #[test]
901 fn test_authorisation_request_prompt_none() {
902 let qs = "response_type=code\
903 &client_id=test_client\
904 &redirect_uri=http%3A%2F%2Flocalhost\
905 &scope=openid\
906 &prompt=none";
907
908 let req: super::AuthorisationRequest =
909 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
910
911 assert_eq!(req.prompt.len(), 1);
912 assert!(req.prompt.contains(&super::Prompt::None));
913 }
914
915 #[test]
916 fn test_authorisation_request_prompt_absent() {
917 let qs = "response_type=code\
918 &client_id=test_client\
919 &redirect_uri=http%3A%2F%2Flocalhost\
920 &scope=openid";
921
922 let req: super::AuthorisationRequest =
923 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
924
925 assert!(req.prompt.is_empty());
926 }
927
928 #[test]
929 fn test_authorisation_request_prompt_invalid_value() {
930 let qs = "response_type=code\
931 &client_id=test_client\
932 &redirect_uri=http%3A%2F%2Flocalhost\
933 &scope=openid\
934 &prompt=bogus";
935
936 let req: super::AuthorisationRequest =
937 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
938
939 assert_eq!(req.prompt.len(), 1);
940 assert!(req
941 .prompt
942 .contains(&super::Prompt::Invalid("bogus".to_string())));
943 }
944
945 #[test]
946 fn test_authorisation_request_prompt_select_account() {
947 let qs = "response_type=code\
948 &client_id=test_client\
949 &redirect_uri=http%3A%2F%2Flocalhost\
950 &scope=openid\
951 &prompt=select_account";
952
953 let req: super::AuthorisationRequest =
954 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
955
956 assert_eq!(req.prompt.len(), 1);
957 assert!(req.prompt.contains(&super::Prompt::SelectAccount));
958 }
959
960 #[test]
961 fn test_authorisation_request_ui_locales() {
962 let qs = "response_type=code\
963 &client_id=test_client\
964 &redirect_uri=http%3A%2F%2Flocalhost\
965 &scope=openid\
966 &ui_locales=en-US";
967
968 let req: super::AuthorisationRequest =
969 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
970
971 assert_eq!(req.ui_locales.len(), 1);
972 assert!(req.ui_locales.contains(&"en-US".to_string()));
973
974 let qs = "response_type=code\
975 &client_id=test_client\
976 &redirect_uri=http%3A%2F%2Flocalhost\
977 &scope=openid\
978 &ui_locales=en-US%20fr-FR";
979
980 let req: super::AuthorisationRequest =
981 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
982 assert_eq!(req.ui_locales.len(), 2);
983 assert!(req.ui_locales.contains(&"fr-FR".to_string()));
984
985 let qs = "response_type=code\
986 &client_id=test_client\
987 &redirect_uri=http%3A%2F%2Flocalhost\
988 &scope=openid";
989
990 let req: super::AuthorisationRequest =
991 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
992 assert_eq!(req.ui_locales.len(), 0);
993 assert!(req.ui_locales.is_empty());
994 }
995
996 #[test]
997 fn test_authorisation_request_prompt_all_valid_values() {
998 let qs = "response_type=code\
999 &client_id=test_client\
1000 &redirect_uri=http%3A%2F%2Flocalhost\
1001 &scope=openid\
1002 &prompt=login+consent+select_account";
1003
1004 let req: super::AuthorisationRequest =
1005 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
1006
1007 assert_eq!(req.prompt.len(), 3);
1008 assert!(req.prompt.contains(&super::Prompt::Login));
1009 assert!(req.prompt.contains(&super::Prompt::Consent));
1010 assert!(req.prompt.contains(&super::Prompt::SelectAccount));
1011 }
1012}