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 #[serde(rename = "urn:ietf:params:oauth:token-type:access_token")]
258 AccessToken,
259 #[serde(rename = "urn:ietf:params:oauth:token-type:refresh_token")]
260 RefreshToken,
261 #[serde(rename = "urn:ietf:params:oauth:token-type:id_token")]
262 IdToken,
263 #[serde(rename = "urn:ietf:params:oauth:token-type:saml1")]
264 Saml1,
265 #[serde(rename = "urn:ietf:params:oauth:token-type:saml2")]
266 Saml2,
267}
268
269#[serde_as]
271#[skip_serializing_none]
272#[derive(Serialize, Deserialize, Debug)]
273pub struct AccessTokenResponse {
274 pub access_token: String,
275 pub token_type: AccessTokenType,
276 pub issued_token_type: Option<IssuedTokenType>,
278 pub expires_in: u32,
280 pub refresh_token: Option<String>,
281 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
284 pub scope: BTreeSet<String>,
285 pub id_token: Option<String>,
287}
288
289#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
291#[serde(try_from = "&str")]
292pub enum AccessTokenType {
293 Bearer,
294 PoP,
295 #[serde(rename = "N_A")]
296 NA,
297 DPoP,
298}
299
300impl TryFrom<&str> for AccessTokenType {
301 type Error = String;
302
303 fn try_from(s: &str) -> Result<Self, Self::Error> {
304 match s.to_lowercase().as_str() {
305 "bearer" => Ok(AccessTokenType::Bearer),
306 "pop" => Ok(AccessTokenType::PoP),
307 "n_a" => Ok(AccessTokenType::NA),
308 "dpop" => Ok(AccessTokenType::DPoP),
309 _ => Err(format!("Unknown AccessTokenType: {s}")),
310 }
311 }
312}
313
314#[skip_serializing_none]
317#[derive(Serialize, Deserialize, Debug)]
318pub struct TokenRevokeRequest {
319 pub token: String,
320 pub token_type_hint: Option<String>,
323
324 #[serde(flatten)]
325 pub client_post_auth: ClientPostAuth,
326}
327
328#[skip_serializing_none]
329#[derive(Serialize, Deserialize, Debug, Default)]
330pub struct ClientPostAuth {
332 pub client_id: Option<String>,
333 pub client_secret: Option<String>,
334}
335
336impl From<(String, Option<String>)> for ClientPostAuth {
337 fn from((client_id, client_secret): (String, Option<String>)) -> Self {
338 ClientPostAuth {
339 client_id: Some(client_id),
340 client_secret,
341 }
342 }
343}
344
345impl From<(&str, Option<&str>)> for ClientPostAuth {
346 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
347 ClientPostAuth {
348 client_id: Some(client_id.to_string()),
349 client_secret: client_secret.map(|s| s.to_string()),
350 }
351 }
352}
353
354#[skip_serializing_none]
355#[derive(Serialize, Deserialize, Debug, Default)]
356pub struct ClientAuth {
358 pub client_id: String,
359 pub client_secret: Option<String>,
360}
361
362impl From<(&str, Option<&str>)> for ClientAuth {
363 fn from((client_id, client_secret): (&str, Option<&str>)) -> Self {
364 ClientAuth {
365 client_id: client_id.to_string(),
366 client_secret: client_secret.map(|s| s.to_string()),
367 }
368 }
369}
370
371#[skip_serializing_none]
373#[derive(Serialize, Deserialize, Debug)]
374pub struct AccessTokenIntrospectRequest {
375 pub token: String,
376 pub token_type_hint: Option<String>,
379
380 #[serde(flatten)]
383 pub client_post_auth: ClientPostAuth,
384}
385
386impl From<String> for AccessTokenIntrospectRequest {
387 fn from(token: String) -> Self {
388 Self {
389 token,
390 token_type_hint: None,
391 client_post_auth: ClientPostAuth::default(),
392 }
393 }
394}
395
396#[serde_as]
399#[skip_serializing_none]
400#[derive(Serialize, Deserialize, Debug, Default)]
401pub struct AccessTokenIntrospectResponse {
402 pub active: bool,
403 #[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
404 pub scope: BTreeSet<String>,
405 pub client_id: Option<String>,
406 pub username: Option<String>,
407 pub token_type: Option<AccessTokenType>,
408 pub exp: Option<i64>,
409 pub iat: Option<i64>,
410 pub nbf: Option<i64>,
411 pub sub: Option<String>,
412 pub aud: Option<String>,
413 pub iss: Option<String>,
414 pub jti: Uuid,
416}
417
418impl AccessTokenIntrospectResponse {
419 pub fn inactive(session_id: Uuid) -> Self {
420 AccessTokenIntrospectResponse {
421 active: false,
422 scope: BTreeSet::default(),
423 client_id: None,
424 username: None,
425 token_type: None,
426 exp: None,
427 iat: None,
428 nbf: None,
429 sub: None,
430 aud: None,
431 iss: None,
432 jti: session_id,
433 }
434 }
435}
436
437#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
438#[serde(rename_all = "snake_case")]
439pub enum ResponseType {
440 Code,
443 Token,
446 IdToken,
448}
449
450#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
451#[serde(rename_all = "snake_case")]
452pub enum ResponseMode {
453 Query,
454 Fragment,
455 FormPost,
456 #[serde(other, deserialize_with = "deserialize_ignore_any")]
457 Invalid,
458}
459
460fn response_modes_supported_default() -> Vec<ResponseMode> {
461 vec![ResponseMode::Query, ResponseMode::Fragment]
462}
463
464#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
465#[serde(rename_all = "snake_case")]
466pub enum Prompt {
467 None,
476 Login,
477 Consent,
478 SelectAccount,
479 #[serde(untagged)]
480 Invalid(String),
481}
482
483impl Display for Prompt {
484 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
485 let s = match self {
486 Prompt::None => "none",
487 Prompt::Login => "login",
488 Prompt::Consent => "consent",
489 Prompt::SelectAccount => "select_account",
490 Prompt::Invalid(str) => &format!("invalid({})", str),
491 };
492 write!(f, "{s}")
493 }
494}
495
496impl std::str::FromStr for Prompt {
497 type Err = std::convert::Infallible;
498
499 fn from_str(s: &str) -> Result<Self, Self::Err> {
500 Ok(match s {
501 "none" => Prompt::None,
502 "login" => Prompt::Login,
503 "consent" => Prompt::Consent,
504 "select_account" => Prompt::SelectAccount,
505 other => Prompt::Invalid(other.to_string()),
506 })
507 }
508}
509
510#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
511#[serde(rename_all = "snake_case")]
512pub enum GrantType {
513 #[serde(rename = "authorization_code")]
514 AuthorisationCode,
515 Implicit,
516 #[serde(rename = "urn:ietf:params:oauth:grant-type:token-exchange")]
517 TokenExchange,
518 ClientCredentials,
519 RefreshToken,
520 #[serde(rename = "urn:ietf:params:oauth:grant-type:jwt-bearer")]
521 JwtBearer,
522}
523
524fn grant_types_supported_default() -> Vec<GrantType> {
525 vec![
526 GrantType::AuthorisationCode,
527 GrantType::Implicit,
528 GrantType::TokenExchange,
529 ]
530}
531
532#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
533#[serde(rename_all = "snake_case")]
534pub enum SubjectType {
535 Pairwise,
536 Public,
537}
538
539#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
540pub enum PkceAlg {
541 S256,
542}
543
544#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
545#[serde(rename_all = "UPPERCASE")]
546pub enum IdTokenSignAlg {
548 ES256,
550 RS256,
551}
552
553#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
554#[serde(rename_all = "snake_case")]
555pub enum EndpointAuthMethod {
556 None,
557 ClientSecretPost,
558 ClientSecretBasic,
559 ClientSecretJwt,
560 PrivateKeyJwt,
561}
562
563fn token_endpoint_auth_methods_supported_default() -> Vec<EndpointAuthMethod> {
564 vec![EndpointAuthMethod::ClientSecretBasic]
565}
566
567#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
568#[serde(rename_all = "snake_case")]
569pub enum DisplayValue {
570 Page,
571 Popup,
572 Touch,
573 Wap,
574}
575
576#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
577#[serde(rename_all = "snake_case")]
578pub enum ClaimType {
580 Normal,
581 Aggregated,
582 Distributed,
583}
584
585fn claim_types_supported_default() -> Vec<ClaimType> {
586 vec![ClaimType::Normal]
587}
588
589fn claims_parameter_supported_default() -> bool {
590 false
591}
592
593fn request_parameter_supported_default() -> bool {
594 false
595}
596
597fn request_uri_parameter_supported_default() -> bool {
598 false
599}
600
601fn require_request_uri_parameter_supported_default() -> bool {
602 false
603}
604
605#[derive(Serialize, Deserialize, Debug)]
606pub struct OidcWebfingerRel {
607 pub rel: String,
608 pub href: String,
609}
610
611#[skip_serializing_none]
614#[derive(Serialize, Deserialize, Debug)]
615pub struct OidcWebfingerResponse {
616 pub subject: String,
617 pub links: Vec<OidcWebfingerRel>,
618}
619
620#[skip_serializing_none]
623#[derive(Serialize, Deserialize, Debug)]
624pub struct OidcDiscoveryResponse {
625 pub issuer: Url,
626 pub authorization_endpoint: Url,
627 pub token_endpoint: Url,
628 pub userinfo_endpoint: Option<Url>,
629 pub jwks_uri: Url,
630 pub registration_endpoint: Option<Url>,
631 pub scopes_supported: Option<Vec<String>>,
632 pub response_types_supported: Vec<ResponseType>,
634 #[serde(default = "response_modes_supported_default")]
636 pub response_modes_supported: Vec<ResponseMode>,
637 #[serde(default = "grant_types_supported_default")]
639 pub grant_types_supported: Vec<GrantType>,
640 pub acr_values_supported: Option<Vec<String>>,
641 pub subject_types_supported: Vec<SubjectType>,
643 pub id_token_signing_alg_values_supported: Vec<IdTokenSignAlg>,
644 pub id_token_encryption_alg_values_supported: Option<Vec<String>>,
645 pub id_token_encryption_enc_values_supported: Option<Vec<String>>,
646 pub userinfo_signing_alg_values_supported: Option<Vec<String>>,
647 pub userinfo_encryption_alg_values_supported: Option<Vec<String>>,
648 pub userinfo_encryption_enc_values_supported: Option<Vec<String>>,
649 pub request_object_signing_alg_values_supported: Option<Vec<String>>,
650 pub request_object_encryption_alg_values_supported: Option<Vec<String>>,
651 pub request_object_encryption_enc_values_supported: Option<Vec<String>>,
652 #[serde(default = "token_endpoint_auth_methods_supported_default")]
654 pub token_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
655 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
656 pub display_values_supported: Option<Vec<DisplayValue>>,
658 #[serde(default = "claim_types_supported_default")]
660 pub claim_types_supported: Vec<ClaimType>,
661 pub claims_supported: Option<Vec<String>>,
662 pub service_documentation: Option<Url>,
663 pub claims_locales_supported: Option<Vec<String>>,
664 pub ui_locales_supported: Option<Vec<String>>,
665 #[serde(default = "claims_parameter_supported_default")]
667 pub claims_parameter_supported: bool,
668
669 pub op_policy_uri: Option<Url>,
670 pub op_tos_uri: Option<Url>,
671
672 #[serde(default = "request_parameter_supported_default")]
674 pub request_parameter_supported: bool,
675 #[serde(default = "request_uri_parameter_supported_default")]
676 pub request_uri_parameter_supported: bool,
677 #[serde(default = "require_request_uri_parameter_supported_default")]
678 pub require_request_uri_registration: bool,
679
680 pub code_challenge_methods_supported: Vec<PkceAlg>,
681
682 pub revocation_endpoint: Option<Url>,
690 pub revocation_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
691
692 pub introspection_endpoint: Option<Url>,
694 pub introspection_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
695 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
696
697 pub device_authorization_endpoint: Option<Url>,
699}
700
701#[skip_serializing_none]
703#[derive(Serialize, Deserialize, Debug)]
704pub struct Oauth2Rfc8414MetadataResponse {
705 pub issuer: Url,
706 pub authorization_endpoint: Url,
707 pub token_endpoint: Url,
708
709 pub jwks_uri: Option<Url>,
710
711 pub registration_endpoint: Option<Url>,
713
714 pub scopes_supported: Option<Vec<String>>,
715
716 pub response_types_supported: Vec<ResponseType>,
718 #[serde(default = "response_modes_supported_default")]
719 pub response_modes_supported: Vec<ResponseMode>,
720 #[serde(default = "grant_types_supported_default")]
721 pub grant_types_supported: Vec<GrantType>,
722
723 #[serde(default = "token_endpoint_auth_methods_supported_default")]
724 pub token_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
725
726 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
727
728 pub service_documentation: Option<Url>,
729 pub ui_locales_supported: Option<Vec<String>>,
730
731 pub op_policy_uri: Option<Url>,
732 pub op_tos_uri: Option<Url>,
733
734 pub revocation_endpoint: Option<Url>,
736 pub revocation_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
737
738 pub introspection_endpoint: Option<Url>,
740 pub introspection_endpoint_auth_methods_supported: Vec<EndpointAuthMethod>,
741 pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<IdTokenSignAlg>>,
742
743 pub code_challenge_methods_supported: Vec<PkceAlg>,
745}
746
747#[skip_serializing_none]
748#[derive(Serialize, Deserialize, Debug, Default)]
749pub struct ErrorResponse {
750 pub error: String,
751 pub error_description: Option<String>,
752 pub error_uri: Option<Url>,
753}
754
755#[derive(Debug, Serialize, Deserialize)]
756pub struct DeviceAuthorizationResponse {
758 device_code: String,
760 user_code: String,
762 verification_uri: Url,
763 verification_uri_complete: Url,
764 expires_in: u64,
765 interval: u64,
766}
767
768impl DeviceAuthorizationResponse {
769 pub fn new(verification_uri: Url, device_code: [u8; 16], user_code: String) -> Self {
770 let mut verification_uri_complete = verification_uri.clone();
771 verification_uri_complete
772 .query_pairs_mut()
773 .append_pair("user_code", &user_code);
774
775 let device_code = STANDARD.encode(device_code);
776
777 Self {
778 verification_uri_complete,
779 device_code,
780 user_code,
781 verification_uri,
782 expires_in: OAUTH2_DEVICE_CODE_EXPIRY_SECONDS,
783 interval: OAUTH2_DEVICE_CODE_INTERVAL_SECONDS,
784 }
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use super::{AccessTokenRequest, GrantTypeReq, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN};
791 use std::collections::BTreeSet;
792 use url::Url;
793
794 #[test]
795 fn test_oauth2_access_token_req() {
796 let atr: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
797 code: "demo code".to_string(),
798 redirect_uri: Url::parse("http://[::1]").unwrap(),
799 code_verifier: None,
800 }
801 .into();
802
803 println!("{:?}", serde_json::to_string(&atr).expect("JSON failure"));
804 }
805
806 #[test]
807 fn test_oauth2_access_token_type_serde() {
808 for testcase in ["bearer", "Bearer", "BeArEr"] {
809 let at: super::AccessTokenType =
810 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
811 assert_eq!(at, super::AccessTokenType::Bearer);
812 }
813
814 for testcase in ["dpop", "dPoP", "DPOP", "DPoP"] {
815 let at: super::AccessTokenType =
816 serde_json::from_str(&format!("\"{testcase}\"")).expect("Failed to parse");
817 assert_eq!(at, super::AccessTokenType::DPoP);
818 }
819
820 {
821 let testcase = "cheese";
822 let at = serde_json::from_str::<super::AccessTokenType>(&format!("\"{testcase}\""));
823 assert!(at.is_err())
824 }
825 }
826
827 #[test]
828 fn test_token_exchange_grant_serialization() {
829 let scopes: BTreeSet<String> = ["groups", "openid"]
830 .into_iter()
831 .map(str::to_string)
832 .collect();
833
834 let atr = AccessTokenRequest {
835 grant_type: GrantTypeReq::TokenExchange {
836 subject_token: "subject".to_string(),
837 subject_token_type: OAUTH2_TOKEN_TYPE_ACCESS_TOKEN.to_string(),
838 requested_token_type: None,
839 audience: Some("test_resource_server".to_string()),
840 resource: None,
841 actor_token: None,
842 actor_token_type: None,
843 scope: Some(scopes.clone()),
844 },
845 client_post_auth: Default::default(),
846 };
847
848 let json = serde_json::to_string(&atr).expect("JSON failure");
849 let de: AccessTokenRequest = serde_json::from_str(&json).expect("Roundtrip failure");
850
851 match de.grant_type {
852 GrantTypeReq::TokenExchange {
853 subject_token,
854 subject_token_type,
855 requested_token_type,
856 audience,
857 actor_token,
858 actor_token_type,
859 scope: descope,
860 ..
861 } => {
862 assert_eq!(subject_token, "subject");
863 assert_eq!(subject_token_type, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN);
864 assert_eq!(requested_token_type, None);
865 assert_eq!(audience.as_deref(), Some("test_resource_server"));
866 assert_eq!(actor_token, None);
867 assert_eq!(actor_token_type, None);
868 assert_eq!(descope, Some(scopes));
869 }
870 _ => panic!("Wrong grant type"),
871 }
872 }
873
874 #[test]
875 fn test_authorisation_request_prompt_single_value() {
876 let qs = "response_type=code\
877 &client_id=test_client\
878 &redirect_uri=http%3A%2F%2Flocalhost\
879 &scope=openid\
880 &prompt=login";
881
882 let req: super::AuthorisationRequest =
883 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
884
885 assert_eq!(req.prompt.len(), 1);
886 assert!(req.prompt.contains(&super::Prompt::Login));
887 }
888
889 #[test]
890 fn test_authorisation_request_prompt_multiple_values() {
891 let qs = "response_type=code\
892 &client_id=test_client\
893 &redirect_uri=http%3A%2F%2Flocalhost\
894 &scope=openid\
895 &prompt=login%20consent";
896
897 let req: super::AuthorisationRequest =
898 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
899
900 assert_eq!(req.prompt.len(), 2);
901 assert!(req.prompt.contains(&super::Prompt::Login));
902 assert!(req.prompt.contains(&super::Prompt::Consent));
903 }
904
905 #[test]
906 fn test_authorisation_request_prompt_none() {
907 let qs = "response_type=code\
908 &client_id=test_client\
909 &redirect_uri=http%3A%2F%2Flocalhost\
910 &scope=openid\
911 &prompt=none";
912
913 let req: super::AuthorisationRequest =
914 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
915
916 assert_eq!(req.prompt.len(), 1);
917 assert!(req.prompt.contains(&super::Prompt::None));
918 }
919
920 #[test]
921 fn test_authorisation_request_prompt_absent() {
922 let qs = "response_type=code\
923 &client_id=test_client\
924 &redirect_uri=http%3A%2F%2Flocalhost\
925 &scope=openid";
926
927 let req: super::AuthorisationRequest =
928 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
929
930 assert!(req.prompt.is_empty());
931 }
932
933 #[test]
934 fn test_authorisation_request_prompt_invalid_value() {
935 let qs = "response_type=code\
936 &client_id=test_client\
937 &redirect_uri=http%3A%2F%2Flocalhost\
938 &scope=openid\
939 &prompt=bogus";
940
941 let req: super::AuthorisationRequest =
942 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
943
944 assert_eq!(req.prompt.len(), 1);
945 assert!(req
946 .prompt
947 .contains(&super::Prompt::Invalid("bogus".to_string())));
948 }
949
950 #[test]
951 fn test_authorisation_request_prompt_select_account() {
952 let qs = "response_type=code\
953 &client_id=test_client\
954 &redirect_uri=http%3A%2F%2Flocalhost\
955 &scope=openid\
956 &prompt=select_account";
957
958 let req: super::AuthorisationRequest =
959 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
960
961 assert_eq!(req.prompt.len(), 1);
962 assert!(req.prompt.contains(&super::Prompt::SelectAccount));
963 }
964
965 #[test]
966 fn test_authorisation_request_ui_locales() {
967 let qs = "response_type=code\
968 &client_id=test_client\
969 &redirect_uri=http%3A%2F%2Flocalhost\
970 &scope=openid\
971 &ui_locales=en-US";
972
973 let req: super::AuthorisationRequest =
974 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
975
976 assert_eq!(req.ui_locales.len(), 1);
977 assert!(req.ui_locales.contains(&"en-US".to_string()));
978
979 let qs = "response_type=code\
980 &client_id=test_client\
981 &redirect_uri=http%3A%2F%2Flocalhost\
982 &scope=openid\
983 &ui_locales=en-US%20fr-FR";
984
985 let req: super::AuthorisationRequest =
986 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
987 assert_eq!(req.ui_locales.len(), 2);
988 assert!(req.ui_locales.contains(&"fr-FR".to_string()));
989
990 let qs = "response_type=code\
991 &client_id=test_client\
992 &redirect_uri=http%3A%2F%2Flocalhost\
993 &scope=openid";
994
995 let req: super::AuthorisationRequest =
996 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
997 assert_eq!(req.ui_locales.len(), 0);
998 assert!(req.ui_locales.is_empty());
999 }
1000
1001 #[test]
1002 fn test_authorisation_request_prompt_all_valid_values() {
1003 let qs = "response_type=code\
1004 &client_id=test_client\
1005 &redirect_uri=http%3A%2F%2Flocalhost\
1006 &scope=openid\
1007 &prompt=login+consent+select_account";
1008
1009 let req: super::AuthorisationRequest =
1010 serde_urlencoded::from_str(qs).expect("Failed to deserialize");
1011
1012 assert_eq!(req.prompt.len(), 3);
1013 assert!(req.prompt.contains(&super::Prompt::Login));
1014 assert!(req.prompt.contains(&super::Prompt::Consent));
1015 assert!(req.prompt.contains(&super::Prompt::SelectAccount));
1016 }
1017}