// v2.8.0 — access policy engine tests. // // The properties that matter most here are the safety ones, because they are // what make the feature usable on a live system rather than a footgun: // // * security OFF is the default and allows everything, so upgrading changes // nothing for any existing deployment; // * enabling is REFUSED without an admin policy, which makes lockout // structurally impossible rather than a matter of operator care; // * removing the last admin of a secured project is refused for the same // reason; // * audit mode evaluates honestly and logs, but allows - the migration path // for switching a live project on; // * once enforcing, an unlisted principal gets nothing (deny-by-default), and // system collections are admin-only, since read access to `_policies` would // expose the whole access model and write access would be escalation. #include #include #include #include #include #include #include "memory_store.hpp" #include "security/policy_manager.hpp" // setSecurity(enabled=true) is refused while kEnforcementCoverageComplete is // false - see policy_manager.hpp. The fixture calls allowEnableForTests() so // these tests can still exercise enforcing behaviour; a separate test below // asserts the guard itself. namespace fs = std::filesystem; using namespace smartbotic::database; namespace { int g_pass = 0; int g_fail = 0; void check(bool cond, const char* msg) { if (cond) { ++g_pass; } else { ++g_fail; std::cerr << "FAIL: " << msg << "\n"; } } struct Fixture { MemoryStore store; PolicyManager pm; Fixture() : store(MemoryStore::Config{}), pm(store) { store.start(); pm.loadFromStore(); // These tests are the engine's own; they must be able to reach // enforcing behaviour even though the deployment-level guard is armed. pm.allowEnableForTests(); } ~Fixture() { store.stop(); } }; Policy mkPolicy(const std::string& principal, bool admin = false) { Policy p; p.principal = principal; p.admin = admin; return p; } PolicyRule rw(bool r, bool w) { PolicyRule x; x.read = r; x.write = w; return x; } // ------------------------------------------------------------------------- void test_security_off_allows_everything() { Fixture f; // No policies, no security record: the pre-2.8.0 world. auto d = f.pm.authorize("acme", "nobody", "users", Access::Read); check(d.allowed, "security off allows an unknown principal to read"); check(f.pm.authorize("acme", "nobody", "users", Access::Write).allowed, "security off allows writes too"); check(f.pm.authorize("acme", "nobody", "_policies", Access::Read).allowed, "security off does not even guard system collections"); check(!f.pm.anyProjectSecured(), "no project is secured by default"); check(f.pm.mayAdminister("acme", "anyone"), "with security off anyone may create the first policy"); } void test_enabling_without_admin_is_refused() { Fixture f; std::string err; ProjectSecurity sec; sec.enabled = true; check(!f.pm.setSecurity("acme", sec, err), "enabling security with no admin policy must be refused"); check(err.find("admin") != std::string::npos, "and the error must say why - no admin policy"); check(!f.pm.securityOf("acme").enabled, "security stays off after refusal"); // Non-admin policies are not enough. auto p = mkPolicy("shadowman"); p.collections["users"] = rw(true, false); check(f.pm.setPolicy("acme", p, err), "a non-admin policy can be created"); check(!f.pm.setSecurity("acme", sec, err), "a non-admin policy does not satisfy the admin requirement"); check(f.pm.setPolicy("acme", mkPolicy("ops", /*admin=*/true), err), "an admin policy can be created"); check(f.pm.setSecurity("acme", sec, err), "with an admin present, enabling succeeds"); check(f.pm.securityOf("acme").enabled, "and security is now on"); check(f.pm.anyProjectSecured(), "anyProjectSecured reflects it"); } void test_deny_by_default_once_enforcing() { Fixture f; std::string err; f.pm.setPolicy("acme", mkPolicy("ops", true), err); auto reader = mkPolicy("reader"); reader.collections["users"] = rw(true, false); f.pm.setPolicy("acme", reader, err); ProjectSecurity sec; sec.enabled = true; f.pm.setSecurity("acme", sec, err); check(!f.pm.authorize("acme", "stranger", "users", Access::Read).allowed, "a principal with no policy gets nothing"); check(f.pm.authorize("acme", "reader", "users", Access::Read).allowed, "a granted read is allowed"); check(!f.pm.authorize("acme", "reader", "users", Access::Write).allowed, "read does not imply write"); check(!f.pm.authorize("acme", "reader", "sessions", Access::Read).allowed, "a collection with no rule is denied even for a known principal"); check(f.pm.authorize("acme", "ops", "anything", Access::Write).allowed, "admin may write anything in the project"); // Another project is untouched. check(f.pm.authorize("other", "stranger", "users", Access::Read).allowed, "enabling one project must not affect another"); } void test_wildcard_rule_and_exact_match_precedence() { Fixture f; std::string err; f.pm.setPolicy("acme", mkPolicy("ops", true), err); auto p = mkPolicy("svc"); p.collections["*"] = rw(true, false); p.collections["secrets"] = rw(false, false); f.pm.setPolicy("acme", p, err); ProjectSecurity sec; sec.enabled = true; f.pm.setSecurity("acme", sec, err); check(f.pm.authorize("acme", "svc", "anything", Access::Read).allowed, "the * fallback grants read on an unlisted collection"); check(!f.pm.authorize("acme", "svc", "secrets", Access::Read).allowed, "an exact rule overrides the * fallback, even to deny"); } void test_system_collections_are_admin_only() { Fixture f; std::string err; f.pm.setPolicy("acme", mkPolicy("ops", true), err); auto p = mkPolicy("svc"); p.collections["*"] = rw(true, true); f.pm.setPolicy("acme", p, err); ProjectSecurity sec; sec.enabled = true; f.pm.setSecurity("acme", sec, err); check(!f.pm.authorize("acme", "svc", "_policies", Access::Read).allowed, "a * grant must NOT reach _policies - that would expose the access model"); check(!f.pm.authorize("acme", "svc", "_policies", Access::Write).allowed, "nor allow writing it - that would be privilege escalation"); check(!f.pm.authorize("acme", "svc", "_views", Access::Read).allowed, "no system collection is reachable via *"); check(f.pm.authorize("acme", "ops", "_policies", Access::Write).allowed, "an admin may still manage system collections"); check(!f.pm.mayAdminister("acme", "svc"), "a non-admin may not administer policy"); check(f.pm.mayAdminister("acme", "ops"), "an admin may"); } void test_mask_and_row_predicate_are_returned() { Fixture f; std::string err; f.pm.setPolicy("acme", mkPolicy("ops", true), err); auto p = mkPolicy("svc"); PolicyRule r = rw(true, false); r.mask = {"ssn", "profile.dob"}; Filter tenant; tenant.field = "tenant"; tenant.op = FilterOp::EQ; tenant.value = "acme"; r.row = {tenant}; p.collections["users"] = r; f.pm.setPolicy("acme", p, err); ProjectSecurity sec; sec.enabled = true; f.pm.setSecurity("acme", sec, err); auto d = f.pm.authorize("acme", "svc", "users", Access::Read); check(d.allowed, "granted"); check(d.mask.size() == 2, "the column mask comes back with the decision"); check(d.mask[0] == "ssn", "mask paths preserved"); check(d.row.size() == 1 && d.row[0].field == "tenant", "the row predicate comes back so the handler can AND-merge it"); } void test_audit_mode_logs_but_allows() { Fixture f; std::string err; f.pm.setPolicy("acme", mkPolicy("ops", true), err); ProjectSecurity sec; sec.enabled = true; sec.mode = SecurityMode::Audit; check(f.pm.setSecurity("acme", sec, err), "audit mode can be enabled"); auto d = f.pm.authorize("acme", "stranger", "users", Access::Read); check(d.allowed, "audit mode ALLOWS what enforce mode would deny"); check(d.audited_denial, "but records that it was really a denial"); check(!d.reason.empty(), "and keeps the reason for the operator log"); // Flip to enforce and the same request is refused. sec.mode = SecurityMode::Enforce; f.pm.setSecurity("acme", sec, err); auto d2 = f.pm.authorize("acme", "stranger", "users", Access::Read); check(!d2.allowed, "enforce mode denies the same request"); check(!d2.audited_denial, "and does not mark it as merely audited"); } void test_cannot_remove_last_admin_of_secured_project() { Fixture f; std::string err; f.pm.setPolicy("acme", mkPolicy("ops", true), err); ProjectSecurity sec; sec.enabled = true; f.pm.setSecurity("acme", sec, err); check(!f.pm.removePolicy("acme", "ops", err), "removing the last admin of a secured project must be refused"); check(err.find("last admin") != std::string::npos, "with a clear reason"); check(f.pm.setPolicy("acme", mkPolicy("ops2", true), err), "add a second admin"); check(f.pm.removePolicy("acme", "ops", err), "now the first admin can be removed"); check(!f.pm.removePolicy("acme", "ops2", err), "but not the remaining last one"); } void test_policies_survive_reload() { Fixture f; std::string err; auto p = mkPolicy("svc"); PolicyRule r = rw(true, false); r.mask = {"ssn"}; p.collections["users"] = r; f.pm.setPolicy("acme", p, err); f.pm.setPolicy("acme", mkPolicy("ops", true), err); ProjectSecurity sec; sec.enabled = true; sec.mode = SecurityMode::Audit; f.pm.setSecurity("acme", sec, err); // Reload from the same store, as a restart would. f.pm.loadFromStore(); check(f.pm.securityOf("acme").enabled, "enable flag survives reload"); check(f.pm.securityOf("acme").mode == SecurityMode::Audit, "mode survives reload"); auto got = f.pm.getPolicy("acme", "svc"); check(got.has_value(), "policy survives reload"); check(got && got->collections.count("users") == 1, "rules survive reload"); check(got && got->collections["users"].mask.size() == 1, "the column mask survives reload"); check(f.pm.listPolicies("acme").size() == 2, "both policies listed"); check(f.pm.listPolicies("other").empty(), "listing is scoped to the project"); } void test_file_rules_are_separate_from_collections() { Fixture f; std::string err; f.pm.setPolicy("acme", mkPolicy("ops", true), err); auto p = mkPolicy("svc"); p.collections["*"] = rw(true, true); p.files["plugin"] = rw(true, false); f.pm.setPolicy("acme", p, err); ProjectSecurity sec; sec.enabled = true; f.pm.setSecurity("acme", sec, err); check(f.pm.authorizeFile("acme", "svc", "plugin", Access::Read).allowed, "a granted file type reads"); check(!f.pm.authorizeFile("acme", "svc", "plugin", Access::Write).allowed, "file write is separately gated"); check(!f.pm.authorizeFile("acme", "svc", "document", Access::Read).allowed, "an unlisted file type is denied - a collection * does not cover files"); } // The deployment guard itself: without the test seam, arming security must be // refused while enforcement is not wired into every handler. A project // protected on some paths and open on others reports safety it does not have. void test_enable_is_refused_while_coverage_incomplete() { MemoryStore store(MemoryStore::Config{}); store.start(); PolicyManager pm(store); // note: NO allowEnableForTests() pm.loadFromStore(); std::string err; pm.setPolicy("acme", mkPolicy("ops", true), err); ProjectSecurity sec; sec.enabled = true; if (kEnforcementCoverageComplete) { check(pm.setSecurity("acme", sec, err), "coverage complete: enabling is permitted"); } else { check(!pm.setSecurity("acme", sec, err), "coverage incomplete: enabling must be refused even with an admin"); check(err.find("not yet wired") != std::string::npos, "and the refusal must say enforcement is incomplete"); check(!pm.securityOf("acme").enabled, "security stays off"); } store.stop(); } } // namespace int main() { std::cout << "=== test_policy_manager ===\n"; test_security_off_allows_everything(); test_enabling_without_admin_is_refused(); test_deny_by_default_once_enforcing(); test_wildcard_rule_and_exact_match_precedence(); test_system_collections_are_admin_only(); test_mask_and_row_predicate_are_returned(); test_audit_mode_logs_but_allows(); test_cannot_remove_last_admin_of_secured_project(); test_policies_survive_reload(); test_file_rules_are_separate_from_collections(); test_enable_is_refused_while_coverage_incomplete(); std::cout << "passed: " << g_pass << ", failed: " << g_fail << "\n"; return g_fail == 0 ? 0 : 1; }