mirror of
https://github.com/logos-co/logos-liblogos.git
synced 2026-08-27 12:51:10 +00:00
172 lines
7.3 KiB
C++
172 lines
7.3 KiB
C++
// =============================================================================
|
|
// Tests for the access-policy parser (src/logos_core/access_policy.{h,cpp}).
|
|
//
|
|
// parseAccessPolicy turns the JSON document set via
|
|
// logos_core_set_access_policy into a structured AccessPolicy. Core uses it to
|
|
// register concrete per-target restrictions with capability_module; enforcement
|
|
// itself lives in capability_module (tested there). These tests pin the parse
|
|
// contract:
|
|
// - the exact basecamp/daemon production document parses correctly
|
|
// - mode != "enforce" is reflected via enforce()==false (core registers nothing)
|
|
// - tolerant parsing: unknown keys ignored, missing/!object restrictions ->
|
|
// empty, missing allowedCallers -> target with empty caller list
|
|
// - the ONLY hard failure is invalid JSON -> std::nullopt
|
|
// =============================================================================
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "access_policy.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
using LogosCore::AccessPolicy;
|
|
using LogosCore::parseAccessPolicy;
|
|
|
|
namespace {
|
|
|
|
// Find a restriction by target name in a parsed policy, or nullptr.
|
|
const LogosCore::AccessRestriction* findTarget(const AccessPolicy& p,
|
|
const std::string& target) {
|
|
for (const auto& r : p.restrictions)
|
|
if (r.target == target) return &r;
|
|
return nullptr;
|
|
}
|
|
|
|
bool callersContain(const LogosCore::AccessRestriction& r, const std::string& caller) {
|
|
return std::find(r.allowedCallers.begin(), r.allowedCallers.end(), caller)
|
|
!= r.allowedCallers.end();
|
|
}
|
|
|
|
// The exact document basecamp (app/main.cpp) and the logoscore daemon pass.
|
|
const char* kProductionPolicy =
|
|
"{\"version\":1,\"mode\":\"enforce\",\"restrictions\":{"
|
|
"\"package_manager\":{\"allowedCallers\":[\"package_manager_ui\"]},"
|
|
"\"package_downloader\":{\"allowedCallers\":[\"package_manager_ui\"]}}}";
|
|
|
|
} // namespace
|
|
|
|
// ── Production document ──────────────────────────────────────────────────────
|
|
|
|
TEST(AccessPolicyParse, ParsesProductionDocument) {
|
|
auto policy = parseAccessPolicy(kProductionPolicy);
|
|
ASSERT_TRUE(policy.has_value());
|
|
|
|
EXPECT_EQ(policy->version, 1);
|
|
EXPECT_EQ(policy->mode, "enforce");
|
|
EXPECT_TRUE(policy->enforce());
|
|
ASSERT_EQ(policy->restrictions.size(), 2u);
|
|
|
|
const auto* pm = findTarget(*policy, "package_manager");
|
|
ASSERT_NE(pm, nullptr);
|
|
ASSERT_EQ(pm->allowedCallers.size(), 1u);
|
|
EXPECT_TRUE(callersContain(*pm, "package_manager_ui"));
|
|
|
|
const auto* pd = findTarget(*policy, "package_downloader");
|
|
ASSERT_NE(pd, nullptr);
|
|
EXPECT_TRUE(callersContain(*pd, "package_manager_ui"));
|
|
}
|
|
|
|
// ── mode semantics ───────────────────────────────────────────────────────────
|
|
|
|
TEST(AccessPolicyParse, NonEnforceModeReportsEnforceFalse) {
|
|
auto policy = parseAccessPolicy(
|
|
"{\"version\":1,\"mode\":\"audit\",\"restrictions\":{"
|
|
"\"package_manager\":{\"allowedCallers\":[\"package_manager_ui\"]}}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
EXPECT_EQ(policy->mode, "audit");
|
|
EXPECT_FALSE(policy->enforce());
|
|
// Restrictions still parse — core just won't register them in non-enforce.
|
|
EXPECT_EQ(policy->restrictions.size(), 1u);
|
|
}
|
|
|
|
TEST(AccessPolicyParse, MissingModeIsNotEnforce) {
|
|
auto policy = parseAccessPolicy("{\"version\":1,\"restrictions\":{}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
EXPECT_FALSE(policy->enforce());
|
|
}
|
|
|
|
// ── Multiple callers per target ──────────────────────────────────────────────
|
|
|
|
TEST(AccessPolicyParse, ParsesMultipleAllowedCallers) {
|
|
auto policy = parseAccessPolicy(
|
|
"{\"version\":1,\"mode\":\"enforce\",\"restrictions\":{"
|
|
"\"target\":{\"allowedCallers\":[\"a\",\"b\",\"c\"]}}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
const auto* t = findTarget(*policy, "target");
|
|
ASSERT_NE(t, nullptr);
|
|
EXPECT_EQ(t->allowedCallers.size(), 3u);
|
|
EXPECT_TRUE(callersContain(*t, "a"));
|
|
EXPECT_TRUE(callersContain(*t, "b"));
|
|
EXPECT_TRUE(callersContain(*t, "c"));
|
|
}
|
|
|
|
// ── Tolerant parsing ─────────────────────────────────────────────────────────
|
|
|
|
TEST(AccessPolicyParse, EmptyRestrictionsObjectYieldsNoRestrictions) {
|
|
auto policy = parseAccessPolicy("{\"version\":1,\"mode\":\"enforce\",\"restrictions\":{}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
EXPECT_TRUE(policy->enforce());
|
|
EXPECT_TRUE(policy->restrictions.empty());
|
|
}
|
|
|
|
TEST(AccessPolicyParse, MissingRestrictionsKeyYieldsNoRestrictions) {
|
|
auto policy = parseAccessPolicy("{\"version\":1,\"mode\":\"enforce\"}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
EXPECT_TRUE(policy->restrictions.empty());
|
|
}
|
|
|
|
TEST(AccessPolicyParse, TargetWithoutAllowedCallersYieldsEmptyCallerList) {
|
|
// A restricted target with no allowedCallers means "nobody may call it"
|
|
// once enforced — the parser surfaces it as a target with zero callers.
|
|
auto policy = parseAccessPolicy(
|
|
"{\"version\":1,\"mode\":\"enforce\",\"restrictions\":{\"target\":{}}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
const auto* t = findTarget(*policy, "target");
|
|
ASSERT_NE(t, nullptr);
|
|
EXPECT_TRUE(t->allowedCallers.empty());
|
|
}
|
|
|
|
TEST(AccessPolicyParse, UnknownTopLevelKeysAreIgnored) {
|
|
auto policy = parseAccessPolicy(
|
|
"{\"version\":1,\"mode\":\"enforce\",\"futureField\":42,"
|
|
"\"restrictions\":{\"target\":{\"allowedCallers\":[\"a\"],\"note\":\"x\"}}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
EXPECT_TRUE(policy->enforce());
|
|
const auto* t = findTarget(*policy, "target");
|
|
ASSERT_NE(t, nullptr);
|
|
EXPECT_EQ(t->allowedCallers.size(), 1u);
|
|
}
|
|
|
|
TEST(AccessPolicyParse, NonStringCallersAreSkipped) {
|
|
auto policy = parseAccessPolicy(
|
|
"{\"version\":1,\"mode\":\"enforce\",\"restrictions\":{"
|
|
"\"target\":{\"allowedCallers\":[\"ok\",123,null,\"also_ok\"]}}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
const auto* t = findTarget(*policy, "target");
|
|
ASSERT_NE(t, nullptr);
|
|
EXPECT_EQ(t->allowedCallers.size(), 2u);
|
|
EXPECT_TRUE(callersContain(*t, "ok"));
|
|
EXPECT_TRUE(callersContain(*t, "also_ok"));
|
|
}
|
|
|
|
// ── Hard failures ────────────────────────────────────────────────────────────
|
|
|
|
TEST(AccessPolicyParse, InvalidJsonReturnsNullopt) {
|
|
EXPECT_FALSE(parseAccessPolicy("{not valid json").has_value());
|
|
EXPECT_FALSE(parseAccessPolicy("").has_value());
|
|
EXPECT_FALSE(parseAccessPolicy("garbage").has_value());
|
|
}
|
|
|
|
TEST(AccessPolicyParse, NonObjectJsonReturnsNullopt) {
|
|
// Valid JSON, but not a policy object.
|
|
EXPECT_FALSE(parseAccessPolicy("[1,2,3]").has_value());
|
|
EXPECT_FALSE(parseAccessPolicy("\"a string\"").has_value());
|
|
EXPECT_FALSE(parseAccessPolicy("42").has_value());
|
|
}
|
|
|
|
TEST(AccessPolicyParse, VersionDefaultsToZeroWhenAbsent) {
|
|
auto policy = parseAccessPolicy("{\"mode\":\"enforce\",\"restrictions\":{}}");
|
|
ASSERT_TRUE(policy.has_value());
|
|
EXPECT_EQ(policy->version, 0);
|
|
}
|