pub const GRAPHQL_CEDAR_SCHEMA: &str = r#"{
"CodeMode": {
"entityTypes": {
"Operation": {
"shape": {
"type": "Record",
"attributes": {
"operationType": { "type": "String", "required": true },
"operationName": { "type": "String", "required": false },
"rootFields": { "type": "Set", "element": { "type": "String" } },
"accessedTypes": { "type": "Set", "element": { "type": "String" } },
"accessedFields": { "type": "Set", "element": { "type": "String" } },
"depth": { "type": "Long", "required": true },
"fieldCount": { "type": "Long", "required": true },
"estimatedCost": { "type": "Long", "required": true },
"hasIntrospection": { "type": "Boolean", "required": true },
"accessesSensitiveData": { "type": "Boolean", "required": true },
"sensitiveCategories": { "type": "Set", "element": { "type": "String" } }
}
}
},
"Server": {
"shape": {
"type": "Record",
"attributes": {
"serverId": { "type": "String", "required": true },
"serverType": { "type": "String", "required": true },
"maxDepth": { "type": "Long", "required": true },
"maxFieldCount": { "type": "Long", "required": true },
"maxCost": { "type": "Long", "required": true },
"allowWrite": { "type": "Boolean", "required": true },
"allowDelete": { "type": "Boolean", "required": true },
"allowAdmin": { "type": "Boolean", "required": true },
"blockedOperations": { "type": "Set", "element": { "type": "String" } },
"allowedOperations": { "type": "Set", "element": { "type": "String" } },
"blockedFields": { "type": "Set", "element": { "type": "String" } }
}
}
}
},
"actions": {
"Read": {
"appliesTo": {
"principalTypes": ["Operation"],
"resourceTypes": ["Server"]
}
},
"Write": {
"appliesTo": {
"principalTypes": ["Operation"],
"resourceTypes": ["Server"]
}
},
"Delete": {
"appliesTo": {
"principalTypes": ["Operation"],
"resourceTypes": ["Server"]
}
},
"Admin": {
"appliesTo": {
"principalTypes": ["Operation"],
"resourceTypes": ["Server"]
}
}
}
}
}"#;
pub const OPENAPI_CEDAR_SCHEMA: &str = r#"{
"CodeMode": {
"entityTypes": {
"Script": {
"shape": {
"type": "Record",
"attributes": {
"scriptType": { "type": "String", "required": true },
"hasWrites": { "type": "Boolean", "required": true },
"hasDeletes": { "type": "Boolean", "required": true },
"totalApiCalls": { "type": "Long", "required": true },
"readCalls": { "type": "Long", "required": true },
"writeCalls": { "type": "Long", "required": true },
"deleteCalls": { "type": "Long", "required": true },
"accessedPaths": { "type": "Set", "element": { "type": "String" } },
"accessedMethods": { "type": "Set", "element": { "type": "String" } },
"pathPatterns": { "type": "Set", "element": { "type": "String" } },
"loopIterations": { "type": "Long", "required": true },
"nestingDepth": { "type": "Long", "required": true },
"scriptLength": { "type": "Long", "required": true },
"accessesSensitivePath": { "type": "Boolean", "required": true },
"hasUnboundedLoop": { "type": "Boolean", "required": true },
"hasDynamicPath": { "type": "Boolean", "required": true },
"outputFields": { "type": "Set", "element": { "type": "String" } },
"hasOutputDeclaration": { "type": "Boolean", "required": true },
"hasSpreadInOutput": { "type": "Boolean", "required": true }
}
}
},
"Server": {
"shape": {
"type": "Record",
"attributes": {
"serverId": { "type": "String", "required": true },
"serverType": { "type": "String", "required": true },
"maxApiCalls": { "type": "Long", "required": true },
"maxLoopIterations": { "type": "Long", "required": true },
"maxScriptLength": { "type": "Long", "required": true },
"maxNestingDepth": { "type": "Long", "required": true },
"executionTimeoutSeconds": { "type": "Long", "required": true },
"allowWrite": { "type": "Boolean", "required": true },
"allowDelete": { "type": "Boolean", "required": true },
"allowAdmin": { "type": "Boolean", "required": true },
"blockedOperations": { "type": "Set", "element": { "type": "String" } },
"allowedOperations": { "type": "Set", "element": { "type": "String" } },
"allowedMethods": { "type": "Set", "element": { "type": "String" } },
"blockedMethods": { "type": "Set", "element": { "type": "String" } },
"allowedPathPatterns": { "type": "Set", "element": { "type": "String" } },
"blockedPathPatterns": { "type": "Set", "element": { "type": "String" } },
"sensitivePathPatterns": { "type": "Set", "element": { "type": "String" } },
"autoApproveReadOnly": { "type": "Boolean", "required": true },
"maxApiCallsForAutoApprove": { "type": "Long", "required": true },
"internalBlockedFields": { "type": "Set", "element": { "type": "String" } },
"outputBlockedFields": { "type": "Set", "element": { "type": "String" } },
"requireOutputDeclaration": { "type": "Boolean", "required": true }
}
}
}
},
"actions": {
"Read": {
"appliesTo": {
"principalTypes": ["Script"],
"resourceTypes": ["Server"]
}
},
"Write": {
"appliesTo": {
"principalTypes": ["Script"],
"resourceTypes": ["Server"]
}
},
"Delete": {
"appliesTo": {
"principalTypes": ["Script"],
"resourceTypes": ["Server"]
}
},
"Admin": {
"appliesTo": {
"principalTypes": ["Script"],
"resourceTypes": ["Server"]
}
}
}
}
}"#;
pub const SQL_CEDAR_SCHEMA: &str = r#"{
"CodeMode": {
"entityTypes": {
"Statement": {
"shape": {
"type": "Record",
"attributes": {
"statementType": { "type": "String", "required": true },
"tables": { "type": "Set", "element": { "type": "String" } },
"columns": { "type": "Set", "element": { "type": "String" } },
"hasWhere": { "type": "Boolean", "required": true },
"hasLimit": { "type": "Boolean", "required": true },
"hasOrderBy": { "type": "Boolean", "required": true },
"estimatedRows": { "type": "Long", "required": true },
"joinCount": { "type": "Long", "required": true },
"subqueryCount": { "type": "Long", "required": true }
}
}
},
"Server": {
"shape": {
"type": "Record",
"attributes": {
"serverId": { "type": "String", "required": true },
"serverType": { "type": "String", "required": true },
"maxRows": { "type": "Long", "required": true },
"maxJoins": { "type": "Long", "required": true },
"allowWrite": { "type": "Boolean", "required": true },
"allowDelete": { "type": "Boolean", "required": true },
"allowAdmin": { "type": "Boolean", "required": true },
"blockedOperations": { "type": "Set", "element": { "type": "String" } },
"allowedOperations": { "type": "Set", "element": { "type": "String" } },
"blockedTables": { "type": "Set", "element": { "type": "String" } },
"blockedColumns": { "type": "Set", "element": { "type": "String" } }
}
}
}
},
"actions": {
"Read": {
"appliesTo": {
"principalTypes": ["Statement"],
"resourceTypes": ["Server"]
}
},
"Write": {
"appliesTo": {
"principalTypes": ["Statement"],
"resourceTypes": ["Server"]
}
},
"Delete": {
"appliesTo": {
"principalTypes": ["Statement"],
"resourceTypes": ["Server"]
}
},
"Admin": {
"appliesTo": {
"principalTypes": ["Statement"],
"resourceTypes": ["Server"]
}
}
}
}
}"#;
#[derive(Debug, Clone)]
pub struct PolicyTemplate {
pub id: &'static str,
pub description: &'static str,
pub statement: &'static str,
pub valid_for: &'static [&'static str],
}
pub const SHARED_TEMPLATES: &[PolicyTemplate] = &[
PolicyTemplate {
id: "PermitAllReads",
description: "Permits all read operations.",
statement: r#"permit(
principal,
action == CodeMode::Action::"Read",
resource
);"#,
valid_for: &["graphql-api", "openapi-api", "sql-api"],
},
PolicyTemplate {
id: "PermitWritesWhenEnabled",
description: "Permits write operations when allowWrite is true.",
statement: r#"permit(
principal,
action == CodeMode::Action::"Write",
resource
) when {
resource.allowWrite == true
};"#,
valid_for: &["graphql-api", "openapi-api", "sql-api"],
},
PolicyTemplate {
id: "PermitDeletesWhenEnabled",
description: "Permits delete operations when allowDelete is true.",
statement: r#"permit(
principal,
action == CodeMode::Action::"Delete",
resource
) when {
resource.allowDelete == true
};"#,
valid_for: &["graphql-api", "openapi-api", "sql-api"],
},
PolicyTemplate {
id: "PermitAdminWhenEnabled",
description: "Permits admin operations when allowAdmin is true.",
statement: r#"permit(
principal,
action == CodeMode::Action::"Admin",
resource
) when {
resource.allowAdmin == true
};"#,
valid_for: &["graphql-api", "openapi-api", "sql-api"],
},
PolicyTemplate {
id: "ForbidAllDeletes",
description: "Forbids all delete operations.",
statement: r#"forbid(
principal,
action == CodeMode::Action::"Delete",
resource
);"#,
valid_for: &["graphql-api", "openapi-api", "sql-api"],
},
];
pub const GRAPHQL_TEMPLATES: &[PolicyTemplate] = &[
PolicyTemplate {
id: "ForbidExcessiveDepth",
description: "Blocks queries exceeding maxDepth.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.depth > resource.maxDepth
};"#,
valid_for: &["graphql-api"],
},
PolicyTemplate {
id: "ForbidExcessiveFieldCount",
description: "Blocks queries exceeding maxFieldCount.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.fieldCount > resource.maxFieldCount
};"#,
valid_for: &["graphql-api"],
},
PolicyTemplate {
id: "ForbidExcessiveCost",
description: "Blocks queries exceeding maxCost.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.estimatedCost > resource.maxCost
};"#,
valid_for: &["graphql-api"],
},
PolicyTemplate {
id: "ForbidBlockedFields",
description: "Blocks queries accessing blocked fields.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.accessedFields.containsAny(resource.blockedFields)
};"#,
valid_for: &["graphql-api"],
},
PolicyTemplate {
id: "ForbidSensitiveData",
description: "Blocks queries accessing sensitive data.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.accessesSensitiveData == true
};"#,
valid_for: &["graphql-api"],
},
PolicyTemplate {
id: "ForbidBlockedOperations",
description: "Blocks operations in the blocklist.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal has operationName &&
resource.blockedOperations.contains(principal.operationName)
};"#,
valid_for: &["graphql-api"],
},
];
pub const OPENAPI_TEMPLATES: &[PolicyTemplate] = &[
PolicyTemplate {
id: "ForbidExcessiveApiCalls",
description: "Blocks scripts exceeding maxApiCalls.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.totalApiCalls > resource.maxApiCalls
};"#,
valid_for: &["openapi-api"],
},
PolicyTemplate {
id: "ForbidExcessiveNesting",
description: "Blocks scripts exceeding maxNestingDepth.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.nestingDepth > resource.maxNestingDepth
};"#,
valid_for: &["openapi-api"],
},
PolicyTemplate {
id: "ForbidUnboundedLoops",
description: "Blocks scripts with unbounded loops.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.hasUnboundedLoop == true
};"#,
valid_for: &["openapi-api"],
},
PolicyTemplate {
id: "ForbidSensitivePaths",
description: "Blocks scripts accessing sensitive paths.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.accessesSensitivePath == true
};"#,
valid_for: &["openapi-api"],
},
PolicyTemplate {
id: "ForbidOutputBlockedFields",
description: "Blocks scripts that return blocked fields.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.outputFields.containsAny(resource.outputBlockedFields)
};"#,
valid_for: &["openapi-api"],
},
PolicyTemplate {
id: "ForbidSpreadWithoutDeclaration",
description: "Blocks scripts with spread when output declaration is required.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.hasSpreadInOutput == true &&
resource.requireOutputDeclaration == true
};"#,
valid_for: &["openapi-api"],
},
];
pub const SQL_TEMPLATES: &[PolicyTemplate] = &[
PolicyTemplate {
id: "ForbidExcessiveRows",
description: "Blocks queries exceeding maxRows.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.estimatedRows > resource.maxRows
};"#,
valid_for: &["sql-api"],
},
PolicyTemplate {
id: "ForbidExcessiveJoins",
description: "Blocks queries exceeding maxJoins.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.joinCount > resource.maxJoins
};"#,
valid_for: &["sql-api"],
},
PolicyTemplate {
id: "ForbidBlockedTables",
description: "Blocks queries accessing blocked tables.",
statement: r#"forbid(
principal,
action,
resource
) when {
principal.tables.containsAny(resource.blockedTables)
};"#,
valid_for: &["sql-api"],
},
PolicyTemplate {
id: "RequireWhereClause",
description: "Requires WHERE clause for write/delete operations.",
statement: r#"forbid(
principal,
action == CodeMode::Action::"Write",
resource
) when {
principal.hasWhere == false
};
forbid(
principal,
action == CodeMode::Action::"Delete",
resource
) when {
principal.hasWhere == false
};"#,
valid_for: &["sql-api"],
},
];
#[cfg(test)]
mod tests {
use super::*;
use crate::graphql::field_name_to_type;
use crate::policy::types::{get_baseline_policies, get_code_mode_schema_json};
#[cfg(feature = "openapi-code-mode")]
use crate::policy::types::{get_openapi_baseline_policies, get_openapi_code_mode_schema_json};
use crate::policy_annotations::{parse_policy_annotations, PolicyCategory, PolicyRiskLevel};
use crate::schema_exposure::pattern_matches;
use crate::templates::conditional;
use cedar_policy::{PolicySet, Schema};
fn parse_schema(json: &str) -> Result<Schema, String> {
Schema::from_json_str(json).map_err(|e| e.to_string())
}
fn parse_policy(statement: &str) -> Result<PolicySet, String> {
statement
.parse()
.map_err(|e: cedar_policy::ParseErrors| e.to_string())
}
fn validate_policy(schema: &Schema, policy: &PolicySet) -> Result<(), String> {
let validator = cedar_policy::Validator::new(schema.clone());
let result = validator.validate(policy, cedar_policy::ValidationMode::Strict);
if result.validation_passed() {
Ok(())
} else {
let errors: Vec<String> = result.validation_errors().map(|e| e.to_string()).collect();
Err(errors.join("; "))
}
}
#[test]
fn test_graphql_schema_is_valid() {
let result = parse_schema(GRAPHQL_CEDAR_SCHEMA);
assert!(
result.is_ok(),
"GraphQL schema failed to parse: {:?}",
result.err()
);
}
#[test]
fn test_openapi_schema_is_valid() {
let result = parse_schema(OPENAPI_CEDAR_SCHEMA);
assert!(
result.is_ok(),
"OpenAPI schema failed to parse: {:?}",
result.err()
);
}
#[test]
fn test_sql_schema_is_valid() {
let result = parse_schema(SQL_CEDAR_SCHEMA);
assert!(
result.is_ok(),
"SQL schema failed to parse: {:?}",
result.err()
);
}
#[test]
fn test_shared_templates_parse() {
for template in SHARED_TEMPLATES {
let result = parse_policy(template.statement);
assert!(
result.is_ok(),
"Shared template '{}' failed to parse: {:?}",
template.id,
result.err()
);
}
}
#[test]
fn test_shared_templates_validate_against_graphql_schema() {
let schema = parse_schema(GRAPHQL_CEDAR_SCHEMA).expect("Schema should parse");
for template in SHARED_TEMPLATES {
if template.valid_for.contains(&"graphql-api") {
let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_ok(),
"Shared template '{}' failed GraphQL validation: {:?}",
template.id,
result.err()
);
}
}
}
#[test]
fn test_shared_templates_validate_against_openapi_schema() {
let schema = parse_schema(OPENAPI_CEDAR_SCHEMA).expect("Schema should parse");
for template in SHARED_TEMPLATES {
if template.valid_for.contains(&"openapi-api") {
let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_ok(),
"Shared template '{}' failed OpenAPI validation: {:?}",
template.id,
result.err()
);
}
}
}
#[test]
fn test_shared_templates_validate_against_sql_schema() {
let schema = parse_schema(SQL_CEDAR_SCHEMA).expect("Schema should parse");
for template in SHARED_TEMPLATES {
if template.valid_for.contains(&"sql-api") {
let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_ok(),
"Shared template '{}' failed SQL validation: {:?}",
template.id,
result.err()
);
}
}
}
#[test]
fn test_graphql_templates_parse() {
for template in GRAPHQL_TEMPLATES {
let result = parse_policy(template.statement);
assert!(
result.is_ok(),
"GraphQL template '{}' failed to parse: {:?}",
template.id,
result.err()
);
}
}
#[test]
fn test_graphql_templates_validate_against_schema() {
let schema = parse_schema(GRAPHQL_CEDAR_SCHEMA).expect("Schema should parse");
for template in GRAPHQL_TEMPLATES {
let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_ok(),
"GraphQL template '{}' failed validation: {:?}",
template.id,
result.err()
);
}
}
#[test]
fn test_openapi_templates_parse() {
for template in OPENAPI_TEMPLATES {
let result = parse_policy(template.statement);
assert!(
result.is_ok(),
"OpenAPI template '{}' failed to parse: {:?}",
template.id,
result.err()
);
}
}
#[test]
fn test_openapi_templates_validate_against_schema() {
let schema = parse_schema(OPENAPI_CEDAR_SCHEMA).expect("Schema should parse");
for template in OPENAPI_TEMPLATES {
let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_ok(),
"OpenAPI template '{}' failed validation: {:?}",
template.id,
result.err()
);
}
}
#[test]
fn test_sql_templates_parse() {
for template in SQL_TEMPLATES {
let result = parse_policy(template.statement);
assert!(
result.is_ok(),
"SQL template '{}' failed to parse: {:?}",
template.id,
result.err()
);
}
}
#[test]
fn test_sql_templates_validate_against_schema() {
let schema = parse_schema(SQL_CEDAR_SCHEMA).expect("Schema should parse");
for template in SQL_TEMPLATES {
let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_ok(),
"SQL template '{}' failed validation: {:?}",
template.id,
result.err()
);
}
}
#[test]
fn test_graphql_templates_fail_against_openapi_schema() {
let schema = parse_schema(OPENAPI_CEDAR_SCHEMA).expect("Schema should parse");
let template = &GRAPHQL_TEMPLATES[0]; let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_err(),
"GraphQL template '{}' should NOT validate against OpenAPI schema",
template.id
);
}
#[test]
fn test_openapi_templates_fail_against_graphql_schema() {
let schema = parse_schema(GRAPHQL_CEDAR_SCHEMA).expect("Schema should parse");
let template = &OPENAPI_TEMPLATES[0]; let policy = parse_policy(template.statement).expect("Policy should parse");
let result = validate_policy(&schema, &policy);
assert!(
result.is_err(),
"OpenAPI template '{}' should NOT validate against GraphQL schema",
template.id
);
}
#[test]
fn test_all_schemas_have_unified_actions() {
let schemas = [
("GraphQL", GRAPHQL_CEDAR_SCHEMA),
("OpenAPI", OPENAPI_CEDAR_SCHEMA),
("SQL", SQL_CEDAR_SCHEMA),
];
let expected_actions = ["Read", "Write", "Delete", "Admin"];
for (name, schema_json) in schemas {
let schema_value: serde_json::Value =
serde_json::from_str(schema_json).expect("Schema should be valid JSON");
let actions = schema_value["CodeMode"]["actions"]
.as_object()
.expect("Schema should have actions");
for action in &expected_actions {
assert!(
actions.contains_key(*action),
"{} schema is missing unified action: {}",
name,
action
);
}
}
}
}