mod aud;
mod azp;
mod common;
mod org;
mod role;
mod scope;
use crate::WorkspaceId;
use serde::{Deserialize, Serialize};
#[doc(inline)]
pub use aud::Audience;
#[doc(inline)]
pub use azp::Azp;
#[doc(inline)]
pub use org::Org;
#[doc(inline)]
pub use role::{Role, RoleError, RoleSet};
#[doc(inline)]
pub use scope::*;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
#[serde(deserialize_with = "deserialize_workspace_id")]
pub workspace: WorkspaceId,
pub iss: String,
pub sub: String,
pub aud: Audience,
pub iat: u64,
pub exp: u64,
pub azp: Option<Azp>,
pub scope: Scope,
#[serde(default, alias = "role", alias = "roles")]
pub role_set: RoleSet,
}
#[cfg(feature = "cached")]
const TOKEN_EXPIRY_LEEWAY_SECONDS: u64 = 60;
#[cfg(feature = "cached")]
impl cached::CanExpire for Claims {
fn is_expired(&self) -> bool {
(self.exp + TOKEN_EXPIRY_LEEWAY_SECONDS) < chrono::offset::Utc::now().timestamp() as u64
}
}
fn deserialize_workspace_id<'de, D>(deserializer: D) -> Result<WorkspaceId, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::str::FromStr;
let id = String::deserialize(deserializer)?;
let parts: Vec<&str> = id.split(":").collect();
match parts.len() {
1 => WorkspaceId::from_str(&id).map_err(serde::de::Error::custom),
2 => {
if parts[0] != "ws" {
return Err(serde::de::Error::custom(format!(
"Invalid workspace ID prefix: {}",
parts[0]
)));
}
WorkspaceId::from_str(parts[1]).map_err(serde::de::Error::custom)
}
_ => Err(serde::de::Error::custom(format!(
"Invalid workspace ID: {id}"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_deserialize_legacy_workspace_id() {
let json_data = json!({
"workspace": "ws:7366ITCXSAPCH5TN",
"iss": "http://example.com",
"sub": "user123",
"aud": "example_audience",
"iat": 1622547800,
"exp": 1622547900,
"azp": "OIDC",
"scope": "read write",
"role": "admin"
});
let claims: Claims =
serde_json::from_value(json_data).expect("Failed to deserialize Claims");
assert_eq!(
claims.workspace,
WorkspaceId::try_from("7366ITCXSAPCH5TN").unwrap()
);
}
#[test]
fn test_deserialize_workspace_id() {
let json_data = json!({
"workspace": "7366ITCXSAPCH5TN",
"iss": "http://example.com",
"sub": "user123",
"aud": "example_audience",
"iat": 1622547800,
"exp": 1622547900,
"azp": "OIDC",
"scope": "read write",
"role": "admin"
});
let claims: Claims =
serde_json::from_value(json_data).expect("Failed to deserialize Claims");
assert_eq!(
claims.workspace,
WorkspaceId::try_from("7366ITCXSAPCH5TN").unwrap()
);
assert_eq!(claims.iss, "http://example.com");
assert_eq!(claims.sub, "user123");
assert_eq!(claims.aud, Audience::new("example_audience"));
assert_eq!(claims.iat, 1622547800);
assert_eq!(claims.exp, 1622547900);
assert_eq!(claims.azp, Some(Azp::RootProvider));
assert_eq!(claims.scope, Scope::parse("read write"));
assert!(claims.role_set.has_role(Role::Admin));
}
mod role_claim {
use serde_json::Value;
use super::*;
fn build_claims(field: &str, role_set: Option<Value>) -> Value {
let mut json_data = json!({
"workspace": "7366ITCXSAPCH5TN",
"iss": "http://example.com",
"sub": "user123",
"aud": "example_audience",
"iat": 1622547800,
"exp": 1622547900,
"azp": "OIDC",
"scope": "read write",
});
if let Some(role_set) = role_set {
if let Some(obj) = json_data.as_object_mut() {
obj.insert(field.into(), role_set);
}
};
json_data
}
#[test]
fn test_deserialize_no_role() {
fn check(claim_name: &str) {
let json_data = build_claims(claim_name, None);
let claims: Claims =
serde_json::from_value(json_data).expect("Failed to deserialize Claims");
assert!(claims.role_set.is_empty())
}
check("role");
check("roles");
check("role_set");
}
#[test]
fn test_deserialize_one_role() {
fn check(claim_name: &str) {
let json_data = build_claims(claim_name, Some(json!("admin")));
let claims: Claims =
serde_json::from_value(json_data).expect("Failed to deserialize Claims");
assert!(claims.role_set.has_role(Role::Admin));
assert_eq!(claims.role_set.len(), 1);
}
check("role");
check("roles");
check("role_set");
}
#[test]
fn test_deserialize_array_of_one_role() {
fn check(claim_name: &str) {
let json_data = build_claims(claim_name, Some(json!(["admin"])));
let claims: Claims =
serde_json::from_value(json_data).expect("Failed to deserialize Claims");
assert!(claims.role_set.has_role(Role::Admin));
assert_eq!(claims.role_set.len(), 1);
}
check("role");
check("roles");
check("role_set");
}
#[test]
fn test_deserialize_muliple_roles() {
fn check(claim_name: &str) {
let json_data = build_claims(claim_name, Some(json!(["admin", "member"])));
let claims: Claims =
serde_json::from_value(json_data).expect("Failed to deserialize Claims");
assert!(claims.role_set.has_role(Role::Admin));
assert!(claims.role_set.has_role(Role::Member));
assert_eq!(claims.role_set.len(), 2);
}
check("role");
check("roles");
check("role_set");
}
#[test]
fn test_deserialize_empty_roles_array() {
fn check(claim_name: &str) {
let json_data = build_claims(claim_name, Some(json!([])));
let claims: Claims =
serde_json::from_value(json_data).expect("Failed to deserialize Claims");
assert!(claims.role_set.is_empty());
}
check("role");
check("roles");
check("role_set");
}
}
}