use std::fmt::Debug;
use std::marker::PhantomData;
use chrono::{DateTime, Utc};
use oauth2::helpers::variant_name;
use oauth2::ClientId;
use serde::Serialize;
use crate::helpers::FilteredFlatten;
use crate::jwt::JsonWebTokenAccess;
use crate::jwt::{JsonWebTokenError, JsonWebTokenJsonPayloadSerde};
use crate::types::helpers::{deserialize_string_or_vec, serde_utc_seconds, serde_utc_seconds_opt};
use crate::types::LocalizedClaim;
use crate::{
AccessToken, AccessTokenHash, AdditionalClaims, AddressClaim, Audience, AudiencesClaim,
AuthenticationContextClass, AuthenticationMethodReference, AuthorizationCode,
AuthorizationCodeHash, ClaimsVerificationError, EndUserBirthday, EndUserEmail,
EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName, EndUserNickname,
EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone, EndUserUsername,
EndUserWebsiteUrl, ExtraTokenFields, GenderClaim, IdTokenVerifier, IssuerClaim, IssuerUrl,
JsonWebKey, JsonWebKeyType, JsonWebKeyUse, JsonWebToken, JsonWebTokenAlgorithm,
JweContentEncryptionAlgorithm, JwsSigningAlgorithm, LanguageTag, Nonce, NonceVerifier,
PrivateSigningKey, SigningError, StandardClaims, SubjectIdentifier,
};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct IdToken<
AC: AdditionalClaims,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
>(
#[serde(bound = "AC: AdditionalClaims")]
JsonWebToken<JE, JS, JT, IdTokenClaims<AC, GC>, JsonWebTokenJsonPayloadSerde>,
);
impl<AC, GC, JE, JS, JT> IdToken<AC, GC, JE, JS, JT>
where
AC: AdditionalClaims,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
pub fn new<JU, K, S>(
claims: IdTokenClaims<AC, GC>,
signing_key: &S,
alg: JS,
access_token: Option<&AccessToken>,
code: Option<&AuthorizationCode>,
) -> Result<Self, JsonWebTokenError>
where
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
S: PrivateSigningKey<JS, JT, JU, K>,
{
let at_hash = access_token
.map(|at| {
AccessTokenHash::from_token(at, &alg).map_err(JsonWebTokenError::SigningError)
})
.transpose()?
.or_else(|| claims.access_token_hash.clone());
let c_hash = code
.map(|c| {
AuthorizationCodeHash::from_code(c, &alg).map_err(JsonWebTokenError::SigningError)
})
.transpose()?
.or_else(|| claims.code_hash.clone());
JsonWebToken::new(
IdTokenClaims {
access_token_hash: at_hash,
code_hash: c_hash,
..claims
},
signing_key,
&alg,
)
.map(Self)
}
pub fn claims<'a, JU, K, N>(
&'a self,
verifier: &IdTokenVerifier<JS, JT, JU, K>,
nonce_verifier: N,
) -> Result<&'a IdTokenClaims<AC, GC>, ClaimsVerificationError>
where
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
N: NonceVerifier,
{
verifier.verified_claims(&self.0, nonce_verifier)
}
pub fn into_claims<JU, K, N>(
self,
verifier: &IdTokenVerifier<JS, JT, JU, K>,
nonce_verifier: N,
) -> Result<IdTokenClaims<AC, GC>, ClaimsVerificationError>
where
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
N: NonceVerifier,
{
verifier.verified_claims_owned(self.0, nonce_verifier)
}
pub fn signing_alg(&self) -> Result<JS, SigningError> {
match self.0.unverified_header().alg {
JsonWebTokenAlgorithm::Signature(ref signing_alg, _) => Ok(signing_alg.clone()),
JsonWebTokenAlgorithm::Encryption(ref other) => Err(SigningError::UnsupportedAlg(
variant_name(other).to_string(),
)),
JsonWebTokenAlgorithm::None => Err(SigningError::UnsupportedAlg("none".to_string())),
}
}
}
impl<AC, GC, JE, JS, JT> ToString for IdToken<AC, GC, JE, JS, JT>
where
AC: AdditionalClaims,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
fn to_string(&self) -> String {
serde_json::to_value(&self)
.expect("ID token serialization failed")
.as_str()
.expect("ID token serializer did not produce a str")
.to_owned()
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct IdTokenClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
#[serde(rename = "iss")]
issuer: IssuerUrl,
#[serde(
default,
rename = "aud",
deserialize_with = "deserialize_string_or_vec"
)]
audiences: Vec<Audience>,
#[serde(rename = "exp", with = "serde_utc_seconds")]
expiration: DateTime<Utc>,
#[serde(rename = "iat", with = "serde_utc_seconds")]
issue_time: DateTime<Utc>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_utc_seconds_opt"
)]
auth_time: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<Nonce>,
#[serde(rename = "acr", skip_serializing_if = "Option::is_none")]
auth_context_ref: Option<AuthenticationContextClass>,
#[serde(rename = "amr", skip_serializing_if = "Option::is_none")]
auth_method_refs: Option<Vec<AuthenticationMethodReference>>,
#[serde(rename = "azp", skip_serializing_if = "Option::is_none")]
authorized_party: Option<ClientId>,
#[serde(rename = "at_hash", skip_serializing_if = "Option::is_none")]
access_token_hash: Option<AccessTokenHash>,
#[serde(rename = "c_hash", skip_serializing_if = "Option::is_none")]
code_hash: Option<AuthorizationCodeHash>,
#[serde(bound = "GC: GenderClaim")]
#[serde(flatten)]
standard_claims: StandardClaims<GC>,
#[serde(bound = "AC: AdditionalClaims")]
#[serde(flatten)]
additional_claims: FilteredFlatten<StandardClaims<GC>, AC>,
}
impl<AC, GC> IdTokenClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
pub fn new(
issuer: IssuerUrl,
audiences: Vec<Audience>,
expiration: DateTime<Utc>,
issue_time: DateTime<Utc>,
standard_claims: StandardClaims<GC>,
additional_claims: AC,
) -> Self {
Self {
issuer,
audiences,
expiration,
issue_time,
auth_time: None,
nonce: None,
auth_context_ref: None,
auth_method_refs: None,
authorized_party: None,
access_token_hash: None,
code_hash: None,
standard_claims,
additional_claims: additional_claims.into(),
}
}
field_getters_setters![
pub self [self] ["claim"] {
set_issuer -> issuer[IssuerUrl] ["iss"],
set_audiences -> audiences[Vec<Audience>] ["aud"],
set_expiration -> expiration[DateTime<Utc>] ["exp"],
set_issue_time -> issue_time[DateTime<Utc>] ["iat"],
set_auth_time -> auth_time[Option<DateTime<Utc>>],
set_nonce -> nonce[Option<Nonce>],
set_auth_context_ref -> auth_context_ref[Option<AuthenticationContextClass>] ["acr"],
set_auth_method_refs -> auth_method_refs[Option<Vec<AuthenticationMethodReference>>] ["amr"],
set_authorized_party -> authorized_party[Option<ClientId>] ["azp"],
set_access_token_hash -> access_token_hash[Option<AccessTokenHash>] ["at_hash"],
set_code_hash -> code_hash[Option<AuthorizationCodeHash>] ["c_hash"],
}
];
pub fn subject(&self) -> &SubjectIdentifier {
&self.standard_claims.sub
}
pub fn set_subject(mut self, subject: SubjectIdentifier) -> Self {
self.standard_claims.sub = subject;
self
}
field_getters_setters![
pub self [self.standard_claims] ["claim"] {
set_name -> name[Option<LocalizedClaim<EndUserName>>],
set_given_name -> given_name[Option<LocalizedClaim<EndUserGivenName>>],
set_family_name ->
family_name[Option<LocalizedClaim<EndUserFamilyName>>],
set_middle_name ->
middle_name[Option<LocalizedClaim<EndUserMiddleName>>],
set_nickname -> nickname[Option<LocalizedClaim<EndUserNickname>>],
set_preferred_username -> preferred_username[Option<EndUserUsername>],
set_profile -> profile[Option<LocalizedClaim<EndUserProfileUrl>>],
set_picture -> picture[Option<LocalizedClaim<EndUserPictureUrl>>],
set_website -> website[Option<LocalizedClaim<EndUserWebsiteUrl>>],
set_email -> email[Option<EndUserEmail>],
set_email_verified -> email_verified[Option<bool>],
set_gender -> gender[Option<GC>],
set_birthday -> birthday[Option<EndUserBirthday>],
set_zoneinfo -> zoneinfo[Option<EndUserTimezone>],
set_locale -> locale[Option<LanguageTag>],
set_phone_number -> phone_number[Option<EndUserPhoneNumber>],
set_phone_number_verified -> phone_number_verified[Option<bool>],
set_address -> address[Option<AddressClaim>],
set_updated_at -> updated_at[Option<DateTime<Utc>>],
}
];
pub fn additional_claims(&self) -> &AC {
self.additional_claims.as_ref()
}
pub fn additional_claims_mut(&mut self) -> &mut AC {
self.additional_claims.as_mut()
}
}
impl<AC, GC> AudiencesClaim for IdTokenClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn audiences(&self) -> Option<&Vec<Audience>> {
Some(IdTokenClaims::audiences(self))
}
}
impl<'a, AC, GC> AudiencesClaim for &'a IdTokenClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn audiences(&self) -> Option<&Vec<Audience>> {
Some(IdTokenClaims::audiences(self))
}
}
impl<AC, GC> IssuerClaim for IdTokenClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn issuer(&self) -> Option<&IssuerUrl> {
Some(IdTokenClaims::issuer(self))
}
}
impl<'a, AC, GC> IssuerClaim for &'a IdTokenClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn issuer(&self) -> Option<&IssuerUrl> {
Some(IdTokenClaims::issuer(self))
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct IdTokenFields<AC, EF, GC, JE, JS, JT>
where
AC: AdditionalClaims,
EF: ExtraTokenFields,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
#[serde(bound = "AC: AdditionalClaims")]
id_token: Option<IdToken<AC, GC, JE, JS, JT>>,
#[serde(bound = "EF: ExtraTokenFields", flatten)]
extra_fields: EF,
#[serde(skip)]
_phantom: PhantomData<JT>,
}
impl<AC, EF, GC, JE, JS, JT> IdTokenFields<AC, EF, GC, JE, JS, JT>
where
AC: AdditionalClaims,
EF: ExtraTokenFields,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
pub fn new(id_token: Option<IdToken<AC, GC, JE, JS, JT>>, extra_fields: EF) -> Self {
Self {
id_token,
extra_fields,
_phantom: PhantomData,
}
}
pub fn id_token(&self) -> Option<&IdToken<AC, GC, JE, JS, JT>> {
self.id_token.as_ref()
}
pub fn extra_fields(&self) -> &EF {
&self.extra_fields
}
}
impl<AC, EF, GC, JE, JS, JT> ExtraTokenFields for IdTokenFields<AC, EF, GC, JE, JS, JT>
where
AC: AdditionalClaims,
EF: ExtraTokenFields,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use chrono::{TimeZone, Utc};
use oauth2::basic::BasicTokenType;
use oauth2::{ClientId, TokenResponse};
use url::Url;
use crate::claims::{AdditionalClaims, EmptyAdditionalClaims, StandardClaims};
use crate::core::{CoreGenderClaim, CoreIdToken, CoreIdTokenClaims, CoreTokenResponse};
use crate::jwt::JsonWebTokenAccess;
use crate::{
AccessTokenHash, AddressClaim, AddressCountry, AddressLocality, AddressPostalCode,
AddressRegion, Audience, AuthenticationContextClass, AuthenticationMethodReference,
AuthorizationCodeHash, EndUserBirthday, EndUserEmail, EndUserFamilyName, EndUserGivenName,
EndUserMiddleName, EndUserName, EndUserNickname, EndUserPhoneNumber, EndUserPictureUrl,
EndUserProfileUrl, EndUserTimezone, EndUserUsername, EndUserWebsiteUrl, FormattedAddress,
IssuerUrl, LanguageTag, Nonce, StreetAddress, SubjectIdentifier,
};
use super::{AudiencesClaim, IdTokenClaims, IssuerClaim};
#[test]
fn test_id_token() {
let id_token_str = "\"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSI\
sImF1ZCI6WyJzNkJoZFJrcXQzIl0sImV4cCI6MTMxMTI4MTk3MCwiaWF0IjoxMzExMjgwOTcwLCJzdWIiOiIyND\
QwMDMyMCIsInRmYV9tZXRob2QiOiJ1MmYifQ.aW52YWxpZF9zaWduYXR1cmU\"";
let id_token =
serde_json::from_str::<CoreIdToken>(id_token_str).expect("failed to deserialize");
let claims = id_token.0.unverified_payload_ref();
assert_eq!(
*claims.issuer().url(),
Url::parse("https://server.example.com").unwrap()
);
assert_eq!(
*claims.audiences(),
vec![Audience::new("s6BhdRkqt3".to_string())]
);
assert_eq!(claims.expiration(), Utc.timestamp(1311281970, 0));
assert_eq!(claims.issue_time(), Utc.timestamp(1311280970, 0));
assert_eq!(
*claims.subject(),
SubjectIdentifier::new("24400320".to_string())
);
assert_eq!(
serde_json::to_string(&id_token).expect("failed to serialize"),
id_token_str
);
}
#[test]
fn test_oauth2_response() {
let response_str = "{\
\"access_token\":\"foobar\",\
\"token_type\":\"bearer\",\
\"id_token\":\"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsImF\
1ZCI6WyJzNkJoZFJrcXQzIl0sImV4cCI6MTMxMTI4MTk3MCwiaWF0IjoxMzExMjgwOTcwLCJzdWIiOiIyNDQwMD\
MyMCIsInRmYV9tZXRob2QiOiJ1MmYifQ.aW52YWxpZF9zaWduYXR1cmU\"\
}";
let response =
serde_json::from_str::<CoreTokenResponse>(response_str).expect("failed to deserialize");
assert_eq!(*response.access_token().secret(), "foobar");
assert_eq!(*response.token_type(), BasicTokenType::Bearer);
let id_token = response.extra_fields().id_token();
let claims = id_token.unwrap().0.unverified_payload_ref();
assert_eq!(
*claims.issuer().url(),
Url::parse("https://server.example.com").unwrap()
);
assert_eq!(
*claims.audiences(),
vec![Audience::new("s6BhdRkqt3".to_string())]
);
assert_eq!(claims.expiration(), Utc.timestamp(1311281970, 0));
assert_eq!(claims.issue_time(), Utc.timestamp(1311280970, 0));
assert_eq!(
*claims.subject(),
SubjectIdentifier::new("24400320".to_string())
);
assert_eq!(
serde_json::to_string(&response).expect("failed to serialize"),
response_str
);
}
#[test]
fn test_minimal_claims_serde() {
let new_claims = CoreIdTokenClaims::new(
IssuerUrl::new("https://server.example.com".to_string()).unwrap(),
vec![Audience::new("s6BhdRkqt3".to_string())],
Utc.timestamp(1311281970, 0),
Utc.timestamp(1311280970, 0),
StandardClaims::new(SubjectIdentifier::new("24400320".to_string())),
EmptyAdditionalClaims {},
);
let expected_serialized_claims = "\
{\
\"iss\":\"https://server.example.com\",\
\"aud\":[\"s6BhdRkqt3\"],\
\"exp\":1311281970,\
\"iat\":1311280970,\
\"sub\":\"24400320\"\
}";
let new_serialized_claims =
serde_json::to_string(&new_claims).expect("failed to serialize");
assert_eq!(new_serialized_claims, expected_serialized_claims);
let claims: CoreIdTokenClaims = serde_json::from_str(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": \"s6BhdRkqt3\",
\"exp\": 1311281970,
\"iat\": 1311280970
}",
)
.expect("failed to deserialize");
assert_eq!(claims, new_claims);
assert_eq!(claims.issuer().url(), new_claims.issuer().url());
assert_eq!(claims.audiences(), new_claims.audiences());
assert_eq!(claims.expiration(), new_claims.expiration());
assert_eq!(claims.issue_time(), new_claims.issue_time());
assert_eq!(claims.auth_time(), None);
assert!(claims.nonce().is_none());
assert_eq!(claims.auth_context_ref(), None);
assert_eq!(claims.auth_method_refs(), None);
assert_eq!(claims.authorized_party(), None);
assert_eq!(claims.access_token_hash(), None);
assert_eq!(claims.code_hash(), None);
assert_eq!(*claims.additional_claims(), EmptyAdditionalClaims {});
assert_eq!(claims.subject(), new_claims.subject());
assert_eq!(claims.name(), None);
assert_eq!(claims.given_name(), None);
assert_eq!(claims.family_name(), None);
assert_eq!(claims.middle_name(), None);
assert_eq!(claims.nickname(), None);
assert_eq!(claims.preferred_username(), None);
assert_eq!(claims.profile(), None);
assert_eq!(claims.picture(), None);
assert_eq!(claims.website(), None);
assert_eq!(claims.email(), None);
assert_eq!(claims.email_verified(), None);
assert_eq!(claims.gender(), None);
assert_eq!(claims.birthday(), None);
assert_eq!(claims.zoneinfo(), None);
assert_eq!(claims.locale(), None);
assert_eq!(claims.phone_number(), None);
assert_eq!(claims.phone_number_verified(), None);
assert_eq!(claims.address(), None);
assert_eq!(claims.updated_at(), None);
let serialized_claims = serde_json::to_string(&claims).expect("failed to serialize");
assert_eq!(serialized_claims, expected_serialized_claims);
let claims_round_trip: CoreIdTokenClaims =
serde_json::from_str(&serialized_claims).expect("failed to deserialize");
assert_eq!(claims, claims_round_trip);
}
#[test]
fn test_complete_claims_serde() {
let claims_json = "{\
\"iss\":\"https://server.example.com\",\
\"aud\":[\"s6BhdRkqt3\"],\
\"exp\":1311281970,\
\"iat\":1311280970,\
\"auth_time\":1311282970,\
\"nonce\":\"Zm9vYmFy\",\
\"acr\":\"urn:mace:incommon:iap:silver\",\
\"amr\":[\"password\",\"totp\"],\
\"azp\":\"dGhpc19jbGllbnQ\",\
\"at_hash\":\"_JPLB-GtkomFJxAOWKHPHQ\",\
\"c_hash\":\"VpTQii5T_8rgwxA-Wtb2Bw\",\
\"sub\":\"24400320\",\
\"name\":\"Homer Simpson\",\
\"name#es\":\"Jomer Simpson\",\
\"given_name\":\"Homer\",\
\"given_name#es\":\"Jomer\",\
\"family_name\":\"Simpson\",\
\"family_name#es\":\"Simpson\",\
\"middle_name\":\"Jay\",\
\"middle_name#es\":\"Jay\",\
\"nickname\":\"Homer\",\
\"nickname#es\":\"Jomer\",\
\"preferred_username\":\"homersimpson\",\
\"profile\":\"https://example.com/profile?id=12345\",\
\"profile#es\":\"https://example.com/profile?id=12345&lang=es\",\
\"picture\":\"https://example.com/avatar?id=12345\",\
\"picture#es\":\"https://example.com/avatar?id=12345&lang=es\",\
\"website\":\"https://homersimpson.me\",\
\"website#es\":\"https://homersimpson.me/?lang=es\",\
\"email\":\"homer@homersimpson.me\",\
\"email_verified\":true,\
\"gender\":\"male\",\
\"birthday\":\"1956-05-12\",\
\"zoneinfo\":\"America/Los_Angeles\",\
\"locale\":\"en-US\",\
\"phone_number\":\"+1 (555) 555-5555\",\
\"phone_number_verified\":false,\
\"address\":{\
\"formatted\":\"1234 Hollywood Blvd., Los Angeles, CA 90210\",\
\"street_address\":\"1234 Hollywood Blvd.\",\
\"locality\":\"Los Angeles\",\
\"region\":\"CA\",\
\"postal_code\":\"90210\",\
\"country\":\"US\"\
},\
\"updated_at\":1311283970\
}";
let new_claims = CoreIdTokenClaims::new(
IssuerUrl::new("https://server.example.com".to_string()).unwrap(),
vec![Audience::new("s6BhdRkqt3".to_string())],
Utc.timestamp(1311281970, 0),
Utc.timestamp(1311280970, 0),
StandardClaims {
sub: SubjectIdentifier::new("24400320".to_string()),
name: Some(
vec![
(None, EndUserName::new("Homer Simpson".to_string())),
(
Some(LanguageTag::new("es".to_string())),
EndUserName::new("Jomer Simpson".to_string()),
),
]
.into_iter()
.collect(),
),
given_name: Some(
vec![
(None, EndUserGivenName::new("Homer".to_string())),
(
Some(LanguageTag::new("es".to_string())),
EndUserGivenName::new("Jomer".to_string()),
),
]
.into_iter()
.collect(),
),
family_name: Some(
vec![
(None, EndUserFamilyName::new("Simpson".to_string())),
(
Some(LanguageTag::new("es".to_string())),
EndUserFamilyName::new("Simpson".to_string()),
),
]
.into_iter()
.collect(),
),
middle_name: Some(
vec![
(None, EndUserMiddleName::new("Jay".to_string())),
(
Some(LanguageTag::new("es".to_string())),
EndUserMiddleName::new("Jay".to_string()),
),
]
.into_iter()
.collect(),
),
nickname: Some(
vec![
(None, EndUserNickname::new("Homer".to_string())),
(
Some(LanguageTag::new("es".to_string())),
EndUserNickname::new("Jomer".to_string()),
),
]
.into_iter()
.collect(),
),
preferred_username: Some(EndUserUsername::new("homersimpson".to_string())),
profile: Some(
vec![
(
None,
EndUserProfileUrl::new(
"https://example.com/profile?id=12345".to_string(),
),
),
(
Some(LanguageTag::new("es".to_string())),
EndUserProfileUrl::new(
"https://example.com/profile?id=12345&lang=es".to_string(),
),
),
]
.into_iter()
.collect(),
),
picture: Some(
vec![
(
None,
EndUserPictureUrl::new(
"https://example.com/avatar?id=12345".to_string(),
),
),
(
Some(LanguageTag::new("es".to_string())),
EndUserPictureUrl::new(
"https://example.com/avatar?id=12345&lang=es".to_string(),
),
),
]
.into_iter()
.collect(),
),
website: Some(
vec![
(
None,
EndUserWebsiteUrl::new("https://homersimpson.me".to_string()),
),
(
Some(LanguageTag::new("es".to_string())),
EndUserWebsiteUrl::new("https://homersimpson.me/?lang=es".to_string()),
),
]
.into_iter()
.collect(),
),
email: Some(EndUserEmail::new("homer@homersimpson.me".to_string())),
email_verified: Some(true),
gender: Some(CoreGenderClaim::new("male".to_string())),
birthday: Some(EndUserBirthday::new("1956-05-12".to_string())),
zoneinfo: Some(EndUserTimezone::new("America/Los_Angeles".to_string())),
locale: Some(LanguageTag::new("en-US".to_string())),
phone_number: Some(EndUserPhoneNumber::new("+1 (555) 555-5555".to_string())),
phone_number_verified: Some(false),
address: Some(AddressClaim {
formatted: Some(FormattedAddress::new(
"1234 Hollywood Blvd., Los Angeles, CA 90210".to_string(),
)),
street_address: Some(StreetAddress::new("1234 Hollywood Blvd.".to_string())),
locality: Some(AddressLocality::new("Los Angeles".to_string())),
region: Some(AddressRegion::new("CA".to_string())),
postal_code: Some(AddressPostalCode::new("90210".to_string())),
country: Some(AddressCountry::new("US".to_string())),
}),
updated_at: Some(Utc.timestamp(1311283970, 0)),
},
EmptyAdditionalClaims {},
)
.set_auth_time(Some(Utc.timestamp(1311282970, 0)))
.set_nonce(Some(Nonce::new("Zm9vYmFy".to_string())))
.set_auth_context_ref(Some(AuthenticationContextClass::new(
"urn:mace:incommon:iap:silver".to_string(),
)))
.set_auth_method_refs(Some(vec![
AuthenticationMethodReference::new("password".to_string()),
AuthenticationMethodReference::new("totp".to_string()),
]))
.set_authorized_party(Some(ClientId::new("dGhpc19jbGllbnQ".to_string())))
.set_access_token_hash(Some(AccessTokenHash::new(
"_JPLB-GtkomFJxAOWKHPHQ".to_string(),
)))
.set_code_hash(Some(AuthorizationCodeHash::new(
"VpTQii5T_8rgwxA-Wtb2Bw".to_string(),
)));
let claims: CoreIdTokenClaims =
serde_json::from_str(claims_json).expect("failed to deserialize");
assert_eq!(claims, new_claims);
assert_eq!(claims.issuer().url(), new_claims.issuer().url());
assert_eq!(claims.audiences(), new_claims.audiences());
assert_eq!(claims.expiration(), new_claims.expiration());
assert_eq!(claims.issue_time(), new_claims.issue_time());
assert_eq!(claims.auth_time(), new_claims.auth_time());
assert_eq!(
claims.nonce().unwrap().secret(),
new_claims.nonce().unwrap().secret()
);
assert_eq!(claims.auth_context_ref(), new_claims.auth_context_ref());
assert_eq!(claims.auth_method_refs(), new_claims.auth_method_refs());
assert_eq!(claims.authorized_party(), new_claims.authorized_party());
assert_eq!(claims.access_token_hash(), new_claims.access_token_hash());
assert_eq!(claims.code_hash(), new_claims.code_hash());
assert_eq!(*claims.additional_claims(), EmptyAdditionalClaims {});
assert_eq!(claims.subject(), new_claims.subject());
assert_eq!(claims.name(), new_claims.name());
assert_eq!(claims.given_name(), new_claims.given_name());
assert_eq!(claims.family_name(), new_claims.family_name());
assert_eq!(claims.middle_name(), new_claims.middle_name());
assert_eq!(claims.nickname(), new_claims.nickname());
assert_eq!(claims.preferred_username(), new_claims.preferred_username());
assert_eq!(claims.preferred_username(), new_claims.preferred_username());
assert_eq!(claims.profile(), new_claims.profile());
assert_eq!(claims.picture(), new_claims.picture());
assert_eq!(claims.website(), new_claims.website());
assert_eq!(claims.email(), new_claims.email());
assert_eq!(claims.email_verified(), new_claims.email_verified());
assert_eq!(claims.gender(), new_claims.gender());
assert_eq!(claims.birthday(), new_claims.birthday());
assert_eq!(claims.zoneinfo(), new_claims.zoneinfo());
assert_eq!(claims.locale(), new_claims.locale());
assert_eq!(claims.phone_number(), new_claims.phone_number(),);
assert_eq!(
claims.phone_number_verified(),
new_claims.phone_number_verified()
);
assert_eq!(claims.address(), new_claims.address());
assert_eq!(claims.updated_at(), new_claims.updated_at());
let serialized_claims = serde_json::to_string(&claims).expect("failed to serialize");
let claims_round_trip: CoreIdTokenClaims =
serde_json::from_str(&serialized_claims).expect("failed to deserialize");
assert_eq!(claims, claims_round_trip);
let serialized_new_claims =
serde_json::to_string(&new_claims).expect("failed to serialize");
assert_eq!(serialized_new_claims, claims_json);
}
#[test]
fn test_unknown_claims_serde() {
let expected_serialized_claims = "{\
\"iss\":\"https://server.example.com\",\
\"aud\":[\"s6BhdRkqt3\"],\
\"exp\":1311281970,\
\"iat\":1311280970,\
\"sub\":\"24400320\"\
}";
let claims: CoreIdTokenClaims = serde_json::from_str(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": \"s6BhdRkqt3\",
\"exp\": 1311281970,
\"iat\": 1311280970,
\"some_other_field\":\"some_other_value\"\
}",
)
.expect("failed to deserialize");
let serialized_claims = serde_json::to_string(&claims).expect("failed to serialize");
assert_eq!(serialized_claims, expected_serialized_claims);
let claims_round_trip: CoreIdTokenClaims =
serde_json::from_str(&serialized_claims).expect("failed to deserialize");
assert_eq!(claims, claims_round_trip);
}
#[test]
fn test_audience() {
let single_aud_str_claims = serde_json::from_str::<CoreIdTokenClaims>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": \"s6BhdRkqt3\",
\"exp\": 1311281970,
\"iat\": 1311280970
}",
)
.expect("failed to deserialize");
assert_eq!(
*single_aud_str_claims.audiences(),
vec![Audience::new("s6BhdRkqt3".to_string())],
);
assert_eq!(
serde_json::to_string(&single_aud_str_claims).expect("failed to serialize"),
"{\
\"iss\":\"https://server.example.com\",\
\"aud\":[\"s6BhdRkqt3\"],\
\"exp\":1311281970,\
\"iat\":1311280970,\
\"sub\":\"24400320\"\
}",
);
let single_aud_vec_claims = serde_json::from_str::<CoreIdTokenClaims>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\"],
\"exp\": 1311281970,
\"iat\": 1311280970
}",
)
.expect("failed to deserialize");
assert_eq!(
*single_aud_vec_claims.audiences(),
vec![Audience::new("s6BhdRkqt3".to_string())],
);
assert_eq!(
serde_json::to_string(&single_aud_vec_claims).expect("failed to serialize"),
"{\
\"iss\":\"https://server.example.com\",\
\"aud\":[\"s6BhdRkqt3\"],\
\"exp\":1311281970,\
\"iat\":1311280970,\
\"sub\":\"24400320\"\
}",
);
let multi_aud_claims = serde_json::from_str::<CoreIdTokenClaims>(
"{\
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\", \"aud2\"],
\"exp\": 1311281970,
\"iat\": 1311280970
}",
)
.expect("failed to deserialize");
assert_eq!(
*multi_aud_claims.audiences(),
vec![
Audience::new("s6BhdRkqt3".to_string()),
Audience::new("aud2".to_string())
],
);
assert_eq!(
serde_json::to_string(&multi_aud_claims).expect("failed to serialize"),
"{\
\"iss\":\"https://server.example.com\",\
\"aud\":[\"s6BhdRkqt3\",\"aud2\"],\
\"exp\":1311281970,\
\"iat\":1311280970,\
\"sub\":\"24400320\"\
}",
);
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
struct TestClaims {
pub tfa_method: String,
}
impl AdditionalClaims for TestClaims {}
#[test]
fn test_additional_claims() {
let claims = serde_json::from_str::<IdTokenClaims<TestClaims, CoreGenderClaim>>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\"],
\"exp\": 1311281970,
\"iat\": 1311280970,
\"tfa_method\": \"u2f\"
}",
)
.expect("failed to deserialize");
assert_eq!(claims.additional_claims().tfa_method, "u2f");
assert_eq!(
serde_json::to_string(&claims).expect("failed to serialize"),
"{\
\"iss\":\"https://server.example.com\",\
\"aud\":[\"s6BhdRkqt3\"],\
\"exp\":1311281970,\
\"iat\":1311280970,\
\"sub\":\"24400320\",\
\"tfa_method\":\"u2f\"\
}",
);
serde_json::from_str::<IdTokenClaims<TestClaims, CoreGenderClaim>>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\"],
\"exp\": 1311281970,
\"iat\": 1311280970
}",
)
.expect_err("missing claim should fail to deserialize");
}
#[derive(Debug, Deserialize, Serialize)]
struct AllOtherClaims(HashMap<String, serde_json::Value>);
impl AdditionalClaims for AllOtherClaims {}
#[test]
fn test_catch_all_additional_claims() {
let claims = serde_json::from_str::<IdTokenClaims<AllOtherClaims, CoreGenderClaim>>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\"],
\"exp\": 1311281970,
\"iat\": 1311280970,
\"tfa_method\": \"u2f\",
\"updated_at\": 1000
}",
)
.expect("failed to deserialize");
assert_eq!(claims.additional_claims().0.len(), 1);
assert_eq!(claims.additional_claims().0["tfa_method"], "u2f");
}
#[test]
fn test_audiences_claim() {
let claims = serde_json::from_str::<CoreIdTokenClaims>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": \"s6BhdRkqt3\",
\"exp\": 1311281970,
\"iat\": 1311280970
}",
)
.expect("failed to deserialize");
fn verify_audiences<A: AudiencesClaim>(audiences_claim: &A) {
assert_eq!(
(*audiences_claim).audiences(),
Some(&vec![Audience::new("s6BhdRkqt3".to_string())]),
)
}
verify_audiences(&claims);
verify_audiences(&&claims);
}
#[test]
fn test_issuer_claim() {
let claims = serde_json::from_str::<CoreIdTokenClaims>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": \"s6BhdRkqt3\",
\"exp\": 1311281970,
\"iat\": 1311280970
}",
)
.expect("failed to deserialize");
fn verify_issuer<I: IssuerClaim>(issuer_claim: &I) {
assert_eq!(
(*issuer_claim).issuer(),
Some(&IssuerUrl::new("https://server.example.com".to_string()).unwrap()),
)
}
verify_issuer(&claims);
verify_issuer(&&claims);
}
}