RPM build fix (reverted CI changes which will need to be un-reverted or made conditional) and vendor Rust dependencies to make builds much faster in any CI system.

This commit is contained in:
Adam Ierymenko
2022-06-08 07:32:16 -04:00
parent 373ca30269
commit d5ca4e5f52
12611 changed files with 2898014 additions and 284 deletions

View File

@@ -0,0 +1,311 @@
use std::fmt::{Debug, Formatter, Result as FormatterResult};
use std::marker::PhantomData;
use std::str;
use chrono::{DateTime, Utc};
use serde::de::{Deserialize, DeserializeOwned, Deserializer, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use crate::helpers::FlattenFilter;
use crate::types::helpers::{split_language_tag_key, timestamp_to_utc, utc_to_seconds};
use crate::types::{LocalizedClaim, Timestamp};
use crate::{
AddressCountry, AddressLocality, AddressPostalCode, AddressRegion, EndUserBirthday,
EndUserEmail, EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName,
EndUserNickname, EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl, EndUserTimezone,
EndUserUsername, EndUserWebsiteUrl, FormattedAddress, LanguageTag, StreetAddress,
SubjectIdentifier,
};
///
/// Additional claims beyond the set of Standard Claims defined by OpenID Connect Core.
///
pub trait AdditionalClaims: Debug + DeserializeOwned + Serialize + 'static {}
///
/// No additional claims.
///
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
// In order to support serde flatten, this must be an empty struct rather than an empty
// tuple struct.
pub struct EmptyAdditionalClaims {}
impl AdditionalClaims for EmptyAdditionalClaims {}
///
/// Address claims.
///
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct AddressClaim {
///
/// Full mailing address, formatted for display or use on a mailing label.
///
/// This field MAY contain multiple lines, separated by newlines. Newlines can be represented
/// either as a carriage return/line feed pair (`\r\n`) or as a single line feed character
/// (`\n`).
///
#[serde(skip_serializing_if = "Option::is_none")]
pub formatted: Option<FormattedAddress>,
///
/// Full street address component, which MAY include house number, street name, Post Office Box,
/// and multi-line extended street address information.
///
/// This field MAY contain multiple lines, separated by newlines. Newlines can be represented
/// either as a carriage return/line feed pair (`\r\n`) or as a single line feed character
/// (`\n`).
///
#[serde(skip_serializing_if = "Option::is_none")]
pub street_address: Option<StreetAddress>,
///
/// City or locality component.
///
#[serde(skip_serializing_if = "Option::is_none")]
pub locality: Option<AddressLocality>,
///
/// State, province, prefecture, or region component.
///
#[serde(skip_serializing_if = "Option::is_none")]
pub region: Option<AddressRegion>,
///
/// Zip code or postal code component.
///
#[serde(skip_serializing_if = "Option::is_none")]
pub postal_code: Option<AddressPostalCode>,
///
/// Country name component.
///
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<AddressCountry>,
}
///
/// Gender claim.
///
pub trait GenderClaim: Clone + Debug + DeserializeOwned + Serialize + 'static {}
///
/// Standard Claims defined by OpenID Connect Core.
///
#[derive(Clone, Debug, PartialEq)]
pub struct StandardClaims<GC>
where
GC: GenderClaim,
{
pub(crate) sub: SubjectIdentifier,
pub(crate) name: Option<LocalizedClaim<EndUserName>>,
pub(crate) given_name: Option<LocalizedClaim<EndUserGivenName>>,
pub(crate) family_name: Option<LocalizedClaim<EndUserFamilyName>>,
pub(crate) middle_name: Option<LocalizedClaim<EndUserMiddleName>>,
pub(crate) nickname: Option<LocalizedClaim<EndUserNickname>>,
pub(crate) preferred_username: Option<EndUserUsername>,
pub(crate) profile: Option<LocalizedClaim<EndUserProfileUrl>>,
pub(crate) picture: Option<LocalizedClaim<EndUserPictureUrl>>,
pub(crate) website: Option<LocalizedClaim<EndUserWebsiteUrl>>,
pub(crate) email: Option<EndUserEmail>,
pub(crate) email_verified: Option<bool>,
pub(crate) gender: Option<GC>,
pub(crate) birthday: Option<EndUserBirthday>,
pub(crate) zoneinfo: Option<EndUserTimezone>,
pub(crate) locale: Option<LanguageTag>,
pub(crate) phone_number: Option<EndUserPhoneNumber>,
pub(crate) phone_number_verified: Option<bool>,
pub(crate) address: Option<AddressClaim>,
pub(crate) updated_at: Option<DateTime<Utc>>,
}
impl<GC> StandardClaims<GC>
where
GC: GenderClaim,
{
///
/// Initializes a set of Standard Claims.
///
/// The Subject (`sub`) claim is the only required Standard Claim.
///
pub fn new(subject: SubjectIdentifier) -> Self {
Self {
sub: subject,
name: None,
given_name: None,
family_name: None,
middle_name: None,
nickname: None,
preferred_username: None,
profile: None,
picture: None,
website: None,
email: None,
email_verified: None,
gender: None,
birthday: None,
zoneinfo: None,
locale: None,
phone_number: None,
phone_number_verified: None,
address: None,
updated_at: None,
}
}
///
/// Returns the Subject (`sub`) claim.
///
pub fn subject(&self) -> &SubjectIdentifier {
&self.sub
}
///
/// Sets the Subject (`sub`) claim.
///
pub fn set_subject(mut self, subject: SubjectIdentifier) -> Self {
self.sub = subject;
self
}
field_getters_setters![
pub self [self] ["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>>],
}
];
}
impl<GC> FlattenFilter for StandardClaims<GC>
where
GC: GenderClaim,
{
// When another struct (i.e., additional claims) is co-flattened with this one, only include
// fields in that other struct which are not part of this struct.
fn should_include(field_name: &str) -> bool {
!matches!(
split_language_tag_key(field_name),
("sub", None)
| ("name", _)
| ("given_name", _)
| ("family_name", _)
| ("middle_name", _)
| ("nickname", _)
| ("preferred_username", None)
| ("profile", _)
| ("picture", _)
| ("website", _)
| ("email", None)
| ("email_verified", None)
| ("gender", None)
| ("birthday", None)
| ("zoneinfo", None)
| ("locale", None)
| ("phone_number", None)
| ("phone_number_verified", None)
| ("address", None)
| ("updated_at", None)
)
}
}
impl<'de, GC> Deserialize<'de> for StandardClaims<GC>
where
GC: GenderClaim,
{
///
/// Special deserializer that supports [RFC 5646](https://tools.ietf.org/html/rfc5646) language
/// tags associated with human-readable client metadata fields.
///
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ClaimsVisitor<GC: GenderClaim>(PhantomData<GC>);
impl<'de, GC> Visitor<'de> for ClaimsVisitor<GC>
where
GC: GenderClaim,
{
type Value = StandardClaims<GC>;
fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {
formatter.write_str("struct StandardClaims")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
deserialize_fields! {
map {
[sub]
[LanguageTag(name)]
[LanguageTag(given_name)]
[LanguageTag(family_name)]
[LanguageTag(middle_name)]
[LanguageTag(nickname)]
[Option(preferred_username)]
[LanguageTag(profile)]
[LanguageTag(picture)]
[LanguageTag(website)]
[Option(email)]
[Option(email_verified)]
[Option(gender)]
[Option(birthday)]
[Option(zoneinfo)]
[Option(locale)]
[Option(phone_number)]
[Option(phone_number_verified)]
[Option(address)]
[Option(DateTime(Seconds(updated_at)))]
}
}
}
}
deserializer.deserialize_map(ClaimsVisitor(PhantomData))
}
}
impl<GC> Serialize for StandardClaims<GC>
where
GC: GenderClaim,
{
#[allow(clippy::cognitive_complexity)]
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
where
SE: Serializer,
{
serialize_fields! {
self -> serializer {
[sub]
[LanguageTag(name)]
[LanguageTag(given_name)]
[LanguageTag(family_name)]
[LanguageTag(middle_name)]
[LanguageTag(nickname)]
[Option(preferred_username)]
[LanguageTag(profile)]
[LanguageTag(picture)]
[LanguageTag(website)]
[Option(email)]
[Option(email_verified)]
[Option(gender)]
[Option(birthday)]
[Option(zoneinfo)]
[Option(locale)]
[Option(phone_number)]
[Option(phone_number_verified)]
[Option(address)]
[Option(DateTime(Seconds(updated_at)))]
}
}
}
}

View File

@@ -0,0 +1,197 @@
use num_bigint::{BigInt, Sign};
use ring::hmac;
use ring::rand::SecureRandom;
use ring::signature as ring_signature;
use crate::types::Base64UrlEncodedBytes;
use crate::{JsonWebKey, SignatureVerificationError, SigningError};
use super::{jwk::CoreJsonCurveType, CoreJsonWebKey, CoreJsonWebKeyType};
use std::ops::Deref;
pub fn sign_hmac(key: &[u8], hmac_alg: hmac::Algorithm, msg: &[u8]) -> hmac::Tag {
let signing_key = hmac::Key::new(hmac_alg, key);
hmac::sign(&signing_key, msg)
}
pub fn verify_hmac(
key: &CoreJsonWebKey,
hmac_alg: hmac::Algorithm,
msg: &[u8],
signature: &[u8],
) -> Result<(), SignatureVerificationError> {
let k = key.k.as_ref().ok_or_else(|| {
SignatureVerificationError::InvalidKey("Symmetric key `k` is missing".to_string())
})?;
let verification_key = hmac::Key::new(hmac_alg, k);
hmac::verify(&verification_key, msg, signature)
.map_err(|_| SignatureVerificationError::CryptoError("bad HMAC".to_string()))
}
pub fn sign_rsa(
key: &ring_signature::RsaKeyPair,
padding_alg: &'static dyn ring_signature::RsaEncoding,
rng: &dyn SecureRandom,
msg: &[u8],
) -> Result<Vec<u8>, SigningError> {
let sig_len = key.public_modulus_len();
let mut sig = vec![0; sig_len];
key.sign(padding_alg, rng, msg, &mut sig)
.map_err(|_| SigningError::CryptoError)?;
Ok(sig)
}
fn rsa_public_key(
key: &CoreJsonWebKey,
) -> Result<(&Base64UrlEncodedBytes, &Base64UrlEncodedBytes), String> {
if *key.key_type() != CoreJsonWebKeyType::RSA {
Err("RSA key required".to_string())
} else {
let n = key
.n
.as_ref()
.ok_or_else(|| "RSA modulus `n` is missing".to_string())?;
let e = key
.e
.as_ref()
.ok_or_else(|| "RSA exponent `e` is missing".to_string())?;
Ok((n, e))
}
}
fn ec_public_key(
key: &CoreJsonWebKey,
) -> Result<
(
&Base64UrlEncodedBytes,
&Base64UrlEncodedBytes,
&CoreJsonCurveType,
),
String,
> {
if *key.key_type() != CoreJsonWebKeyType::EllipticCurve {
Err("EC key required".to_string())
} else {
let x = key
.x
.as_ref()
.ok_or_else(|| "EC `x` part is missing".to_string())?;
let y = key
.y
.as_ref()
.ok_or_else(|| "EC `y` part is missing".to_string())?;
let crv = key
.crv
.as_ref()
.ok_or_else(|| "EC `crv` part is missing".to_string())?;
Ok((x, y, crv))
}
}
pub fn verify_rsa_signature(
key: &CoreJsonWebKey,
params: &ring_signature::RsaParameters,
msg: &[u8],
signature: &[u8],
) -> Result<(), SignatureVerificationError> {
let (n, e) = rsa_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;
// let's n and e as a big integers to prevent issues with leading zeros
// according to https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1.1
// `n` is alwasy unsigned (hence has sign plus)
let n_bigint = BigInt::from_bytes_be(Sign::Plus, n.deref());
let e_bigint = BigInt::from_bytes_be(Sign::Plus, e.deref());
let public_key = ring_signature::RsaPublicKeyComponents {
n: &n_bigint.to_bytes_be().1,
e: &e_bigint.to_bytes_be().1,
};
public_key
.verify(params, msg, signature)
.map_err(|_| SignatureVerificationError::CryptoError("bad signature".to_string()))
}
/// According to RFC5480, Section-2.2 implementations of Elliptic Curve Cryptography MUST support the uncompressed form.
/// The first octet of the octet string indicates whether the uncompressed or compressed form is used. For the uncompressed
/// form, the first octet has to be 0x04.
/// According to https://briansmith.org/rustdoc/ring/signature/index.html#ecdsa__fixed-details-fixed-length-pkcs11-style-ecdsa-signatures,
/// to recover the X and Y coordinates from an octet string, the Octet-String-To-Elliptic-Curve-Point Conversion
/// is used (Section 2.3.4 of https://www.secg.org/sec1-v2.pdf).
pub fn verify_ec_signature(
key: &CoreJsonWebKey,
params: &'static ring_signature::EcdsaVerificationAlgorithm,
msg: &[u8],
signature: &[u8],
) -> Result<(), SignatureVerificationError> {
let (x, y, crv) = ec_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;
if *crv == CoreJsonCurveType::P521 {
return Err(SignatureVerificationError::UnsupportedAlg(
"P521".to_string(),
));
}
let mut pk = vec![0x04];
pk.extend(x.deref());
pk.extend(y.deref());
let public_key = ring_signature::UnparsedPublicKey::new(params, pk);
public_key
.verify(msg, signature)
.map_err(|_| SignatureVerificationError::CryptoError("EC Signature was wrong".to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Deref;
use crate::{
core::{crypto::rsa_public_key, CoreJsonWebKey},
SignatureVerificationError,
};
#[test]
fn test_leading_zeros_are_parsed_correctly() {
// The message we signed
let msg = "THIS IS A SIGNATURE TEST";
let signature = base64::decode_config("bg0ohqKwYHAiODeG6qkJ-6IhodN7LGPxAh4hbWeIoBdSXrXMt8Ft8U0BV7vANPvF56h20XB9C0021x2kt7iAbMgPNcZ7LCuXMPPq04DrBpMHafH5BXBwnyDKJKrzDm5sfr6OgEkcxSLHaSJ6gTWQ3waPt6_SeH2-Fi74rg13MHyX-0iqz7bZveoBbGIs5yQCwvXgrDS9zW5LUwUHozHfE6FuSi_Z92ioXeu7FHHDg1KFfg3hs8ZLx4wAX15Vw2GCQOzvyNdbItxXRLnrN1NPqxFquVNo5RGlx6ihR1Jfe7y_n0NSR2q2TuU4cIwR0LRwEaANy5SDqtleQPrTEn8nGQ", base64::URL_SAFE_NO_PAD).unwrap();
// RSA pub key with leading 0
let key : CoreJsonWebKey = serde_json::from_value(serde_json::json!(
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "TEST_KEY_ID",
"alg": "RS256",
"n": "AN0M6Y760b9Ok2PxDOps1TgSmiOaR9mLIfUHtZ_o-6JypOckGcl1CxrteyokOb3WyDsfIAN9fFNrycv5YoLKO7sh0IcfzNEXFgzK84HTBcGuqhN8NV98Z6N9EryUrgJYsJeVoPYm0MzkDe4NyWHhnq-9OyNCQzVELH0NhhViQqRyM92OPrJcQlk8s3ZvcgRmkd-rEtRua8SbS3GEvfvgweVy5-qcJCGoziKfx-IteMOm6yKoHvqisKb91N-qw_kSS4YQUx-DZVDo2g24F7VIbcYzJGUOU674HUF1j-wJyXzG3VV8lAXD8hABs5Lh87gr8_hIZD5gbYBJRObJk9XZbfk"
}
)).unwrap();
// Old way of verifying the jwt, take the modulus directly form the JWK
let (n, e) = rsa_public_key(&key)
.map_err(SignatureVerificationError::InvalidKey)
.unwrap();
let public_key = ring_signature::RsaPublicKeyComponents {
n: n.deref(),
e: e.deref(),
};
// This fails, since ring expects the keys to have no leading zeros
assert! {
public_key
.verify(
&ring_signature::RSA_PKCS1_2048_8192_SHA256,
msg.as_bytes(),
&signature,
).is_err()
};
// This should succeed as the function uses big-integers to actually harmonize parsing
assert! {
verify_rsa_signature(
&key,
&ring_signature::RSA_PKCS1_2048_8192_SHA256,
msg.as_bytes(),
&signature,
).is_ok()
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
use super::CoreGrantType;
#[test]
fn test_grant_type_serialize() {
let serialized_implicit = serde_json::to_string(&CoreGrantType::Implicit).unwrap();
assert_eq!("\"implicit\"", serialized_implicit);
assert_eq!(
CoreGrantType::Implicit,
serde_json::from_str::<CoreGrantType>(&serialized_implicit).unwrap()
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
use serde::de::value::MapDeserializer;
use serde::de::{DeserializeOwned, Deserializer, MapAccess, Visitor};
use serde::{Deserialize, Serialize};
use serde_value::{Value, ValueDeserializer};
use std::cmp::PartialEq;
use std::fmt::{Debug, Formatter, Result as FormatterResult};
use std::marker::PhantomData;
pub(crate) trait FlattenFilter {
fn should_include(field_name: &str) -> bool;
}
/// Helper container for filtering map keys out of serde(flatten). This is needed because
/// [`crate::StandardClaims`] doesn't have a fixed set of field names due to its support for
/// localized claims. Consequently, serde by default passes all of the claims to the deserializer
/// for `AC` (additional claims), leading to duplicate claims. [`FilteredFlatten`] is used for
/// eliminating the duplicate claims.
#[derive(Serialize)]
pub(crate) struct FilteredFlatten<F, T>
where
F: FlattenFilter,
T: DeserializeOwned + Serialize,
{
// We include another level of flattening here because the derived flatten
// ([`serde::private::de::FlatMapDeserializer`]) seems to support a wider set of types
// (e.g., various forms of enum tagging) than [`serde_value::ValueDeserializer`].
#[serde(flatten)]
inner: Flatten<T>,
#[serde(skip)]
_phantom: PhantomData<F>,
}
impl<F, T> From<T> for FilteredFlatten<F, T>
where
F: FlattenFilter,
T: DeserializeOwned + Serialize,
{
fn from(value: T) -> Self {
Self {
inner: Flatten { inner: value },
_phantom: PhantomData,
}
}
}
impl<F, T> AsRef<T> for FilteredFlatten<F, T>
where
F: FlattenFilter,
T: DeserializeOwned + Serialize,
{
fn as_ref(&self) -> &T {
self.inner.as_ref()
}
}
impl<F, T> AsMut<T> for FilteredFlatten<F, T>
where
F: FlattenFilter,
T: DeserializeOwned + Serialize,
{
fn as_mut(&mut self) -> &mut T {
self.inner.as_mut()
}
}
impl<F, T> PartialEq for FilteredFlatten<F, T>
where
F: FlattenFilter,
T: DeserializeOwned + PartialEq + Serialize,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<F, T> Clone for FilteredFlatten<F, T>
where
F: FlattenFilter,
T: Clone + DeserializeOwned + Serialize,
{
fn clone(&self) -> Self {
Self {
inner: Flatten {
inner: self.inner.inner.clone(),
},
_phantom: PhantomData,
}
}
}
impl<F, T> Debug for FilteredFlatten<F, T>
where
F: FlattenFilter,
T: Debug + DeserializeOwned + Serialize,
{
// Transparent Debug since we don't care about this struct.
fn fmt(&self, f: &mut Formatter) -> FormatterResult {
Debug::fmt(&self.inner, f)
}
}
impl<'de, F, T> Deserialize<'de> for FilteredFlatten<F, T>
where
F: FlattenFilter,
T: DeserializeOwned + Serialize,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MapVisitor<F: FlattenFilter, T: DeserializeOwned + Serialize>(PhantomData<(F, T)>);
impl<'de, F, T> Visitor<'de> for MapVisitor<F, T>
where
F: FlattenFilter,
T: DeserializeOwned + Serialize,
{
type Value = Flatten<T>;
fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {
formatter.write_str("map type T")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut entries = Vec::<(Value, Value)>::new();
// JSON only supports String keys, and we really only need to support JSON input.
while let Some(key) = map.next_key::<Value>()? {
let key_str = String::deserialize(ValueDeserializer::new(key.clone()))?;
if F::should_include(&key_str) {
entries.push((key, map.next_value()?));
}
}
Deserialize::deserialize(MapDeserializer::new(entries.into_iter()))
.map_err(serde_value::DeserializerError::into_error)
}
}
Ok(FilteredFlatten {
inner: deserializer.deserialize_map(MapVisitor(PhantomData::<(F, T)>))?,
_phantom: PhantomData,
})
}
}
#[derive(Deserialize, Serialize)]
struct Flatten<T>
where
T: DeserializeOwned + Serialize,
{
#[serde(flatten, bound = "T: DeserializeOwned + Serialize")]
inner: T,
}
impl<T> AsRef<T> for Flatten<T>
where
T: DeserializeOwned + Serialize,
{
fn as_ref(&self) -> &T {
&self.inner
}
}
impl<T> AsMut<T> for Flatten<T>
where
T: DeserializeOwned + Serialize,
{
fn as_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T> PartialEq for Flatten<T>
where
T: DeserializeOwned + PartialEq + Serialize,
{
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<T> Debug for Flatten<T>
where
T: Debug + DeserializeOwned + Serialize,
{
// Transparent Debug since we don't care about this struct.
fn fmt(&self, f: &mut Formatter) -> FormatterResult {
Debug::fmt(&self.inner, f)
}
}

View File

@@ -0,0 +1,50 @@
use http::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
use oauth2::AccessToken;
pub const MIME_TYPE_JSON: &str = "application/json";
pub const MIME_TYPE_JWKS: &str = "application/jwk-set+json";
pub const MIME_TYPE_JWT: &str = "application/jwt";
pub const BEARER: &str = "Bearer";
// The [essence](https://mimesniff.spec.whatwg.org/#mime-type-essence) is the <type>/<subtype>
// representation.
pub fn content_type_has_essence(content_type: &HeaderValue, expected_essence: &str) -> bool {
#[allow(clippy::or_fun_call)]
content_type
.to_str()
.ok()
.filter(|ct| {
ct[..ct.find(';').unwrap_or(ct.len())].to_lowercase() == expected_essence.to_lowercase()
})
.is_some()
}
pub fn check_content_type(headers: &HeaderMap, expected_content_type: &str) -> Result<(), String> {
headers
.get(CONTENT_TYPE)
.map_or(Ok(()), |content_type|
// Section 3.1.1.1 of RFC 7231 indicates that media types are case insensitive and
// may be followed by optional whitespace and/or a parameter (e.g., charset).
// See https://tools.ietf.org/html/rfc7231#section-3.1.1.1.
if !content_type_has_essence(content_type, expected_content_type) {
Err(
format!(
"Unexpected response Content-Type: {:?}, should be `{}`",
content_type,
expected_content_type
)
)
} else {
Ok(())
}
)
}
pub fn auth_bearer(access_token: &AccessToken) -> (HeaderName, HeaderValue) {
(
AUTHORIZATION,
HeaderValue::from_str(&format!("{} {}", BEARER, access_token.secret()))
.expect("invalid access token"),
)
}

File diff suppressed because it is too large Load Diff

837
zeroidc/vendor/openidconnect/src/jwt.rs vendored Normal file
View File

@@ -0,0 +1,837 @@
use std::fmt::{Debug, Formatter, Result as FormatterResult};
use std::marker::PhantomData;
use std::ops::Deref;
use std::str;
use serde::de::{DeserializeOwned, Error as _, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
use super::{
JsonWebKey, JsonWebKeyId, JsonWebKeyType, JsonWebKeyUse, JweContentEncryptionAlgorithm,
JwsSigningAlgorithm, PrivateSigningKey, SignatureVerificationError, SigningError,
};
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
JsonWebTokenContentType(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
JsonWebTokenType(String)
];
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum JsonWebTokenAlgorithm<JE, JS, JT>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
Encryption(JE),
// This is ugly, but we don't expose this module via the public API, so it's fine.
Signature(JS, PhantomData<JT>),
///
/// No digital signature or MAC performed.
///
/// # Security Warning
///
/// This algorithm provides no security over the integrity of the JSON Web Token. Clients
/// should be careful not to rely on unsigned JWT's for security purposes. See
/// [Critical vulnerabilities in JSON Web Token libraries](
/// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/) for
/// further discussion.
///
None,
}
impl<'de, JE, JS, JT> Deserialize<'de> for JsonWebTokenAlgorithm<JE, JS, JT>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: serde_json::Value = Deserialize::deserialize(deserializer)?;
// TODO: get rid of this clone() (see below)
let s: String = serde_json::from_value(value.clone()).map_err(D::Error::custom)?;
// NB: These comparisons are case sensitive. Section 4.1.1 of RFC 7515 states: "The "alg"
// value is a case-sensitive ASCII string containing a StringOrURI value."
if s == "none" {
Ok(JsonWebTokenAlgorithm::None)
// TODO: Figure out a way to deserialize the enums without giving up ownership
} else if let Ok(val) = serde_json::from_value::<JE>(value.clone()) {
Ok(JsonWebTokenAlgorithm::Encryption(val))
} else if let Ok(val) = serde_json::from_value::<JS>(value) {
Ok(JsonWebTokenAlgorithm::Signature(val, PhantomData))
} else {
Err(D::Error::custom(format!(
"unrecognized JSON Web Algorithm `{}`",
s
)))
}
}
}
impl<JE, JS, JT> Serialize for JsonWebTokenAlgorithm<JE, JS, JT>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
where
SE: Serializer,
{
match self {
JsonWebTokenAlgorithm::Encryption(ref enc) => enc.serialize(serializer),
JsonWebTokenAlgorithm::Signature(ref sig, _) => sig.serialize(serializer),
JsonWebTokenAlgorithm::None => serializer.serialize_str("none"),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct JsonWebTokenHeader<JE, JS, JT>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
#[serde(
bound = "JE: JweContentEncryptionAlgorithm<JT>, JS: JwsSigningAlgorithm<JT>, JT: JsonWebKeyType"
)]
pub alg: JsonWebTokenAlgorithm<JE, JS, JT>,
// Additional critical header parameters that must be understood by this implementation. Since
// we don't understand any such extensions, we reject any JWT with this value present (the
// spec specifically prohibits including public (standard) headers in this field).
// See https://tools.ietf.org/html/rfc7515#section-4.1.11.
#[serde(skip_serializing_if = "Option::is_none")]
pub crit: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cty: Option<JsonWebTokenContentType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub kid: Option<JsonWebKeyId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub typ: Option<JsonWebTokenType>,
// Other JOSE header fields are omitted since the OpenID Connect spec specifically says that
// the "x5u", "x5c", "jku", "jwk" header parameter fields SHOULD NOT be used.
// See http://openid.net/specs/openid-connect-core-1_0-final.html#IDToken.
#[serde(skip)]
_phantom_jt: PhantomData<JT>,
}
pub trait JsonWebTokenPayloadSerde<P>: Debug
where
P: Debug + DeserializeOwned + Serialize,
{
fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<P, DE>;
fn serialize(payload: &P) -> Result<String, serde_json::Error>;
}
#[derive(Clone, Debug, PartialEq)]
pub struct JsonWebTokenJsonPayloadSerde;
impl<P> JsonWebTokenPayloadSerde<P> for JsonWebTokenJsonPayloadSerde
where
P: Debug + DeserializeOwned + Serialize,
{
fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<P, DE> {
serde_json::from_slice(payload)
.map_err(|err| DE::custom(format!("Failed to parse payload JSON: {:?}", err)))
}
fn serialize(payload: &P) -> Result<String, serde_json::Error> {
serde_json::to_string(payload).map_err(Into::into)
}
}
// Helper trait so that we can get borrowed payload when we have a reference to the JWT and owned
// payload when we own the JWT.
pub trait JsonWebTokenAccess<JE, JS, JT, P>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
{
type ReturnType;
fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS, JT>;
fn unverified_payload(self) -> Self::ReturnType;
fn unverified_payload_ref(&self) -> &P;
fn payload<JU, JW>(
self,
signature_alg: &JS,
key: &JW,
) -> Result<Self::ReturnType, SignatureVerificationError>
where
JU: JsonWebKeyUse,
JW: JsonWebKey<JS, JT, JU>;
}
///
/// Error creating a JSON Web Token.
///
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum JsonWebTokenError {
///
/// Failed to serialize JWT.
///
#[error("Failed to serialize JWT")]
SerializationError(#[source] serde_json::Error),
///
/// Failed to sign JWT.
///
#[error("Failed to sign JWT")]
SigningError(#[source] SigningError),
}
#[derive(Clone, Debug, PartialEq)]
pub struct JsonWebToken<JE, JS, JT, P, S>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
{
header: JsonWebTokenHeader<JE, JS, JT>,
payload: P,
signature: Vec<u8>,
signing_input: String,
_phantom: PhantomData<S>,
}
impl<JE, JS, JT, P, S> JsonWebToken<JE, JS, JT, P, S>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
{
pub fn new<JU, K, SK>(payload: P, signing_key: &SK, alg: &JS) -> Result<Self, JsonWebTokenError>
where
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
SK: PrivateSigningKey<JS, JT, JU, K>,
{
let header = JsonWebTokenHeader::<JE, _, _> {
alg: JsonWebTokenAlgorithm::Signature(alg.clone(), PhantomData),
crit: None,
cty: None,
kid: signing_key.as_verification_key().key_id().cloned(),
typ: None,
_phantom_jt: PhantomData,
};
let header_json =
serde_json::to_string(&header).map_err(JsonWebTokenError::SerializationError)?;
let header_base64 = base64::encode_config(&header_json, base64::URL_SAFE_NO_PAD);
let serialized_payload =
S::serialize(&payload).map_err(JsonWebTokenError::SerializationError)?;
let payload_base64 = base64::encode_config(&serialized_payload, base64::URL_SAFE_NO_PAD);
let signing_input = format!("{}.{}", header_base64, payload_base64);
let signature = signing_key
.sign(alg, signing_input.as_bytes())
.map_err(JsonWebTokenError::SigningError)?;
Ok(JsonWebToken {
header,
payload,
signature,
signing_input,
_phantom: PhantomData,
})
}
}
// Owned JWT.
impl<JE, JS, JT, P, S> JsonWebTokenAccess<JE, JS, JT, P> for JsonWebToken<JE, JS, JT, P, S>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
{
type ReturnType = P;
fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS, JT> {
&self.header
}
fn unverified_payload(self) -> Self::ReturnType {
self.payload
}
fn unverified_payload_ref(&self) -> &P {
&self.payload
}
fn payload<JU, JW>(
self,
signature_alg: &JS,
key: &JW,
) -> Result<Self::ReturnType, SignatureVerificationError>
where
JU: JsonWebKeyUse,
JW: JsonWebKey<JS, JT, JU>,
{
key.verify_signature(
signature_alg,
self.signing_input.as_bytes(),
&self.signature,
)?;
Ok(self.payload)
}
}
// Borrowed JWT.
impl<'a, JE, JS, JT, P, S> JsonWebTokenAccess<JE, JS, JT, P> for &'a JsonWebToken<JE, JS, JT, P, S>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
{
type ReturnType = &'a P;
fn unverified_header(&self) -> &JsonWebTokenHeader<JE, JS, JT> {
&self.header
}
fn unverified_payload(self) -> Self::ReturnType {
&self.payload
}
fn unverified_payload_ref(&self) -> &P {
&self.payload
}
fn payload<JU, JW>(
self,
signature_alg: &JS,
key: &JW,
) -> Result<Self::ReturnType, SignatureVerificationError>
where
JU: JsonWebKeyUse,
JW: JsonWebKey<JS, JT, JU>,
{
key.verify_signature(
signature_alg,
self.signing_input.as_bytes(),
&self.signature,
)?;
Ok(&self.payload)
}
}
impl<'de, JE, JS, JT, P, S> Deserialize<'de> for JsonWebToken<JE, JS, JT, P, S>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct JsonWebTokenVisitor<
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
>(
PhantomData<JE>,
PhantomData<JS>,
PhantomData<JT>,
PhantomData<P>,
PhantomData<S>,
);
impl<'de, JE, JS, JT, P, S> Visitor<'de> for JsonWebTokenVisitor<JE, JS, JT, P, S>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
{
type Value = JsonWebToken<JE, JS, JT, P, S>;
fn expecting(&self, formatter: &mut Formatter) -> FormatterResult {
formatter.write_str("JsonWebToken")
}
fn visit_str<DE>(self, v: &str) -> Result<Self::Value, DE>
where
DE: serde::de::Error,
{
let raw_token = v.to_string();
let header: JsonWebTokenHeader<JE, JS, JT>;
let payload: P;
let signature;
let signing_input;
{
let parts = raw_token.split('.').collect::<Vec<_>>();
// NB: We avoid including the full payload encoding in the error output to avoid
// clients potentially logging sensitive values.
if parts.len() != 3 {
return Err(DE::custom(format!(
"Invalid JSON web token: found {} parts (expected 3)",
parts.len()
)));
}
let header_json =
base64::decode_config(parts[0], crate::core::base64_url_safe_no_pad())
.map_err(|err| {
DE::custom(format!("Invalid base64url header encoding: {:?}", err))
})?;
header = serde_json::from_slice(&header_json).map_err(|err| {
DE::custom(format!("Failed to parse header JSON: {:?}", err))
})?;
let raw_payload =
base64::decode_config(parts[1], crate::core::base64_url_safe_no_pad())
.map_err(|err| {
DE::custom(format!("Invalid base64url payload encoding: {:?}", err))
})?;
payload = S::deserialize::<DE>(&raw_payload)?;
signature =
base64::decode_config(parts[2], crate::core::base64_url_safe_no_pad())
.map_err(|err| {
DE::custom(format!(
"Invalid base64url signature encoding: {:?}",
err
))
})?;
signing_input = format!("{}.{}", parts[0], parts[1]);
}
Ok(JsonWebToken {
header,
payload,
signature,
signing_input,
_phantom: PhantomData,
})
}
}
deserializer.deserialize_str(JsonWebTokenVisitor(
PhantomData,
PhantomData,
PhantomData,
PhantomData,
PhantomData,
))
}
}
impl<JE, JS, JT, P, S> Serialize for JsonWebToken<JE, JS, JT, P, S>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
P: Debug + DeserializeOwned + Serialize,
S: JsonWebTokenPayloadSerde<P>,
{
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
where
SE: Serializer,
{
let signature_base64 = base64::encode_config(&self.signature, base64::URL_SAFE_NO_PAD);
serializer.serialize_str(&format!("{}.{}", self.signing_input, signature_base64))
}
}
#[cfg(test)]
pub mod tests {
use std::marker::PhantomData;
use std::string::ToString;
use crate::core::{
CoreJsonWebKey, CoreJsonWebKeyType, CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm, CoreRsaPrivateSigningKey,
};
use crate::JsonWebKeyId;
use super::{
JsonWebToken, JsonWebTokenAccess, JsonWebTokenAlgorithm, JsonWebTokenJsonPayloadSerde,
JsonWebTokenPayloadSerde,
};
type CoreAlgorithm = JsonWebTokenAlgorithm<
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
>;
pub const TEST_JWT: &str =
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZ\
GFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGU\
gcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlc\
mUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e\
5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3l\
fWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV\
0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41\
Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg";
const TEST_JWT_PAYLOAD: &str = "It\u{2019}s a dangerous business, Frodo, going out your \
door. You step onto the road, and if you don't keep your feet, \
there\u{2019}s no knowing where you might be swept off \
to.";
pub const TEST_RSA_PUB_KEY: &str = "{
\"kty\": \"RSA\",
\"kid\": \"bilbo.baggins@hobbiton.example\",
\"use\": \"sig\",
\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT\
-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV\
wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-\
oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde\
3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC\
LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g\
HdrNP5zw\",
\"e\": \"AQAB\"
}";
pub const TEST_EC_PUB_KEY_P256: &str = r#"{
"kty": "EC",
"kid": "bilbo.baggins@hobbiton.example",
"use": "sig",
"crv": "P-256",
"x": "t6PHivOTggpaX9lkMkis2p8kMhy-CktJAFTz6atReZw",
"y": "ODobXupKlD0DeM1yRd7bX4XFNBO1HOgCT1UCu0KY3lc"
}"#;
pub const TEST_EC_PUB_KEY_P384: &str = r#"{
"kty": "EC",
"kid": "bilbo.baggins@hobbiton.example",
"use": "sig",
"crv" : "P-384",
"x": "9ywsUbxX59kJXFRiWHcx97wRKNiF8Hc9F5wI08n8h2ek_qAl0veEc36k1Qz6KLiL",
"y": "6PWlqjRbaV7V8ohDscM243IneuLZmxDGLiGNA1w69fQhEDsvZtKLUQ5KiHLgR3op"
}"#;
// This is the PEM form of the test private key from:
// https://tools.ietf.org/html/rfc7520#section-3.4
pub const TEST_RSA_PRIV_KEY: &str = "-----BEGIN RSA PRIVATE KEY-----\n\
MIIEowIBAAKCAQEAn4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8/KuKPEHLd4\n\
rHVTeT+O+XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz/AJmSCpMaJMRBSFKrKb2wqVwG\n\
U/NsYOYL+QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj+oBHqFEHYpP\n\
e7Tpe+OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzw\n\
OHrtIQbS0FVbb9k3+tVTU4fg/3L/vniUFAKwuCLqKnS2BYwdq/mzSnbLY7h/qixo\n\
R7jig3//kRhuaxwUkRz5iaiQkqgc5gHdrNP5zwIDAQABAoIBAG1lAvQfhBUSKPJK\n\
Rn4dGbshj7zDSr2FjbQf4pIh/ZNtHk/jtavyO/HomZKV8V0NFExLNi7DUUvvLiW7\n\
0PgNYq5MDEjJCtSd10xoHa4QpLvYEZXWO7DQPwCmRofkOutf+NqyDS0QnvFvp2d+\n\
Lov6jn5C5yvUFgw6qWiLAPmzMFlkgxbtjFAWMJB0zBMy2BqjntOJ6KnqtYRMQUxw\n\
TgXZDF4rhYVKtQVOpfg6hIlsaoPNrF7dofizJ099OOgDmCaEYqM++bUlEHxgrIVk\n\
wZz+bg43dfJCocr9O5YX0iXaz3TOT5cpdtYbBX+C/5hwrqBWru4HbD3xz8cY1TnD\n\
qQa0M8ECgYEA3Slxg/DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex/fp7AZ/9\n\
nRaO7HX/+SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr/WCsmGpeNqQn\n\
ev1T7IyEsnh8UMt+n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8bUq0kCgYEAuKE2\n\
dh+cTf6ERF4k4e/jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR/cu0Dm1MZwW\n\
mtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoB\n\
vyY898EXvRD+hdqRxHlSqAZ192zB3pVFJ0s7pFcCgYAHw9W9eS8muPYv4ZhDu/fL\n\
2vorDmD1JqFcHCxZTOnX1NWWAj5hXzmrU0hvWvFC0P4ixddHf5Nqd6+5E9G3k4E5\n\
2IwZCnylu3bqCWNh8pT8T3Gf5FQsfPT5530T2BcsoPhUaeCnP499D+rb2mTnFYeg\n\
mnTT1B/Ue8KGLFFfn16GKQKBgAiw5gxnbocpXPaO6/OKxFFZ+6c0OjxfN2PogWce\n\
TU/k6ZzmShdaRKwDFXisxRJeNQ5Rx6qgS0jNFtbDhW8E8WFmQ5urCOqIOYk28EBi\n\
At4JySm4v+5P7yYBh8B8YD2l9j57z/s8hJAxEbn/q8uHP2ddQqvQKgtsni+pHSk9\n\
XGBfAoGBANz4qr10DdM8DHhPrAb2YItvPVz/VwkBd1Vqj8zCpyIEKe/07oKOvjWQ\n\
SgkLDH9x2hBgY01SbP43CvPk0V72invu2TGkI/FXwXWJLLG7tDSgw4YyfhrYrHmg\n\
1Vre3XB9HH8MYBVB6UIexaAq4xSeoemRKTBesZro7OKjKT8/GmiO\
-----END RSA PRIVATE KEY-----";
#[test]
fn test_jwt_algorithm_deserialization() {
assert_eq!(
serde_json::from_str::<CoreAlgorithm>("\"A128CBC-HS256\"")
.expect("failed to deserialize"),
JsonWebTokenAlgorithm::Encryption(
CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256
),
);
assert_eq!(
serde_json::from_str::<CoreAlgorithm>("\"A128GCM\"").expect("failed to deserialize"),
JsonWebTokenAlgorithm::Encryption(CoreJweContentEncryptionAlgorithm::Aes128Gcm),
);
assert_eq!(
serde_json::from_str::<CoreAlgorithm>("\"HS256\"").expect("failed to deserialize"),
JsonWebTokenAlgorithm::Signature(CoreJwsSigningAlgorithm::HmacSha256, PhantomData),
);
assert_eq!(
serde_json::from_str::<CoreAlgorithm>("\"RS256\"").expect("failed to deserialize"),
JsonWebTokenAlgorithm::Signature(
CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
PhantomData,
),
);
assert_eq!(
serde_json::from_str::<CoreAlgorithm>("\"none\"").expect("failed to deserialize"),
JsonWebTokenAlgorithm::None,
);
serde_json::from_str::<CoreAlgorithm>("\"invalid\"")
.expect_err("deserialization should have failed");
}
#[test]
fn test_jwt_algorithm_serialization() {
assert_eq!(
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Encryption(
CoreJweContentEncryptionAlgorithm::Aes128CbcHmacSha256
))
.expect("failed to serialize"),
"\"A128CBC-HS256\"",
);
assert_eq!(
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Encryption(
CoreJweContentEncryptionAlgorithm::Aes128Gcm
))
.expect("failed to serialize"),
"\"A128GCM\"",
);
assert_eq!(
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Signature(
CoreJwsSigningAlgorithm::HmacSha256,
PhantomData,
))
.expect("failed to serialize"),
"\"HS256\"",
);
assert_eq!(
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::Signature(
CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
PhantomData,
))
.expect("failed to serialize"),
"\"RS256\"",
);
assert_eq!(
serde_json::to_string::<CoreAlgorithm>(&JsonWebTokenAlgorithm::None)
.expect("failed to serialize"),
"\"none\"",
);
}
#[derive(Clone, Debug)]
pub struct JsonWebTokenStringPayloadSerde;
impl JsonWebTokenPayloadSerde<String> for JsonWebTokenStringPayloadSerde {
fn deserialize<DE: serde::de::Error>(payload: &[u8]) -> Result<String, DE> {
Ok(String::from_utf8(payload.to_owned()).unwrap())
}
fn serialize(payload: &String) -> Result<String, serde_json::Error> {
Ok(payload.to_string())
}
}
#[test]
fn test_jwt_basic() {
fn verify_jwt<A>(jwt_access: A, key: &CoreJsonWebKey, expected_payload: &str)
where
A: JsonWebTokenAccess<
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
String,
>,
A::ReturnType: ToString,
{
{
let header = jwt_access.unverified_header();
assert_eq!(
header.alg,
JsonWebTokenAlgorithm::Signature(
CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
PhantomData,
)
);
assert_eq!(header.crit, None);
assert_eq!(header.cty, None);
assert_eq!(
header.kid,
Some(JsonWebKeyId::new(
"bilbo.baggins@hobbiton.example".to_string()
))
);
assert_eq!(header.typ, None);
}
assert_eq!(jwt_access.unverified_payload_ref(), expected_payload);
assert_eq!(
jwt_access
.payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, key)
.expect("failed to validate payload")
.to_string(),
expected_payload
);
}
let key: CoreJsonWebKey =
serde_json::from_str(TEST_RSA_PUB_KEY).expect("deserialization failed");
let jwt: JsonWebToken<
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
String,
JsonWebTokenStringPayloadSerde,
> = serde_json::from_value(serde_json::Value::String(TEST_JWT.to_string()))
.expect("failed to deserialize");
assert_eq!(
serde_json::to_value(&jwt).expect("failed to serialize"),
serde_json::Value::String(TEST_JWT.to_string())
);
verify_jwt(&jwt, &key, TEST_JWT_PAYLOAD);
assert_eq!((&jwt).unverified_payload(), TEST_JWT_PAYLOAD);
verify_jwt(jwt, &key, TEST_JWT_PAYLOAD);
}
#[test]
fn test_new_jwt() {
let signing_key = CoreRsaPrivateSigningKey::from_pem(
TEST_RSA_PRIV_KEY,
Some(JsonWebKeyId::new(
"bilbo.baggins@hobbiton.example".to_string(),
)),
)
.unwrap();
let new_jwt = JsonWebToken::<
CoreJweContentEncryptionAlgorithm,
_,
_,
_,
JsonWebTokenStringPayloadSerde,
>::new(
TEST_JWT_PAYLOAD.to_owned(),
&signing_key,
&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
)
.unwrap();
assert_eq!(
serde_json::to_value(&new_jwt).expect("failed to serialize"),
serde_json::Value::String(TEST_JWT.to_string())
);
}
#[test]
fn test_invalid_signature() {
let corrupted_jwt_str = TEST_JWT
.to_string()
.chars()
.take(TEST_JWT.len() - 1)
.collect::<String>()
+ "f";
let jwt: JsonWebToken<
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
String,
JsonWebTokenStringPayloadSerde,
> = serde_json::from_value(serde_json::Value::String(corrupted_jwt_str))
.expect("failed to deserialize");
let key: CoreJsonWebKey =
serde_json::from_str(TEST_RSA_PUB_KEY).expect("deserialization failed");
// JsonWebTokenAccess for reference.
(&jwt)
.payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, &key)
.expect_err("signature verification should have failed");
// JsonWebTokenAccess for owned value.
jwt.payload(&CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256, &key)
.expect_err("signature verification should have failed");
}
#[test]
fn test_invalid_deserialization() {
#[derive(Debug, Deserialize, Serialize)]
struct TestPayload {
foo: String,
}
fn expect_deserialization_err<I: Into<String>>(jwt_str: I, pattern: &str) {
let err = serde_json::from_value::<
JsonWebToken<
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
TestPayload,
JsonWebTokenJsonPayloadSerde,
>,
>(serde_json::Value::String(jwt_str.into()))
.expect_err("deserialization should have failed");
assert!(
err.to_string().contains(pattern),
"Error `{}` must contain string `{}`",
err,
pattern,
);
}
// Too many dots
expect_deserialization_err("a.b.c.d", "found 4 parts (expected 3)");
// Invalid header base64
expect_deserialization_err("a!.b.c", "Invalid base64url header encoding");
// Invalid header utf-8 (after base64 decoding)
expect_deserialization_err("gA.b.c", "Error(\"expected value\", line: 1, column: 1)");
// Invalid header JSON
expect_deserialization_err("bm90X2pzb24.b.c", "Failed to parse header JSON");
let valid_header =
"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9";
// Invalid payload base64
expect_deserialization_err(
format!("{}.b!.c", valid_header),
"Invalid base64url payload encoding",
);
// Invalid payload utf-8 (after base64 decoding)
expect_deserialization_err(
format!("{}.gA.c", valid_header),
"Error(\"expected value\", line: 1, column: 1)",
);
// Invalid payload JSON
expect_deserialization_err(
format!("{}.bm90X2pzb24.c", valid_header),
"Failed to parse payload JSON",
);
let valid_body = "eyJmb28iOiAiYmFyIn0";
// Invalid signature base64
expect_deserialization_err(
format!("{}.{}.c!", valid_header, valid_body),
"Invalid base64url signature encoding",
);
let deserialized = serde_json::from_value::<
JsonWebToken<
CoreJweContentEncryptionAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
TestPayload,
JsonWebTokenJsonPayloadSerde,
>,
>(serde_json::Value::String(format!(
"{}.{}.e2FiY30",
valid_header, valid_body
)))
.expect("failed to deserialize");
assert_eq!(deserialized.unverified_payload().foo, "bar");
}
}

1648
zeroidc/vendor/openidconnect/src/lib.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,895 @@
///
/// Copied from oauth2-rs crate (not part of that crate's stable public interface).
///
macro_rules! new_type {
// Convenience pattern without an impl.
(
$(#[$attr:meta])*
$name:ident(
$(#[$type_attr:meta])*
$type:ty
)
) => {
new_type![
@new_type_pub $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {}
];
};
// Convenience pattern without an impl.
(
$(#[$attr:meta])*
pub(crate) $name:ident(
$(#[$type_attr:meta])*
$type:ty
)
) => {
new_type![
@new_type_pub_crate $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {}
];
};
// Main entry point with an impl.
(
$(#[$attr:meta])*
$name:ident(
$(#[$type_attr:meta])*
$type:ty
)
impl {
$($item:tt)*
}
) => {
new_type![
@new_type_pub $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {
$($item)*
}
];
};
// Main entry point with an impl.
(
$(#[$attr:meta])*
pub(crate) $name:ident(
$(#[$type_attr:meta])*
$type:ty
)
impl {
$($item:tt)*
}
) => {
new_type![
@new_type_pub_crate $(#[$attr])*,
$name(
$(#[$type_attr])*
$type
),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
impl {
$($item)*
}
];
};
// Actual implementation, after stringifying the #[doc] attr.
(
@new_type_pub $(#[$attr:meta])*,
$name:ident(
$(#[$type_attr:meta])*
$type:ty
),
$new_doc:expr,
impl {
$($item:tt)*
}
) => {
$(#[$attr])*
#[derive(Clone, Debug, PartialEq)]
pub struct $name(
$(#[$type_attr])*
$type
);
impl $name {
$($item)*
#[allow(dead_code)]
#[doc = $new_doc]
pub fn new(s: $type) -> Self {
$name(s)
}
}
impl Deref for $name {
type Target = $type;
fn deref(&self) -> &$type {
&self.0
}
}
};
// Actual implementation, after stringifying the #[doc] attr.
(
@new_type_pub_crate $(#[$attr:meta])*,
$name:ident(
$(#[$type_attr:meta])*
$type:ty
),
$new_doc:expr,
impl {
$($item:tt)*
}
) => {
$(#[$attr])*
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct $name(
$(#[$type_attr])*
$type
);
impl $name {
$($item)*
#[doc = $new_doc]
pub fn new(s: $type) -> Self {
$name(s)
}
}
impl Deref for $name {
type Target = $type;
fn deref(&self) -> &$type {
&self.0
}
}
};
}
///
/// Copied from oauth2-rs crate (not part of that crate's stable public interface).
///
macro_rules! new_secret_type {
(
$(#[$attr:meta])*
$name:ident($type:ty)
) => {
new_secret_type![
$(#[$attr])*
$name($type)
impl {}
];
};
(
$(#[$attr:meta])*
$name:ident($type:ty)
impl {
$($item:tt)*
}
) => {
new_secret_type![
$(#[$attr])*,
$name($type),
concat!(
"Create a new `",
stringify!($name),
"` to wrap the given `",
stringify!($type),
"`."
),
concat!("Get the secret contained within this `", stringify!($name), "`."),
impl {
$($item)*
}
];
};
(
$(#[$attr:meta])*,
$name:ident($type:ty),
$new_doc:expr,
$secret_doc:expr,
impl {
$($item:tt)*
}
) => {
$(
#[$attr]
)*
pub struct $name($type);
impl $name {
$($item)*
#[doc = $new_doc]
pub fn new(s: $type) -> Self {
$name(s)
}
///
#[doc = $secret_doc]
///
/// # Security Warning
///
/// Leaking this value may compromise the security of the OAuth2 flow.
///
pub fn secret(&self) -> &$type { &self.0 }
}
impl Debug for $name {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
write!(f, concat!(stringify!($name), "([redacted])"))
}
}
};
}
///
/// Creates a URL-specific new type
///
/// Types created by this macro enforce during construction that the contained value represents a
/// syntactically valid URL. However, comparisons and hashes of these types are based on the string
/// representation given during construction, disregarding any canonicalization performed by the
/// underlying `Url` struct. OpenID Connect requires certain URLs (e.g., ID token issuers) to be
/// compared exactly, without canonicalization.
///
/// In addition to the raw string representation, these types include a `url` method to retrieve a
/// parsed `Url` struct.
///
macro_rules! new_url_type {
// Convenience pattern without an impl.
(
$(#[$attr:meta])*
$name:ident
) => {
new_url_type![
@new_type_pub $(#[$attr])*,
$name,
concat!("Create a new `", stringify!($name), "` from a `String` to wrap a URL."),
concat!("Create a new `", stringify!($name), "` from a `Url` to wrap a URL."),
concat!("Return this `", stringify!($name), "` as a parsed `Url`."),
impl {}
];
};
// Main entry point with an impl.
(
$(#[$attr:meta])*
$name:ident
impl {
$($item:tt)*
}
) => {
new_url_type![
@new_type_pub $(#[$attr])*,
$name,
concat!("Create a new `", stringify!($name), "` from a `String` to wrap a URL."),
concat!("Create a new `", stringify!($name), "` from a `Url` to wrap a URL."),
concat!("Return this `", stringify!($name), "` as a parsed `Url`."),
impl {
$($item)*
}
];
};
// Actual implementation, after stringifying the #[doc] attr.
(
@new_type_pub $(#[$attr:meta])*,
$name:ident,
$new_doc:expr,
$from_url_doc:expr,
$url_doc:expr,
impl {
$($item:tt)*
}
) => {
$(#[$attr])*
#[derive(Clone)]
pub struct $name(Url, String);
impl $name {
#[doc = $new_doc]
pub fn new(url: String) -> Result<Self, ::url::ParseError> {
Ok($name(Url::parse(&url)?, url))
}
#[doc = $from_url_doc]
pub fn from_url(url: Url) -> Self {
let s = url.to_string();
Self(url, s)
} #[doc = $url_doc]
pub fn url(&self) -> &Url {
return &self.0;
}
$($item)*
}
impl Deref for $name {
type Target = String;
fn deref(&self) -> &String {
&self.1
}
}
impl ::std::fmt::Debug for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
let mut debug_trait_builder = f.debug_tuple(stringify!($name));
debug_trait_builder.field(&self.1);
debug_trait_builder.finish()
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: ::serde::de::Deserializer<'de>,
{
struct UrlVisitor;
impl<'de> ::serde::de::Visitor<'de> for UrlVisitor {
type Value = $name;
fn expecting(
&self,
formatter: &mut ::std::fmt::Formatter
) -> ::std::fmt::Result {
formatter.write_str(stringify!($name))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
$name::new(v.to_string()).map_err(E::custom)
}
}
deserializer.deserialize_str(UrlVisitor {})
}
}
impl ::serde::Serialize for $name {
fn serialize<SE>(&self, serializer: SE) -> Result<SE::Ok, SE::Error>
where
SE: ::serde::Serializer,
{
serializer.serialize_str(&self.1)
}
}
impl ::std::hash::Hash for $name {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) -> () {
::std::hash::Hash::hash(&(self.1), state);
}
}
impl Ord for $name {
fn cmp(&self, other: &$name) -> ::std::cmp::Ordering {
self.1.cmp(&other.1)
}
}
impl PartialOrd for $name {
fn partial_cmp(&self, other: &$name) -> Option<::std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for $name {
fn eq(&self, other: &$name) -> bool {
self.1 == other.1
}
}
impl Eq for $name {}
};
}
macro_rules! deserialize_fields {
(@field_str Option(Seconds($field:ident))) => { stringify![$field] };
(@field_str Option(DateTime(Seconds($field:ident)))) => { stringify![$field] };
(@field_str Option($field:ident)) => { stringify![$field] };
(@field_str LanguageTag($field:ident)) => { stringify![$field] };
(@field_str $field:ident) => { stringify![$field] };
(@let_none Option(Seconds($field:ident))) => { let mut $field = None; };
(@let_none Option(DateTime(Seconds($field:ident)))) => { let mut $field = None; };
(@let_none Option($field:ident)) => { let mut $field = None; };
(@let_none LanguageTag($field:ident)) => { let mut $field = None; };
(@let_none $field:ident) => { let mut $field = None; };
(@case $map:ident $key:ident $language_tag_opt:ident Option(Seconds($field:ident))) => {
if $field.is_some() {
return Err(serde::de::Error::duplicate_field(stringify!($field)));
} else if let Some(language_tag) = $language_tag_opt {
return Err(
serde::de::Error::custom(
format!(
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
language_tag.as_ref()
)
)
);
}
let seconds = $map.next_value::<Option<u64>>()?;
$field = seconds.map(Duration::from_secs);
};
(@case $map:ident $key:ident $language_tag_opt:ident
Option(DateTime(Seconds($field:ident)))) => {
if $field.is_some() {
return Err(serde::de::Error::duplicate_field(stringify!($field)));
} else if let Some(language_tag) = $language_tag_opt {
return Err(
serde::de::Error::custom(
format!(
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
language_tag.as_ref()
)
)
);
}
let seconds = $map.next_value::<Option<Timestamp>>()?;
$field = seconds
.map(|sec| timestamp_to_utc(&sec).map_err(|_| serde::de::Error::custom(
format!(
concat!(
"failed to parse `{}` as UTC datetime (in seconds) for key `",
stringify!($field),
"`"
),
sec,
)
))).transpose()?;
};
(@case $map:ident $key:ident $language_tag_opt:ident Option($field:ident)) => {
if $field.is_some() {
return Err(serde::de::Error::duplicate_field(stringify!($field)));
} else if let Some(language_tag) = $language_tag_opt {
return Err(
serde::de::Error::custom(
format!(
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
language_tag.as_ref()
)
)
);
}
$field = $map.next_value()?;
};
(@case $map:ident $key:ident $language_tag_opt:ident LanguageTag($field:ident)) => {
let localized_claim =
if let Some(ref mut localized_claim) = $field {
localized_claim
} else {
let new = LocalizedClaim::new();
$field = Some(new);
$field.as_mut().unwrap()
};
if localized_claim.contains_key($language_tag_opt.as_ref()) {
return Err(serde::de::Error::custom(format!("duplicate field `{}`", $key)));
}
localized_claim.insert($language_tag_opt, $map.next_value()?);
};
(@case $map:ident $key:ident $language_tag_opt:ident $field:ident) => {
if $field.is_some() {
return Err(serde::de::Error::duplicate_field(stringify!($field)));
} else if let Some(language_tag) = $language_tag_opt {
return Err(
serde::de::Error::custom(
format!(
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
language_tag.as_ref()
)
)
);
}
$field = Some($map.next_value()?);
};
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),* => [Option(Seconds($field_new:ident))] $([$($entry:tt)+])*
}) => {
deserialize_fields![
@struct_recurs [$($struct_type)+] {
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
}
]
};
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),* => [Option(DateTime(Seconds($field_new:ident)))] $([$($entry:tt)+])*
}) => {
deserialize_fields![
@struct_recurs [$($struct_type)+] {
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
}
]
};
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),* => [Option($field_new:ident)] $([$($entry:tt)+])*
}) => {
deserialize_fields![
@struct_recurs [$($struct_type)+] {
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
}
]
};
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),* => [LanguageTag($field_new:ident)] $([$($entry:tt)+])*
}) => {
deserialize_fields![
@struct_recurs [$($struct_type)+] {
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
}
]
};
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),* => [$field_new:ident] $([$($entry:tt)+])*
}) => {
deserialize_fields![
@struct_recurs [$($struct_type)+] {
$($name: $e,)* $field_new:
$field_new
.ok_or_else(|| serde::de::Error::missing_field(stringify!($field_new)))? =>
$([$($entry)+])*
}
]
};
// Actually instantiate the struct.
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),+ =>
}) => {
#[allow(clippy::redundant_field_names)]
$($struct_type)+ {
$($name: $e),+
}
};
// Main entry point
(
$map:ident {
$([$($entry:tt)+])+
}
) => {
// let mut field_name = None;
$(deserialize_fields![@let_none $($entry)+];)+
while let Some(key) = $map.next_key::<String>()? {
let (field_name, language_tag_opt) = split_language_tag_key(&key);
match field_name {
$(
// "field_name" => { ... }
deserialize_fields![@field_str $($entry)+] => {
deserialize_fields![@case $map key language_tag_opt $($entry)+];
},
)+
// Ignore unknown fields.
_ => {
$map.next_value::<serde::de::IgnoredAny>()?;
}
}
}
Ok(deserialize_fields![@struct_recurs [Self::Value] { => $([$($entry)+])* }])
};
}
macro_rules! serialize_fields {
(@case $self:ident $map:ident Option(Seconds($field:ident))) => {
if let Some(ref $field) = $self.$field {
$map.serialize_entry(stringify!($field), &$field.as_secs())?;
}
};
(@case $self:ident $map:ident Option(DateTime(Seconds($field:ident)))) => {
if let Some(ref $field) = $self.$field {
$map.serialize_entry(stringify!($field), &utc_to_seconds(&$field))?;
}
};
(@case $self:ident $map:ident Option($field:ident)) => {
if let Some(ref $field) = $self.$field {
$map.serialize_entry(stringify!($field), $field)?;
}
};
(@case $self:ident $map:ident LanguageTag($field:ident)) => {
if let Some(ref field_map) = $self.$field {
use itertools::sorted;
let sorted_field_map = sorted(field_map.iter());
for (language_tag_opt, $field) in sorted_field_map {
if let Some(ref language_tag) = language_tag_opt {
$map.serialize_entry(
&format!(concat!(stringify!($field), "#{}"), language_tag.as_ref()),
&$field
)?;
} else {
$map.serialize_entry(stringify!($field), &$field)?;
}
}
}
};
(@case $self:ident $map:ident $field:ident) => {
$map.serialize_entry(stringify!($field), &$self.$field)?;
};
// Main entry point
(
$self:ident -> $serializer:ident {
$([$($entry:tt)+])+
}
) => {
let mut map = $serializer.serialize_map(None)?;
$(
serialize_fields![@case $self map $($entry)+];
)+
map.end()
};
}
macro_rules! field_getters {
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < bool >) => {
#[doc = $doc]
$vis fn $field(&$self) -> Option<bool> {
$zero.$field
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < bool > { $($body:tt)+ }) => {
#[doc = $doc]
$vis fn $field(&$self) -> Option<bool> {
$($body)+
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < DateTime < Utc >>) => {
#[doc = $doc]
$vis fn $field(&$self) -> Option<DateTime<Utc>> {
$zero.$field
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < DateTime < Utc >> { $($body:tt)+ }) => {
#[doc = $doc]
$vis fn $field(&$self) -> Option<DateTime<Utc>> {
$($body)+
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < $type:ty >) => {
#[doc = $doc]
$vis fn $field(&$self) -> Option<&$type> {
$zero.$field.as_ref()
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident Option < $type:ty > { $($body:tt)+ }) => {
#[doc = $doc]
$vis fn $field(&$self) -> Option<$type> {
$($body)+
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident DateTime < Utc >) => {
#[doc = $doc]
$vis fn $field(&$self) -> DateTime<Utc> {
$zero.$field
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident $type:ty) => {
#[doc = $doc]
$vis fn $field(&$self) -> &$type {
&$zero.$field
}
};
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $field:ident $type:ty { $($body:tt)+ }) => {
#[doc = $doc]
$vis fn $field(&$self) -> $type {
$($body)+
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < bool >) => {
#[doc = $doc]
fn $field(&$self) -> Option<bool> {
$zero.$field()
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < bool > { $($body:tt)+ }) => {
#[doc = $doc]
fn $field(&$self) -> Option<bool> {
$($body)+
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < DateTime < Utc >>) => {
#[doc = $doc]
fn $field(&$self) -> Option<DateTime<Utc>> {
$zero.$field()
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < DateTime < Utc >> { $($body:tt)+ }) => {
#[doc = $doc]
fn $field(&$self) -> Option<DateTime<Utc>> {
$($body)+
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < $type:ty >) => {
#[doc = $doc]
fn $field(&$self) -> Option<&$type> {
$zero.$field()
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() Option < $type:ty > { $($body:tt)+ }) => {
#[doc = $doc]
fn $field(&$self) -> Option<$type> {
$($body)+
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() DateTime < Utc >) => {
#[doc = $doc]
fn $field(&$self) -> DateTime<Utc> {
$zero.$field()
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() $type:ty) => {
#[doc = $doc]
fn $field(&$self) -> &$type {
&$zero.$field()
}
};
(@case [$doc:expr] $self:ident [$zero:expr] $field:ident() $type:ty { $($body:tt)+ }) => {
#[doc = $doc]
fn $field(&$self) -> $type {
$($body)+
}
};
// Main entry points
(
$vis:vis $self:ident [$zero:expr] [$doc:expr] {
$(
$field:ident[$($entry:tt)+] [$doc_field:expr],
)+
}
) => {
$(
field_getters![
@case
[concat!("Returns the `", $doc_field, "` ", $doc, ".")]
$vis $self [$zero] $field $($entry)+
];
)+
};
(
$vis:vis $self:ident [$zero:expr]() [$doc:expr] {
$(
$field:ident[$($entry:tt)+] [$doc_field:expr],
)+
}
) => {
$(
field_getters![
@case
[concat!("Returns the `", $doc_field, "` ", $doc, ".")]
$vis $self [$zero] $field() $($entry)+
];
)+
};
}
macro_rules! field_setters {
(@case [$doc:expr] $vis:vis $self:ident [$zero:expr] $setter:ident $field:ident $type:ty [$doc_field:expr]) => {
field_setters![
@case2
[concat!("Sets the `", $doc_field, "` ", $doc, ".")]
$vis $self [$zero] $setter $field $type
];
};
(@case2 [$doc:expr] $vis:vis $self:ident [$zero:expr] $setter:ident $field:ident $type:ty) => {
#[doc = $doc]
$vis fn $setter(
mut $self,
$field: $type
) -> Self {
$zero.$field = $field;
$self
}
};
// Main entry point
(
$vis:vis $self:ident [$zero:expr] [$doc:expr] {
$setter:ident -> $field:ident[$($entry:tt)+] [$doc_field:expr]
}
) => {
field_setters![
@case [$doc] $vis $self [$zero] $setter $field $($entry)+ [$doc_field]
];
};
}
macro_rules! field_getters_setters {
(
@single $vis:vis $self:ident [$zero:expr] [$doc:expr]
[$setter:ident -> $field:ident[$($entry:tt)+] [$field_doc:expr], $($rest:tt)*]
) => {
field_getters![$vis $self [$zero] [$doc] { $field[$($entry)+] [$field_doc], }];
field_setters![
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [$field_doc] }
];
field_getters_setters![@single $vis $self [$zero] [$doc] [$($rest)*]];
};
(
@single $vis:vis $self:ident [$zero:expr]() [$doc:expr]
[$setter:ident -> $field:ident[$($entry:tt)+] [$field_doc:expr], $($rest:tt)*]
) => {
field_getters![$vis $self [$zero]() [$doc] { $field[$($entry)+] [$field_doc], }];
field_setters![
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [$field_doc] }
];
field_getters_setters![@single $vis $self [$zero]() [$doc] [$($rest)*]];
};
(
@single $vis:vis $self:ident [$zero:expr] [$doc:expr]
[$setter:ident -> $field:ident[$($entry:tt)+], $($rest:tt)*]
) => {
field_getters![$vis $self [$zero] [$doc] { $field[$($entry)+] [stringify!($field)], }];
field_setters![
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [stringify!($field)] }
];
field_getters_setters![@single $vis $self [$zero] [$doc] [$($rest)*]];
};
(
@single $vis:vis $self:ident [$zero:expr]() [$doc:expr]
[$setter:ident -> $field:ident[$($entry:tt)+], $($rest:tt)*]
) => {
field_getters![$vis $self [$zero]() [$doc] { $field[$($entry)+] [stringify!($field)], }];
field_setters![
$vis $self [$zero] [$doc] { $setter -> $field[$($entry)+] [stringify!($field)] }
];
field_getters_setters![@single $vis $self [$zero]() [$doc] [$($rest)*]];
};
// Base case.
(@single $vis:vis $self:ident [$zero:expr] [$doc:expr] []) => {};
// Main entry points.
(
$vis:vis $self:ident [$zero:expr] [$doc:expr] {
$setter:ident -> $field:ident[$($entry:tt)+] $($rest:tt)*
}
) => {
field_getters_setters![
@single
$vis $self [$zero] [$doc] [$setter -> $field[$($entry)+] $($rest)*]
];
};
(
$vis:vis $self:ident [$zero:expr]() [$doc:expr] {
$setter:ident -> $field:ident[$($entry:tt)+] $($rest:tt)*
}
) => {
field_getters_setters![
@single
$vis $self [$zero]() [$doc] [$setter -> $field[$($entry)+] $($rest)*]
];
};
}
macro_rules! deserialize_from_str {
($type:path) => {
impl<'de> serde::Deserialize<'de> for $type {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let variant_str = String::deserialize(deserializer)?;
Ok(Self::from_str(&variant_str))
}
}
};
}
macro_rules! serialize_as_str {
($type:path) => {
impl serde::ser::Serialize for $type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.as_ref())
}
}
};
}

File diff suppressed because it is too large Load Diff

1342
zeroidc/vendor/openidconnect/src/types.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,557 @@
use std::future::Future;
use std::ops::Deref;
use std::str;
use chrono::{DateTime, Utc};
use http::header::{HeaderValue, ACCEPT, CONTENT_TYPE};
use http::method::Method;
use http::status::StatusCode;
use oauth2::AccessToken;
use thiserror::Error;
use url::Url;
use crate::helpers::FilteredFlatten;
use crate::http_utils::{auth_bearer, content_type_has_essence, MIME_TYPE_JSON, MIME_TYPE_JWT};
use crate::jwt::{JsonWebTokenError, JsonWebTokenJsonPayloadSerde};
use crate::types::helpers::deserialize_string_or_vec_opt;
use crate::types::LocalizedClaim;
use crate::verification::UserInfoVerifier;
use crate::{
AdditionalClaims, AddressClaim, Audience, AudiencesClaim, ClaimsVerificationError,
EndUserBirthday, EndUserEmail, EndUserFamilyName, EndUserGivenName, EndUserMiddleName,
EndUserName, EndUserNickname, EndUserPhoneNumber, EndUserPictureUrl, EndUserProfileUrl,
EndUserTimezone, EndUserUsername, EndUserWebsiteUrl, GenderClaim, HttpRequest, HttpResponse,
IssuerClaim, IssuerUrl, JsonWebKey, JsonWebKeyType, JsonWebKeyUse, JsonWebToken,
JweContentEncryptionAlgorithm, JwsSigningAlgorithm, LanguageTag, PrivateSigningKey,
StandardClaims, SubjectIdentifier,
};
///
/// User info request.
///
pub struct UserInfoRequest<'a, JE, JS, JT, JU, K>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
pub(super) url: &'a UserInfoUrl,
pub(super) access_token: AccessToken,
pub(super) require_signed_response: bool,
pub(super) signed_response_verifier: UserInfoVerifier<'static, JE, JS, JT, JU, K>,
}
impl<'a, JE, JS, JT, JU, K> UserInfoRequest<'a, JE, JS, JT, JU, K>
where
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
///
/// Submits this request to the associated user info endpoint using the specified synchronous
/// HTTP client.
///
pub fn request<AC, GC, HC, RE>(
self,
http_client: HC,
) -> Result<UserInfoClaims<AC, GC>, UserInfoError<RE>>
where
AC: AdditionalClaims,
GC: GenderClaim,
HC: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: std::error::Error + 'static,
{
http_client(self.prepare_request())
.map_err(UserInfoError::Request)
.and_then(|http_response| self.user_info_response(http_response))
}
///
/// Submits this request to the associated user info endpoint using the specified asynchronous
/// HTTP client.
///
pub async fn request_async<AC, C, F, GC, RE>(
self,
http_client: C,
) -> Result<UserInfoClaims<AC, GC>, UserInfoError<RE>>
where
AC: AdditionalClaims,
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
GC: GenderClaim,
RE: std::error::Error + 'static,
{
let http_request = self.prepare_request();
let http_response = http_client(http_request)
.await
.map_err(UserInfoError::Request)?;
self.user_info_response(http_response)
}
fn prepare_request(&self) -> HttpRequest {
let (auth_header, auth_value) = auth_bearer(&self.access_token);
HttpRequest {
url: self.url.url().clone(),
method: Method::GET,
headers: vec![
(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON)),
(auth_header, auth_value),
]
.into_iter()
.collect(),
body: Vec::new(),
}
}
fn user_info_response<AC, GC, RE>(
self,
http_response: HttpResponse,
) -> Result<UserInfoClaims<AC, GC>, UserInfoError<RE>>
where
AC: AdditionalClaims,
GC: GenderClaim,
RE: std::error::Error + 'static,
{
if http_response.status_code != StatusCode::OK {
return Err(UserInfoError::Response(
http_response.status_code,
http_response.body,
"unexpected HTTP status code".to_string(),
));
}
match http_response
.headers
.get(CONTENT_TYPE)
.map(ToOwned::to_owned)
.unwrap_or_else(|| HeaderValue::from_static(MIME_TYPE_JSON))
{
ref content_type if content_type_has_essence(content_type, MIME_TYPE_JSON) => {
if self.require_signed_response {
return Err(UserInfoError::ClaimsVerification(
ClaimsVerificationError::NoSignature,
));
}
UserInfoClaims::from_json(
&http_response.body,
self.signed_response_verifier.expected_subject(),
)
}
ref content_type if content_type_has_essence(content_type, MIME_TYPE_JWT) => {
let jwt_str = String::from_utf8(http_response.body).map_err(|_| {
UserInfoError::Other("response body has invalid UTF-8 encoding".to_string())
})?;
serde_path_to_error::deserialize::<_, UserInfoJsonWebToken<AC, GC, JE, JS, JT>>(
serde_json::Value::String(jwt_str),
)
.map_err(UserInfoError::Parse)?
.claims(&self.signed_response_verifier)
.map_err(UserInfoError::ClaimsVerification)
}
ref content_type => Err(UserInfoError::Response(
http_response.status_code,
http_response.body,
format!("unexpected response Content-Type: `{:?}`", content_type),
)),
}
}
///
/// Specifies whether to require the user info response to be a signed JSON Web Token (JWT).
///
pub fn require_signed_response(mut self, require_signed_response: bool) -> Self {
self.require_signed_response = require_signed_response;
self
}
///
/// Specifies whether to require the issuer of the signed JWT response to match the expected
/// issuer URL for this provider.
///
/// This option has no effect on unsigned JSON responses.
///
pub fn require_issuer_match(mut self, iss_required: bool) -> Self {
self.signed_response_verifier = self
.signed_response_verifier
.require_issuer_match(iss_required);
self
}
///
/// Specifies whether to require the audience of the signed JWT response to match the expected
/// audience (client ID).
///
/// This option has no effect on unsigned JSON responses.
///
pub fn require_audience_match(mut self, aud_required: bool) -> Self {
self.signed_response_verifier = self
.signed_response_verifier
.require_audience_match(aud_required);
self
}
}
///
/// User info claims.
///
#[derive(Clone, Debug, Serialize)]
pub struct UserInfoClaims<AC: AdditionalClaims, GC: GenderClaim>(UserInfoClaimsImpl<AC, GC>);
impl<AC, GC> UserInfoClaims<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
///
/// Initializes user info claims.
///
pub fn new(standard_claims: StandardClaims<GC>, additional_claims: AC) -> Self {
Self(UserInfoClaimsImpl {
issuer: None,
audiences: None,
standard_claims,
additional_claims: additional_claims.into(),
})
}
///
/// Initializes user info claims from the provided raw JSON response.
///
/// If an `expected_subject` is provided, this function verifies that the user info claims
/// contain the expected subject and returns an error otherwise.
///
pub fn from_json<RE>(
user_info_json: &[u8],
expected_subject: Option<&SubjectIdentifier>,
) -> Result<Self, UserInfoError<RE>>
where
RE: std::error::Error + 'static,
{
let user_info = serde_path_to_error::deserialize::<_, UserInfoClaimsImpl<AC, GC>>(
&mut serde_json::Deserializer::from_slice(user_info_json),
)
.map_err(UserInfoError::Parse)?;
// This is the only verification we need to do for JSON-based user info claims, so don't
// bother with the complexity of a separate verifier object.
if expected_subject
.iter()
.all(|expected_subject| user_info.standard_claims.sub == **expected_subject)
{
Ok(Self(user_info))
} else {
Err(UserInfoError::ClaimsVerification(
ClaimsVerificationError::InvalidSubject(format!(
"expected `{}` (found `{}`)",
// This can only happen when expected_subject is not None.
expected_subject.unwrap().as_str(),
user_info.standard_claims.sub.as_str(),
)),
))
}
}
field_getters_setters![
pub self [self.0] ["claim"] {
set_issuer -> issuer[Option<IssuerUrl>],
set_audiences -> audiences[Option<Vec<Audience>>] ["aud"],
}
];
///
/// Returns the `sub` claim.
///
pub fn subject(&self) -> &SubjectIdentifier {
&self.0.standard_claims.sub
}
///
/// Sets the `sub` claim.
///
pub fn set_subject(&mut self, subject: SubjectIdentifier) {
self.0.standard_claims.sub = subject
}
field_getters_setters![
pub self [self.0.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>>],
}
];
///
/// Returns the standard claims as a `StandardClaims` object.
///
pub fn standard_claims(&self) -> &StandardClaims<GC> {
&self.0.standard_claims
}
///
/// Returns additional user info claims.
///
pub fn additional_claims(&self) -> &AC {
self.0.additional_claims.as_ref()
}
///
/// Returns mutable additional user info claims.
///
pub fn additional_claims_mut(&mut self) -> &mut AC {
self.0.additional_claims.as_mut()
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct UserInfoClaimsImpl<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
#[serde(rename = "iss", skip_serializing_if = "Option::is_none")]
pub issuer: Option<IssuerUrl>,
// We always serialize as an array, which is valid according to the spec.
#[serde(
default,
rename = "aud",
deserialize_with = "deserialize_string_or_vec_opt",
skip_serializing_if = "Option::is_none"
)]
pub audiences: Option<Vec<Audience>>,
#[serde(bound = "GC: GenderClaim", flatten)]
pub standard_claims: StandardClaims<GC>,
#[serde(bound = "AC: AdditionalClaims", flatten)]
pub additional_claims: FilteredFlatten<StandardClaims<GC>, AC>,
}
impl<AC, GC> AudiencesClaim for UserInfoClaimsImpl<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn audiences(&self) -> Option<&Vec<Audience>> {
self.audiences.as_ref()
}
}
impl<'a, AC, GC> AudiencesClaim for &'a UserInfoClaimsImpl<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn audiences(&self) -> Option<&Vec<Audience>> {
self.audiences.as_ref()
}
}
impl<AC, GC> IssuerClaim for UserInfoClaimsImpl<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn issuer(&self) -> Option<&IssuerUrl> {
self.issuer.as_ref()
}
}
impl<'a, AC, GC> IssuerClaim for &'a UserInfoClaimsImpl<AC, GC>
where
AC: AdditionalClaims,
GC: GenderClaim,
{
fn issuer(&self) -> Option<&IssuerUrl> {
self.issuer.as_ref()
}
}
///
/// JSON Web Token (JWT) containing user info claims.
///
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct UserInfoJsonWebToken<
AC: AdditionalClaims,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
>(
#[serde(bound = "AC: AdditionalClaims")]
JsonWebToken<JE, JS, JT, UserInfoClaimsImpl<AC, GC>, JsonWebTokenJsonPayloadSerde>,
);
impl<AC, GC, JE, JS, JT> UserInfoJsonWebToken<AC, GC, JE, JS, JT>
where
AC: AdditionalClaims,
GC: GenderClaim,
JE: JweContentEncryptionAlgorithm<JT>,
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
///
/// Initializes a new signed JWT containing the specified claims, signed with the specified key
/// and signing algorithm.
///
pub fn new<JU, K, S>(
claims: UserInfoClaims<AC, GC>,
signing_key: &S,
alg: JS,
) -> Result<Self, JsonWebTokenError>
where
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
S: PrivateSigningKey<JS, JT, JU, K>,
{
Ok(Self(JsonWebToken::new(claims.0, signing_key, &alg)?))
}
///
/// Verifies and returns the user info claims.
///
pub fn claims<JU, K>(
self,
verifier: &UserInfoVerifier<JE, JS, JT, JU, K>,
) -> Result<UserInfoClaims<AC, GC>, ClaimsVerificationError>
where
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
Ok(UserInfoClaims(verifier.verified_claims(self.0)?))
}
}
new_url_type![
///
/// URL for a provider's user info endpoint.
///
UserInfoUrl
];
///
/// Error retrieving user info.
///
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum UserInfoError<RE>
where
RE: std::error::Error + 'static,
{
///
/// Failed to verify user info claims.
///
#[error("Failed to verify claims")]
ClaimsVerification(#[source] ClaimsVerificationError),
///
/// Failed to parse server response.
///
#[error("Failed to parse server response")]
Parse(#[source] serde_path_to_error::Error<serde_json::Error>),
///
/// An error occurred while sending the request or receiving the response (e.g., network
/// connectivity failed).
///
#[error("Request failed")]
Request(#[source] RE),
///
/// Server returned an invalid response.
///
#[error("Server returned invalid response: {2}")]
Response(StatusCode, Vec<u8>, String),
///
/// An unexpected error occurred.
///
#[error("Other error: {0}")]
Other(String),
}
#[cfg(test)]
mod tests {
use crate::core::CoreGenderClaim;
use crate::{AdditionalClaims, UserInfoClaims};
use std::collections::HashMap;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
struct TestClaims {
pub tfa_method: String,
}
impl AdditionalClaims for TestClaims {}
#[test]
fn test_additional_claims() {
let claims = UserInfoClaims::<TestClaims, CoreGenderClaim>::from_json::<
crate::reqwest::HttpClientError,
>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\"],
\"tfa_method\": \"u2f\"
}"
.as_bytes(),
None,
)
.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\"],\
\"sub\":\"24400320\",\
\"tfa_method\":\"u2f\"\
}",
);
UserInfoClaims::<TestClaims, CoreGenderClaim>::from_json::<crate::reqwest::HttpClientError>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\"]
}".as_bytes(),
None,
)
.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 = UserInfoClaims::<AllOtherClaims, CoreGenderClaim>::from_json::<
crate::reqwest::HttpClientError,
>(
"{
\"iss\": \"https://server.example.com\",
\"sub\": \"24400320\",
\"aud\": [\"s6BhdRkqt3\"],
\"tfa_method\": \"u2f\",
\"updated_at\": 1000
}"
.as_bytes(),
None,
)
.expect("failed to deserialize");
assert_eq!(claims.additional_claims().0.len(), 1);
assert_eq!(claims.additional_claims().0["tfa_method"], "u2f");
}
}

File diff suppressed because it is too large Load Diff