use test_utils::assert_eq;
use tokio::test;
use super::utils::*;
use crate::log::interface::LogStorage;
use crate::{
tests::utils::cedarling_util::get_cedarling_with_callback,
tests::utils::test_helpers::{create_test_principal, create_test_unsigned_request},
};
static POLICY_STORE_RAW_YAML: &str =
include_str!("../../../test_files/policy-store_no_trusted_issuers.yaml");
#[test]
async fn test_authorize_unsigned_single_principal_allow() {
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|_| {},
)
.await;
let request = create_test_unsigned_request(
"Jans::Action::\"UpdateForTestPrincipals\"",
Some(
create_test_principal("Jans::TestPrincipal1", "id1", json!({"is_ok": true}))
.expect("principal should build"),
),
create_test_principal(
"Jans::Issue",
"random_id",
json!({"org_id": "some_long_id", "country": "US"}),
)
.expect("resource should build"),
);
let result = cedarling
.authorize_unsigned(request)
.await
.expect("request should be parsed without errors");
assert!(result.decision, "request result should be allowed");
assert_eq!(
result.response.decision(),
Decision::Allow,
"cedar response should allow"
);
}
#[test]
async fn test_authorize_unsigned_single_principal_deny() {
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|_| {},
)
.await;
let request = create_test_unsigned_request(
"Jans::Action::\"UpdateForTestPrincipals\"",
Some(
create_test_principal("Jans::TestPrincipal1", "id1", json!({"is_ok": false}))
.expect("principal should build"),
),
create_test_principal(
"Jans::Issue",
"random_id",
json!({"org_id": "some_long_id", "country": "US"}),
)
.expect("resource should build"),
);
let result = cedarling
.authorize_unsigned(request)
.await
.expect("request should be parsed without errors");
assert!(!result.decision, "request result should be denied");
assert_eq!(
result.response.decision(),
Decision::Deny,
"cedar response should deny"
);
}
#[test]
async fn test_authorize_unsigned_no_principal_partial_eval() {
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|_| {},
)
.await;
let request = create_test_unsigned_request(
"Jans::Action::\"OpenPublicIssue\"",
None,
create_test_principal(
"Jans::Issue",
"random_id",
json!({"org_id": "some_long_id", "country": "US"}),
)
.expect("resource should build"),
);
let result = cedarling
.authorize_unsigned(request)
.await
.expect("request should be parsed without errors");
assert!(
result.decision,
"partial eval should allow when policy does not depend on principal"
);
}
#[test]
async fn test_authorize_unsigned_no_principal_residual_denies() {
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|_| {},
)
.await;
let request = create_test_unsigned_request(
"Jans::Action::\"UpdateForTestPrincipals\"",
None,
create_test_principal(
"Jans::Issue",
"random_id",
json!({"org_id": "some_long_id", "country": "US"}),
)
.expect("resource should build"),
);
let result = cedarling
.authorize_unsigned(request)
.await
.expect("request should be parsed without errors");
assert!(
!result.decision,
"residual (principal-dependent) policy must fail closed to Deny"
);
assert_eq!(
result.response.decision(),
Decision::Deny,
"synthesized cedar response should be Deny"
);
let reason_ids: Vec<String> = result
.response
.diagnostics()
.reason()
.map(ToString::to_string)
.collect();
assert!(
reason_ids.iter().any(|id| id == "5"),
"residual policy id should be reported in diagnostics reason set, got: {reason_ids:?}"
);
}
#[test]
async fn test_get_matching_policies_unsigned_both_branches() {
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|_| {},
)
.await;
let principal = create_test_principal("Jans::TestPrincipal1", "id1", json!({"is_ok": true}))
.expect("principal should build");
let resource = create_test_principal(
"Jans::Issue",
"random_id",
json!({"org_id": "x", "country": "US"}),
)
.expect("resource should build");
let actions = vec!["Jans::Action::\"UpdateForTestPrincipals\"".to_string()];
let with_principal = cedarling
.get_matching_policies_unsigned(Some(&principal), &actions, std::slice::from_ref(&resource))
.expect("Some-principal branch should succeed");
assert!(
with_principal.iter().any(|p| p.id == "5"),
"policy 5 should match when principal type is provided, got: {with_principal:?}"
);
let no_principal = cedarling
.get_matching_policies_unsigned(None, &actions, std::slice::from_ref(&resource))
.expect("None-principal branch should succeed");
assert!(
no_principal.iter().any(|p| p.id == "5"),
"policy 5 should still match when principal is absent, got: {no_principal:?}"
);
}
#[test]
async fn test_annotations_of_determining_policies() {
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|_| {},
)
.await;
let request = create_test_unsigned_request(
"Jans::Action::\"UpdateForTestPrincipals\"",
Some(
create_test_principal("Jans::TestPrincipal1", "id1", json!({"is_ok": true}))
.expect("principal should build"),
),
create_test_principal(
"Jans::Issue",
"random_id",
json!({"org_id": "some_long_id", "country": "US"}),
)
.expect("resource should build"),
);
let result = cedarling
.authorize_unsigned(request)
.await
.expect("request should be parsed without errors");
assert!(result.decision, "request result should be allowed");
let reason: Vec<_> = result.response.diagnostics().reason().collect();
assert!(!reason.is_empty(), "allow decision should have a reason set");
let merged = cedarling.annotations_map(reason.iter().copied());
assert_eq!(merged.get("redirect").map(String::as_str), Some("/upgrade"));
assert_eq!(merged.get("tier").map(String::as_str), Some("premium"));
let redirects = cedarling.annotation_values(reason.iter().copied(), "redirect");
assert_eq!(redirects, ["/upgrade"]);
assert!(
cedarling
.annotation_values(reason.iter().copied(), "absent")
.is_empty()
);
let by_policy = cedarling.annotations_by_policy(reason.iter().copied());
let policy_5 = by_policy
.get("5")
.expect("policy 5 should be a determining policy");
assert_eq!(policy_5.get("redirect").map(String::as_str), Some("/upgrade"));
assert_eq!(policy_5.get("tier").map(String::as_str), Some("premium"));
}
#[test]
async fn test_policy_evaluation_errors_logging_unsigned() {
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|_| {},
)
.await;
let principal = create_test_principal(
"Jans::User",
"user1",
json!({"country": "US", "role": ["Admin"], "sub": "user1"}),
)
.expect("principal should build");
let resource = create_test_principal(
"Jans::Issue",
"issue1",
json!({"org_id": "invalid", "country": "US"}),
)
.expect("resource should build");
let request =
create_test_unsigned_request("Jans::Action::\"AlwaysDeny\"", Some(principal), resource);
let result = cedarling
.authorize_unsigned(request)
.await
.expect("request should be parsed without errors");
let logs = cedarling.pop_logs();
assert!(!logs.is_empty(), "Should have created logs");
let logs_with_request_id: Vec<&serde_json::Value> = logs
.iter()
.filter(|log| log.get("request_id") == Some(&serde_json::json!(result.request_id)))
.collect();
assert!(
!logs_with_request_id.is_empty(),
"Should have logs for the request ID"
);
for log in &logs_with_request_id {
assert!(log.get("id").is_some(), "Log should have an id field");
assert!(
log.get("timestamp").is_some(),
"Log should have a timestamp field"
);
let log_kind = log.get("log_kind").expect("log_kind should exist");
assert!(
log_kind == "Decision" || log_kind == "System" || log_kind == "Metric",
"Log kind should be Decision, System, or Metric, got: {log_kind:?}"
);
if log_kind == "Decision" {
let log_action = log.get("action").expect("Decision log should have action");
assert_eq!(
log_action,
&serde_json::json!("Jans::Action::\"AlwaysDeny\""),
"Decision log should have the correct action"
);
let log_decision = log
.get("decision")
.expect("Decision log should have decision");
assert_eq!(
log_decision,
&serde_json::json!("DENY"),
"Decision log should show DENY decision"
);
}
}
}
#[test]
async fn test_unsigned_authz_works_without_trusted_issuers() {
use crate::JwtConfig;
use jsonwebtoken::Algorithm;
use std::collections::HashSet;
let cedarling = get_cedarling_with_callback(
PolicyStoreSource::Yaml(POLICY_STORE_RAW_YAML.to_string()),
|config| {
config.jwt_config = JwtConfig {
jwks: None,
jwt_sig_validation: true,
jwt_status_validation: false,
signature_algorithms_supported: HashSet::from_iter([
Algorithm::HS256,
Algorithm::RS256,
]),
..Default::default()
};
},
)
.await;
let request = create_test_unsigned_request(
"Jans::Action::\"UpdateForTestPrincipals\"",
Some(
create_test_principal("Jans::TestPrincipal1", "test_id", json!({"is_ok": true}))
.expect("principal should build"),
),
create_test_principal(
"Jans::Issue",
"issue1",
json!({"org_id": "some_id", "country": "US"}),
)
.expect("resource should build"),
);
let result = cedarling
.authorize_unsigned(request)
.await
.expect("unsigned authorization should work without trusted issuers");
assert!(result.decision, "authorization should be allowed");
let logs = cedarling.pop_logs();
assert!(!logs.is_empty(), "Should have created logs");
let warning_logs: Vec<&serde_json::Value> = logs
.iter()
.filter(|log| {
log.get("msg")
.and_then(|m| m.as_str())
.is_some_and(|m| m.contains("signed authorization is unavailable"))
})
.collect();
assert!(
!warning_logs.is_empty(),
"Should have logged a warning about signed authorization being unavailable"
);
}