use cedar_policy::{ContextJsonError, ParseErrors, RequestValidationError};
use serde::{Deserialize, Serialize};
use serde_json::Error as SerdeJsonError;
use crate::entity_builder::MultiIssuerEntityError;
use crate::entity_builder::{BuildEntityError, BuildUnsignedEntityError};
use crate::jwt::JwtProcessingError;
use cedar_policy::entities_errors::EntitiesError;
#[derive(Debug, thiserror::Error)]
pub enum MultiIssuerValidationError {
#[error("Token input validation failed: {0}")]
TokenInput(#[from] TokenInputError),
#[error("Empty token array")]
EmptyTokenArray,
#[error("Could not validate tokens.")]
TokenValidationFailed,
#[error("Invalid JSON in context field")]
InvalidContextJson,
#[error("Missing issuer claim in JWT")]
MissingIssuer,
#[error("JWT claims are not a JSON object: {0}")]
InvalidClaims(&'static str),
}
#[derive(Debug, thiserror::Error)]
pub enum TokenInputError {
#[error("Empty mapping string")]
EmptyMapping,
#[error("Empty payload")]
EmptyPayload,
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum BatchValidationError {
#[error("Empty items array")]
EmptyItems,
#[error("Empty tokens array")]
EmptyTokens,
#[error("Context for item {index} must be a JSON object")]
InvalidItemContext { index: usize },
}
#[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, thiserror::Error)]
#[serde(tag = "variant", rename_all = "snake_case")]
pub enum BatchItemError {
#[error("item {item_index}: action parse failed: {message}")]
ActionParse { message: String, item_index: usize },
#[error("item {item_index}: resource build failed: {message}")]
ResourceBuild { message: String, item_index: usize },
#[error("item {item_index}: context build failed: {message}")]
ContextBuild { message: String, item_index: usize },
#[error("item {item_index}: principal build failed: {message}")]
PrincipalBuild { message: String, item_index: usize },
#[error("item {item_index}: schema validation failed: {message}")]
SchemaValidation { message: String, item_index: usize },
#[error("item {item_index}: multi-issuer entity build failed: {message}")]
MultiIssuerEntity { message: String, item_index: usize },
#[error("item {item_index}: request validation failed: {message}")]
RequestValidation { message: String, item_index: usize },
}
impl BatchItemError {
#[must_use]
pub const fn category(&self) -> &'static str {
match self {
Self::ActionParse { .. } => "action_parse",
Self::ResourceBuild { .. } => "resource_build",
Self::ContextBuild { .. } => "context_build",
Self::PrincipalBuild { .. } => "principal_build",
Self::SchemaValidation { .. } => "schema_validation",
Self::MultiIssuerEntity { .. } => "multi_issuer_entity",
Self::RequestValidation { .. } => "request_validation",
}
}
#[must_use]
pub const fn item_index(&self) -> usize {
match self {
Self::ActionParse { item_index, .. }
| Self::ResourceBuild { item_index, .. }
| Self::ContextBuild { item_index, .. }
| Self::PrincipalBuild { item_index, .. }
| Self::SchemaValidation { item_index, .. }
| Self::MultiIssuerEntity { item_index, .. }
| Self::RequestValidation { item_index, .. } => *item_index,
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum AuthorizeError {
#[error(transparent)]
ProcessTokens(#[from] JwtProcessingError),
#[error("could not parse action: {0}")]
Action(Box<ParseErrors>),
#[error("could not parse identifier: {0}")]
IdentifierParsing(Box<ParseErrors>),
#[error("could not create context: {0}")]
CreateContext(#[from] Box<ContextJsonError>),
#[error(transparent)]
InvalidPrincipal(#[from] Box<InvalidPrincipalError>),
#[error("request validation error: {0}")]
RequestValidation(#[from] Box<RequestValidationError>),
#[error("failed to validate Cedar entities: {0:?}")]
ValidateEntities(#[from] Box<EntitiesError>),
#[error("could not convert entities to json: {0}")]
EntitiesToJson(SerdeJsonError),
#[error("Failed to build context: {0}")]
BuildContext(#[from] BuildContextError),
#[error(transparent)]
BuildEntity(#[from] BuildEntityError),
#[error("failed to build role entities for unsigned request: {0}")]
BuildUnsignedRoleEntity(#[from] BuildUnsignedEntityError),
#[error(transparent)]
MultiIssuerValidation(#[from] MultiIssuerValidationError),
#[error(transparent)]
BatchValidation(#[from] BatchValidationError),
#[error("Multi-issuer entity building failed: {0}")]
MultiIssuerEntity(MultiIssuerEntityError),
}
impl From<ParseErrors> for AuthorizeError {
fn from(err: ParseErrors) -> Self {
Self::Action(Box::new(err))
}
}
impl From<InvalidPrincipalError> for AuthorizeError {
fn from(err: InvalidPrincipalError) -> Self {
Self::InvalidPrincipal(Box::new(err))
}
}
#[derive(Debug, thiserror::Error)]
pub enum BuildContextError {
#[error("failed to merge JSON objects due to conflicting keys: {0}")]
KeyConflict(String),
#[error(transparent)]
DeserializeFromJson(#[from] Box<ContextJsonError>),
#[error("failed to find the action `{0}` in the schema")]
UnknownAction(String),
#[error("failed to build entity reference for `{0}` since an entity id was not provided")]
MissingEntityId(String),
#[error("invalid action context type: {0}. expected: {1}")]
InvalidKind(String, String),
#[error("failed to parse the entity name `{0}`: {1}")]
ParseEntityName(String, Box<ParseErrors>),
#[error("failed to create Cedar context: {0}")]
ContextCreation(String),
}
impl From<ContextJsonError> for BuildContextError {
fn from(err: ContextJsonError) -> Self {
BuildContextError::DeserializeFromJson(Box::new(err))
}
}
#[derive(Debug, derive_more::Error, derive_more::Display)]
#[display("The request for `{principal}` does not conform to the schema: {err}")]
pub struct InvalidPrincipalError {
pub principal: cedar_policy::EntityUid,
pub err: RequestValidationError,
}
impl InvalidPrincipalError {
pub fn new(principal: &cedar_policy::EntityUid, err: RequestValidationError) -> Self {
InvalidPrincipalError {
principal: principal.clone(),
err,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn batch_item_error_category_matches_wire_variant() {
let cases = vec![
BatchItemError::ActionParse {
message: String::new(),
item_index: 0,
},
BatchItemError::ResourceBuild {
message: String::new(),
item_index: 0,
},
BatchItemError::ContextBuild {
message: String::new(),
item_index: 0,
},
BatchItemError::PrincipalBuild {
message: String::new(),
item_index: 0,
},
BatchItemError::SchemaValidation {
message: String::new(),
item_index: 0,
},
BatchItemError::MultiIssuerEntity {
message: String::new(),
item_index: 0,
},
BatchItemError::RequestValidation {
message: String::new(),
item_index: 0,
},
];
for error in cases {
let json = serde_json::to_value(&error).expect("serialize");
let wire_variant = json
.get("variant")
.and_then(|v| v.as_str())
.expect("missing variant field");
assert_eq!(
wire_variant,
error.category(),
"wire variant {wire_variant:?} must match category() for {error:?}",
);
}
}
}