use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::errors::{MultiIssuerValidationError, TokenInputError};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RequestUnsigned {
pub principal: Option<EntityData>,
pub action: String,
pub resource: EntityData,
pub context: Value,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct EntityData {
#[serde(rename = "cedar_entity_mapping")]
pub cedar_mapping: CedarEntityMapping,
#[serde(flatten)]
pub attributes: HashMap<String, Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct CedarEntityMapping {
#[serde(rename = "entity_type")]
pub entity_type: String,
pub id: String,
}
impl EntityData {
pub fn from_json(entity_data: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str::<Self>(entity_data)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TokenInput {
pub mapping: String,
pub payload: String,
}
impl TokenInput {
#[must_use]
pub fn new(mapping: String, payload: String) -> Self {
Self { mapping, payload }
}
pub fn validate(&self) -> Result<(), TokenInputError> {
if self.mapping.trim().is_empty() {
return Err(TokenInputError::EmptyMapping);
}
if self.payload.trim().is_empty() {
return Err(TokenInputError::EmptyPayload);
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AuthorizeMultiIssuerRequest {
pub tokens: Vec<TokenInput>,
pub resource: EntityData,
pub action: String,
pub context: Option<Value>,
}
impl AuthorizeMultiIssuerRequest {
#[must_use]
pub fn new(tokens: Vec<TokenInput>, resource: EntityData, action: String) -> Self {
Self {
tokens,
resource,
action,
context: None,
}
}
#[must_use]
pub fn new_with_fields(
tokens: Vec<TokenInput>,
resource: EntityData,
action: String,
context: Option<Value>,
) -> Self {
Self {
tokens,
resource,
action,
context,
}
}
pub fn validate(&self) -> Result<(), MultiIssuerValidationError> {
if self.tokens.is_empty() {
return Err(MultiIssuerValidationError::EmptyTokenArray);
}
if let Some(ref context) = self.context
&& !context.is_object()
{
return Err(MultiIssuerValidationError::InvalidContextJson);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use test_utils::token_claims::generate_token_using_claims;
fn create_test_token(mapping: &str, issuer: &str, sub: &str) -> TokenInput {
let claims = json!({
"sub": sub,
"iat": 1_516_239_022,
"iss": issuer
});
let token_string = generate_token_using_claims(&claims);
TokenInput::new(mapping.to_string(), token_string)
}
#[test]
fn test_token_input_creation() {
let token = create_test_token("Jans::Access_Token", "https://example.com", "1234567890");
assert_eq!(token.mapping, "Jans::Access_Token");
assert!(token.payload.contains('.')); }
#[test]
fn test_token_input_validate_success() {
let token = create_test_token("Jans::Access_Token", "https://example.com", "1234567890");
let result = token.validate();
assert!(result.is_ok());
}
#[test]
fn test_token_input_validate_empty_mapping() {
let token = TokenInput::new(String::new(), "valid.jwt.token".to_string());
let result = token.validate();
assert!(matches!(result, Err(TokenInputError::EmptyMapping)));
}
#[test]
fn test_token_input_validate_empty_payload() {
let token = TokenInput::new("Jans::Access_Token".to_string(), String::new());
let result = token.validate();
assert!(matches!(result, Err(TokenInputError::EmptyPayload)));
}
#[test]
fn test_authorize_multi_issuer_request_creation() {
let tokens = vec![
create_test_token("Jans::Access_Token", "https://example.com", "1234567890"),
create_test_token("Jans::Id_Token", "https://example.com", "1234567890"),
];
let resource = EntityData {
cedar_mapping: CedarEntityMapping {
entity_type: "Document".to_string(),
id: "doc123".to_string(),
},
attributes: HashMap::new(),
};
let request =
AuthorizeMultiIssuerRequest::new(tokens.clone(), resource.clone(), "Read".to_string());
assert_eq!(request.tokens.len(), 2);
assert_eq!(request.resource, resource);
assert_eq!(request.action, "Read");
assert!(request.context.is_none());
}
#[test]
fn test_authorize_multi_issuer_request_with_fields() {
let tokens = vec![create_test_token(
"Jans::Access_Token",
"https://example.com",
"1234567890",
)];
let resource = EntityData {
cedar_mapping: CedarEntityMapping {
entity_type: "Document".to_string(),
id: "doc123".to_string(),
},
attributes: HashMap::new(),
};
let action = "Read".to_string();
let context = Some(json!({"location": "miami"}));
let request = AuthorizeMultiIssuerRequest::new_with_fields(
tokens,
resource.clone(),
action.clone(),
context.clone(),
);
assert_eq!(request.tokens.len(), 1);
assert_eq!(request.resource, resource);
assert_eq!(request.action, action);
assert_eq!(request.context, context);
}
#[test]
fn test_authorize_multi_issuer_request_validation_success() {
let tokens = vec![
create_test_token("Jans::Access_Token", "https://example.com", "1234567890"),
create_test_token("Jans::Id_Token", "https://example.com", "1234567890"),
];
let resource = EntityData {
cedar_mapping: CedarEntityMapping {
entity_type: "Document".to_string(),
id: "doc123".to_string(),
},
attributes: HashMap::new(),
};
let request = AuthorizeMultiIssuerRequest::new(tokens, resource, "Read".to_string());
assert!(request.validate().is_ok());
}
#[test]
fn test_authorize_multi_issuer_request_validation_empty_tokens() {
let resource = EntityData {
cedar_mapping: CedarEntityMapping {
entity_type: "Document".to_string(),
id: "doc123".to_string(),
},
attributes: HashMap::new(),
};
let request = AuthorizeMultiIssuerRequest::new(vec![], resource, "Read".to_string());
let result = request.validate();
assert!(matches!(
result,
Err(MultiIssuerValidationError::EmptyTokenArray)
));
}
#[test]
fn test_authorize_multi_issuer_request_validation_invalid_token() {
let tokens = vec![TokenInput::new(
"valid-mapping".to_string(), "some-payload".to_string(),
)];
let resource = EntityData {
cedar_mapping: CedarEntityMapping {
entity_type: "Document".to_string(),
id: "doc123".to_string(),
},
attributes: HashMap::new(),
};
let request = AuthorizeMultiIssuerRequest::new(tokens, resource, "Read".to_string());
let result = request.validate();
assert!(result.is_ok());
}
#[test]
fn test_authorize_multi_issuer_request_validation_invalid_json_fields() {
let tokens = vec![create_test_token(
"Jans::Access_Token",
"https://example.com",
"1234567890",
)];
let resource = EntityData {
cedar_mapping: CedarEntityMapping {
entity_type: "Document".to_string(),
id: "doc123".to_string(),
},
attributes: HashMap::new(),
};
let request = AuthorizeMultiIssuerRequest::new_with_fields(
tokens,
resource, "Read".to_string(), Some(json!(123)), );
let result = request.validate();
assert!(matches!(
result,
Err(MultiIssuerValidationError::InvalidContextJson)
));
}
#[test]
fn test_serialization_deserialization() {
let tokens = vec![create_test_token(
"Jans::Access_Token",
"https://example.com",
"1234567890",
)];
let resource = EntityData {
cedar_mapping: CedarEntityMapping {
entity_type: "Document".to_string(),
id: "doc123".to_string(),
},
attributes: HashMap::new(),
};
let request = AuthorizeMultiIssuerRequest::new_with_fields(
tokens,
resource,
"Read".to_string(),
Some(json!({"location": "miami"})),
);
let json = serde_json::to_string(&request).expect("Should serialize");
let deserialized: AuthorizeMultiIssuerRequest =
serde_json::from_str(&json).expect("Should deserialize");
assert_eq!(request, deserialized);
}
}