use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
pub const MAX_LABEL_LEN: usize = 256;
pub const MAX_CAPABILITIES: usize = 64;
pub(crate) fn valid_label(value: &str) -> bool {
!value.is_empty() && value.len() <= MAX_LABEL_LEN && !value.chars().any(char::is_control)
}
macro_rules! label {
($name:ident, $doc:literal) => {
#[doc = $doc]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> Result<Self> {
let value = value.into();
if !valid_label(&value) {
return Err(Error::InvalidInput);
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl TryFrom<String> for $name {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
Self::new(value)
}
}
impl From<$name> for String {
fn from(value: $name) -> Self {
value.0
}
}
};
}
label!(Audience, "Exact logical recipient; independent of DNS and transport.");
label!(Capability, "Exact application-defined permission; no implicit wildcards or hierarchy.");
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Purpose {
Session,
Refresh,
Access,
WebSocket,
Internal
}
impl Purpose {
pub(crate) fn as_bytes(self) -> &'static [u8] {
match self {
Self::Session => b"session",
Self::Refresh => b"refresh",
Self::Access => b"access",
Self::WebSocket => b"websocket",
Self::Internal => b"internal"
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TokenId(pub [u8; 16]);
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Claims {
pub subject: String,
pub audience: Audience,
pub purpose: Purpose,
pub capabilities: BTreeSet<Capability>,
pub issued_at: u64,
pub not_before: u64,
pub expires_at: u64
}
impl Claims {
pub(crate) fn check_shape(&self) -> Result<()> {
if !valid_label(&self.subject)
|| self.capabilities.len() > MAX_CAPABILITIES
|| self.issued_at > self.not_before
|| self.not_before >= self.expires_at
{
return Err(Error::InvalidInput);
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Principal {
pub(crate) realm: String,
pub(crate) issuer: String,
pub(crate) jti: TokenId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) session: Option<crate::fleet::SessionBinding>,
pub(crate) claims: Claims
}
impl Principal {
pub fn session_id(&self) -> Option<crate::SessionId> {
self.session.as_ref().map(|session| session.id)
}
pub fn realm(&self) -> &str {
&self.realm
}
pub fn issuer(&self) -> &str {
&self.issuer
}
pub fn jti(&self) -> TokenId {
self.jti
}
pub fn subject(&self) -> &str {
&self.claims.subject
}
pub fn claims(&self) -> &Claims {
&self.claims
}
pub fn has_capability(
&self,
capability: &Capability
) -> bool {
self.claims.capabilities.contains(capability)
}
}
#[derive(Clone, Debug)]
pub struct Validation {
pub audience: Audience,
pub purpose: Purpose,
pub required_capabilities: BTreeSet<Capability>
}
pub trait ValidationHook {
fn check(
&self,
principal: &Principal,
now: u64
) -> Result<()>;
fn check_lapsed(
&self,
_: &Principal,
_: u64
) -> Result<()> {
Err(Error::Expired)
}
}
pub struct AllowReusable;
impl ValidationHook for AllowReusable {
fn check(
&self,
_: &Principal,
_: u64
) -> Result<()> {
Ok(())
}
}