pub trait ScopeSet: sealed::Sealed {
fn names() -> &'static [&'static str];
}
pub trait HasEmail: ScopeSet {}
pub trait HasProfile: ScopeSet {}
pub trait HasPhone: ScopeSet {}
pub trait HasAddress: ScopeSet {}
mod sealed {
pub trait Sealed {}
}
#[allow(dead_code)]
pub(crate) const BASE_CLAIMS: &[&str] = &[
"iss",
"sub",
"aud",
"exp",
"iat",
"nonce",
"at_hash",
"c_hash",
"azp",
"auth_time",
"acr",
"amr",
"cat",
];
#[allow(dead_code)]
pub(crate) const EMAIL_CLAIMS: &[&str] = &["email", "email_verified"];
#[allow(dead_code)]
pub(crate) const PROFILE_CLAIMS: &[&str] = &[
"name",
"given_name",
"family_name",
"middle_name",
"nickname",
"preferred_username",
"profile",
"picture",
"website",
"gender",
"birthdate",
"zoneinfo",
"locale",
"updated_at",
];
#[allow(dead_code)]
pub(crate) const PHONE_CLAIMS: &[&str] = &["phone_number", "phone_number_verified"];
#[allow(dead_code)]
pub(crate) const ADDRESS_CLAIMS: &[&str] = &["address"];
static NAMES_OPENID: &[&str] = &[
"iss", "sub", "aud", "exp", "iat", "nonce", "at_hash", "c_hash", "azp", "auth_time", "acr", "amr", "cat",
];
static NAMES_EMAIL: &[&str] = &[
"iss", "sub", "aud", "exp", "iat", "nonce", "at_hash", "c_hash", "azp", "auth_time", "acr", "amr", "cat",
"email", "email_verified",
];
static NAMES_PROFILE: &[&str] = &[
"iss", "sub", "aud", "exp", "iat", "nonce", "at_hash", "c_hash", "azp", "auth_time", "acr", "amr", "cat",
"name", "given_name", "family_name", "middle_name", "nickname", "preferred_username",
"profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale",
"updated_at",
];
static NAMES_EMAIL_PROFILE: &[&str] = &[
"iss", "sub", "aud", "exp", "iat", "nonce", "at_hash", "c_hash", "azp", "auth_time", "acr", "amr", "cat",
"email", "email_verified",
"name", "given_name", "family_name", "middle_name", "nickname", "preferred_username",
"profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale",
"updated_at",
];
static NAMES_EMAIL_PROFILE_PHONE: &[&str] = &[
"iss", "sub", "aud", "exp", "iat", "nonce", "at_hash", "c_hash", "azp", "auth_time", "acr", "amr", "cat",
"email", "email_verified",
"name", "given_name", "family_name", "middle_name", "nickname", "preferred_username",
"profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale",
"updated_at",
"phone_number", "phone_number_verified",
];
static NAMES_EMAIL_PROFILE_PHONE_ADDRESS: &[&str] = &[
"iss", "sub", "aud", "exp", "iat", "nonce", "at_hash", "c_hash", "azp", "auth_time", "acr", "amr", "cat",
"email", "email_verified",
"name", "given_name", "family_name", "middle_name", "nickname", "preferred_username",
"profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale",
"updated_at",
"phone_number", "phone_number_verified",
"address",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Openid;
impl sealed::Sealed for Openid {}
impl ScopeSet for Openid {
fn names() -> &'static [&'static str] {
NAMES_OPENID
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Email;
impl sealed::Sealed for Email {}
impl ScopeSet for Email {
fn names() -> &'static [&'static str] {
NAMES_EMAIL
}
}
impl HasEmail for Email {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Profile;
impl sealed::Sealed for Profile {}
impl ScopeSet for Profile {
fn names() -> &'static [&'static str] {
NAMES_PROFILE
}
}
impl HasProfile for Profile {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmailProfile;
impl sealed::Sealed for EmailProfile {}
impl ScopeSet for EmailProfile {
fn names() -> &'static [&'static str] {
NAMES_EMAIL_PROFILE
}
}
impl HasEmail for EmailProfile {}
impl HasProfile for EmailProfile {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmailProfilePhone;
impl sealed::Sealed for EmailProfilePhone {}
impl ScopeSet for EmailProfilePhone {
fn names() -> &'static [&'static str] {
NAMES_EMAIL_PROFILE_PHONE
}
}
impl HasEmail for EmailProfilePhone {}
impl HasProfile for EmailProfilePhone {}
impl HasPhone for EmailProfilePhone {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmailProfilePhoneAddress;
impl sealed::Sealed for EmailProfilePhoneAddress {}
impl ScopeSet for EmailProfilePhoneAddress {
fn names() -> &'static [&'static str] {
NAMES_EMAIL_PROFILE_PHONE_ADDRESS
}
}
impl HasEmail for EmailProfilePhoneAddress {}
impl HasProfile for EmailProfilePhoneAddress {}
impl HasPhone for EmailProfilePhoneAddress {}
impl HasAddress for EmailProfilePhoneAddress {}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
fn set(slice: &[&'static str]) -> HashSet<&'static str> {
slice.iter().copied().collect()
}
fn union(slices: &[&[&'static str]]) -> HashSet<&'static str> {
slices.iter().flat_map(|s| s.iter().copied()).collect()
}
#[test]
fn openid_names_includes_all_binding_claims() {
let names = set(Openid::names());
for required in [
"iss", "sub", "aud", "exp", "iat",
"nonce", "at_hash", "c_hash", "azp", "auth_time", "acr", "amr", "cat",
] {
assert!(
names.contains(required),
"Openid::names() missing binding claim {required:?} — M72 would refuse a valid token"
);
}
assert_eq!(names, set(BASE_CLAIMS));
}
#[test]
fn email_names_is_union_of_base_and_email() {
assert_eq!(set(Email::names()), union(&[BASE_CLAIMS, EMAIL_CLAIMS]));
}
#[test]
fn profile_names_is_union_of_base_and_profile() {
assert_eq!(set(Profile::names()), union(&[BASE_CLAIMS, PROFILE_CLAIMS]));
}
#[test]
fn email_profile_names_is_union_of_base_email_profile() {
assert_eq!(
set(EmailProfile::names()),
union(&[BASE_CLAIMS, EMAIL_CLAIMS, PROFILE_CLAIMS]),
);
}
#[test]
fn email_profile_phone_names_is_union_of_base_email_profile_phone() {
assert_eq!(
set(EmailProfilePhone::names()),
union(&[BASE_CLAIMS, EMAIL_CLAIMS, PROFILE_CLAIMS, PHONE_CLAIMS]),
);
}
#[test]
fn email_profile_phone_address_is_union_of_components() {
assert_eq!(
set(EmailProfilePhoneAddress::names()),
union(&[
BASE_CLAIMS,
EMAIL_CLAIMS,
PROFILE_CLAIMS,
PHONE_CLAIMS,
ADDRESS_CLAIMS,
]),
);
}
}