You've already forked tribes-plugin-kobold
Make Kobold require Trust, declare editable dataset/feature permissions, and enforce generic access rules for remote dataset reads and writes.\n\nUpdate the Docker e2e harness to include Trust and verify handshake-backed public access plus explicit private read/write grants.
This commit is contained in:
@@ -3,11 +3,16 @@
|
||||
|
||||
inputs = {
|
||||
tribes.url = "git+https://git.teralink.net/tribes/tribes.git";
|
||||
trust = {
|
||||
url = "git+https://git.teralink.net/tribes/tribes-plugin-trust.git";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
tribes,
|
||||
trust,
|
||||
}: let
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
@@ -56,7 +61,9 @@
|
||||
};
|
||||
|
||||
src = cleanSource ./.;
|
||||
trustSrc = cleanSource trust;
|
||||
koboldManifest = builtins.fromJSON (builtins.readFile ./manifest.json);
|
||||
trustManifest = builtins.fromJSON (builtins.readFile "${trust}/manifest.json");
|
||||
buildTribesPlugin = pkgs.callPackage "${tribes}/nix/tribes-plugin-package.nix" {};
|
||||
|
||||
koboldPlugin = buildTribesPlugin {
|
||||
@@ -77,6 +84,16 @@
|
||||
meta.description = "Distributed dataset plugin for Tribes";
|
||||
};
|
||||
|
||||
trustPluginE2E = buildTribesPlugin {
|
||||
pname = "tribes-plugin-trust";
|
||||
inherit version;
|
||||
src = trustSrc;
|
||||
hostRelease = tribes.packages.${system}.tribesReleaseE2E;
|
||||
hostSource = tribes;
|
||||
mixFodDepsHash = "sha256-O8+48XnD2eFRW/w6sHvo3qS5hunddEGVGpvjMduClBM=";
|
||||
meta.description = "Tribe trust plugin for Tribes";
|
||||
};
|
||||
|
||||
koboldExternalPlugin = package: {
|
||||
name = "kobold";
|
||||
inherit package;
|
||||
@@ -84,6 +101,13 @@
|
||||
extraPackages = [];
|
||||
};
|
||||
|
||||
trustExternalPlugin = package: {
|
||||
name = "trust";
|
||||
inherit package;
|
||||
manifest = trustManifest;
|
||||
extraPackages = [];
|
||||
};
|
||||
|
||||
koboldWithTribesE2E =
|
||||
(import "${tribes}/default.nix" {
|
||||
inherit pkgs lib;
|
||||
@@ -94,6 +118,7 @@
|
||||
name = "tribes_ui";
|
||||
path = "./plugins/tribes_ui";
|
||||
}
|
||||
(trustExternalPlugin trustPluginE2E)
|
||||
(koboldExternalPlugin koboldPluginE2E)
|
||||
];
|
||||
})
|
||||
@@ -101,7 +126,7 @@
|
||||
in
|
||||
{
|
||||
default = koboldPlugin;
|
||||
inherit koboldPlugin koboldPluginE2E koboldWithTribesE2E;
|
||||
inherit koboldPlugin koboldPluginE2E trustPluginE2E koboldWithTribesE2E;
|
||||
}
|
||||
// lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
||||
koboldDockerE2E = pkgs.dockerTools.buildLayeredImage {
|
||||
|
||||
+95
-1
@@ -21,6 +21,7 @@ defmodule TribeOne.TribesPlugin.Kobold.API do
|
||||
|
||||
def management_upsert_record(_context, params), do: upsert_record(params)
|
||||
def management_state(_context, params), do: state(params)
|
||||
def management_access_rule_create(_context, params), do: create_access_rule(params)
|
||||
def management_rebuild_projections(_context, params), do: rebuild_projections(params)
|
||||
|
||||
def schema do
|
||||
@@ -79,7 +80,8 @@ defmodule TribeOne.TribesPlugin.Kobold.API do
|
||||
action =
|
||||
if visibility(params) == :public, do: :create_public_dataset, else: :create_private_dataset
|
||||
|
||||
with {:ok, dataset} <- apply(Kobold, action, [attrs, [authorize?: false]]) do
|
||||
with {:ok, dataset} <- apply(Kobold, action, [attrs, [authorize?: false]]),
|
||||
:ok <- maybe_seed_dataset_policy(dataset) do
|
||||
{:ok, %{ok: true, dataset: dataset_json(dataset)}}
|
||||
end
|
||||
rescue
|
||||
@@ -118,6 +120,7 @@ defmodule TribeOne.TribesPlugin.Kobold.API do
|
||||
resource_name = required_string!(params, "resource_name")
|
||||
|
||||
with {:ok, dataset} <- Kobold.get_dataset(dataset_id, authorize?: false),
|
||||
:ok <- authorize_dataset(params, dataset, "write"),
|
||||
{:ok, definition} <-
|
||||
Kobold.get_resource_definition(dataset.id, resource_name, authorize?: false) do
|
||||
fields = map_param(params, "fields")
|
||||
@@ -163,6 +166,7 @@ defmodule TribeOne.TribesPlugin.Kobold.API do
|
||||
run_id = optional_string(params, "run_id")
|
||||
|
||||
with {:ok, datasets} <- datasets_for_run(run_id),
|
||||
{:ok, datasets} <- authorize_datasets(params, datasets, "read"),
|
||||
{:ok, resources} <- resources_for_datasets(datasets),
|
||||
{:ok, events} <- events_for_run(run_id, datasets) do
|
||||
{:ok,
|
||||
@@ -177,6 +181,26 @@ defmodule TribeOne.TribesPlugin.Kobold.API do
|
||||
end
|
||||
end
|
||||
|
||||
def create_access_rule(params) do
|
||||
with {:ok, rule} <-
|
||||
Tribes.Access.put_rule(%{
|
||||
plugin_name: "kobold",
|
||||
resource_type: required_string!(params, "resource_type"),
|
||||
resource_id: Map.get(params, "resource_id", "*"),
|
||||
action: required_string!(params, "action"),
|
||||
subject_type: Map.get(params, "subject_type", "tribe"),
|
||||
subject_id: Map.get(params, "subject_id", "*"),
|
||||
effect: Map.get(params, "effect", "allow"),
|
||||
condition: Map.get(params, "condition", "none"),
|
||||
min_trust_score: Map.get(params, "min_trust_score"),
|
||||
note: optional_string(params, "note")
|
||||
}) do
|
||||
{:ok, %{ok: true, rule: rule}}
|
||||
end
|
||||
rescue
|
||||
error in ArgumentError -> {:error, error.message}
|
||||
end
|
||||
|
||||
def rebuild_projections(params) do
|
||||
with {:ok, %{events: events}} <- state_events(params),
|
||||
:ok <- rebuild_events(events) do
|
||||
@@ -202,6 +226,76 @@ defmodule TribeOne.TribesPlugin.Kobold.API do
|
||||
end)
|
||||
end
|
||||
|
||||
defp maybe_seed_dataset_policy(%Dataset{visibility: :public} = dataset) do
|
||||
with {:ok, _read_rule} <-
|
||||
Tribes.Access.put_rule(%{
|
||||
plugin_name: "kobold",
|
||||
resource_type: "kobold.dataset",
|
||||
resource_id: dataset.id,
|
||||
action: "read",
|
||||
subject_type: "tribe",
|
||||
subject_id: "*",
|
||||
effect: "allow",
|
||||
condition: "min_trust_score",
|
||||
min_trust_score: 0,
|
||||
note: "Default public dataset read access after Trust handshake"
|
||||
}),
|
||||
{:ok, _advertise_rule} <-
|
||||
Tribes.Access.put_rule(%{
|
||||
plugin_name: "kobold",
|
||||
resource_type: "kobold.dataset",
|
||||
resource_id: dataset.id,
|
||||
action: "advertise",
|
||||
subject_type: "tribe",
|
||||
subject_id: "*",
|
||||
effect: "allow",
|
||||
condition: "min_trust_score",
|
||||
min_trust_score: 0,
|
||||
note: "Default public dataset advertisement after Trust handshake"
|
||||
}) do
|
||||
:ok
|
||||
else
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_seed_dataset_policy(%Dataset{}), do: :ok
|
||||
|
||||
defp authorize_datasets(params, datasets, action) do
|
||||
case remote_subject(params) do
|
||||
nil ->
|
||||
{:ok, datasets}
|
||||
|
||||
subject ->
|
||||
{:ok, Enum.filter(datasets, &dataset_allowed?(subject, &1, action))}
|
||||
end
|
||||
end
|
||||
|
||||
defp authorize_dataset(params, dataset, action) do
|
||||
case remote_subject(params) do
|
||||
nil ->
|
||||
:ok
|
||||
|
||||
subject ->
|
||||
if dataset_allowed?(subject, dataset, action) do
|
||||
:ok
|
||||
else
|
||||
{:error, :access_denied}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp dataset_allowed?(subject, dataset, action) do
|
||||
Tribes.Access.allowed?(subject, action, Tribes.Access.resource("kobold.dataset", dataset.id))
|
||||
end
|
||||
|
||||
defp remote_subject(params) do
|
||||
case optional_string(params, "remote_tribe_pubkey") do
|
||||
nil -> nil
|
||||
pubkey -> Tribes.Access.subject(:tribe, pubkey)
|
||||
end
|
||||
end
|
||||
|
||||
defp datasets_for_run(nil), do: Kobold.list_datasets(authorize?: false)
|
||||
defp datasets_for_run(run_id), do: Kobold.list_datasets_by_run(run_id, authorize?: false)
|
||||
|
||||
|
||||
+35
-1
@@ -85,6 +85,14 @@ defmodule TribeOne.TribesPlugin.Kobold.Plugin do
|
||||
auth: :admin,
|
||||
description: "Read Kobold dataset state"
|
||||
},
|
||||
%{
|
||||
name: "kobold.access.rules.create",
|
||||
version: "1",
|
||||
module: TribeOne.TribesPlugin.Kobold.API,
|
||||
action: :management_access_rule_create,
|
||||
auth: :admin,
|
||||
description: "Create a Kobold access rule"
|
||||
},
|
||||
%{
|
||||
name: "kobold.projections.rebuild",
|
||||
version: "1",
|
||||
@@ -96,7 +104,33 @@ defmodule TribeOne.TribesPlugin.Kobold.Plugin do
|
||||
],
|
||||
metrics: [],
|
||||
plugs: [],
|
||||
hooks: %{}
|
||||
hooks: %{},
|
||||
access_schema: %{
|
||||
title: "Kobold permissions",
|
||||
description: "Dataset and feature access rules for Kobold.",
|
||||
resources: [
|
||||
%{
|
||||
type: "kobold.dataset",
|
||||
label: "Kobold dataset",
|
||||
description: "Controls read/write/admin access to individual Kobold datasets.",
|
||||
actions: [
|
||||
%{name: "advertise", label: "Advertise"},
|
||||
%{name: "read", label: "Read"},
|
||||
%{name: "write", label: "Write"},
|
||||
%{name: "admin", label: "Admin"}
|
||||
]
|
||||
},
|
||||
%{
|
||||
type: "kobold.feature",
|
||||
label: "Kobold feature",
|
||||
description: "Controls access to plugin-level Kobold features.",
|
||||
actions: [
|
||||
%{name: "use", label: "Use"},
|
||||
%{name: "admin", label: "Admin"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@
|
||||
"org.tribes.kobold.dataset@1"
|
||||
],
|
||||
"requires": [
|
||||
"org.tribe-one.caps.ui@1"
|
||||
"org.tribe-one.caps.ui@1",
|
||||
"org.tribes.alliance.trust@1"
|
||||
],
|
||||
"enhances_with": [],
|
||||
"assets": {
|
||||
|
||||
@@ -21,12 +21,16 @@ defmodule TribeOne.TribesPlugin.Kobold.E2ERunner do
|
||||
|
||||
{:ok, edge_info} = admin_with_retry(@edge, "node_info", %{}, attempts: 90, delay_ms: 1_000)
|
||||
|
||||
assert_plugin_loaded!(@origin, "origin")
|
||||
assert_plugin_loaded!(@edge, "edge")
|
||||
assert_plugin_loaded!(@origin, "origin", "kobold")
|
||||
assert_plugin_loaded!(@edge, "edge", "kobold")
|
||||
assert_plugin_loaded!(@origin, "origin", "trust")
|
||||
assert_plugin_loaded!(@edge, "edge", "trust")
|
||||
assert_kobold_schema_ready!(@origin, "origin")
|
||||
assert_kobold_schema_ready!(@edge, "edge")
|
||||
|
||||
:ok = connect_cluster_pair(@origin, origin_info, @edge, edge_info)
|
||||
edge_pubkey = node_pubkey(edge_info)
|
||||
:ok = establish_unknown_tribe_trust(@origin, edge_pubkey)
|
||||
|
||||
run_id = "kobold-docker-e2e-#{System.system_time(:millisecond)}"
|
||||
{:ok, _} = kobold(@origin, "kobold.reset", %{"run_id" => run_id})
|
||||
@@ -63,6 +67,25 @@ defmodule TribeOne.TribesPlugin.Kobold.E2ERunner do
|
||||
"fields" => %{"name" => "Private pepper", "germination_days" => 14}
|
||||
})
|
||||
|
||||
assert_remote_cannot_read_or_write_private!(run_id, private_dataset["id"], edge_pubkey)
|
||||
:ok = grant_dataset_access(@origin, private_dataset["id"], edge_pubkey, "read")
|
||||
:ok = grant_dataset_access(@origin, private_dataset["id"], edge_pubkey, "write")
|
||||
|
||||
{:ok, %{"record" => private_remote_record}} =
|
||||
kobold(@origin, "kobold.records.upsert", %{
|
||||
"dataset_id" => private_dataset["id"],
|
||||
"resource_name" => "SeedVariety",
|
||||
"remote_tribe_pubkey" => edge_pubkey,
|
||||
"fields" => %{"name" => "Remote-written private bean", "germination_days" => 10}
|
||||
})
|
||||
|
||||
assert_remote_can_read_private!(
|
||||
run_id,
|
||||
private_dataset["id"],
|
||||
private_remote_record["record_id"],
|
||||
edge_pubkey
|
||||
)
|
||||
|
||||
assert_origin_state!(run_id, public_record["record_id"], private_record["record_id"])
|
||||
|
||||
assert_ok(
|
||||
@@ -93,6 +116,46 @@ defmodule TribeOne.TribesPlugin.Kobold.E2ERunner do
|
||||
end
|
||||
end
|
||||
|
||||
defp assert_remote_cannot_read_or_write_private!(run_id, private_dataset_id, edge_pubkey) do
|
||||
{:ok, state} =
|
||||
kobold(@origin, "kobold.state", %{
|
||||
"run_id" => run_id,
|
||||
"remote_tribe_pubkey" => edge_pubkey
|
||||
})
|
||||
|
||||
assert_ok(
|
||||
Enum.all?(Map.get(state, "datasets", []), &(&1["id"] != private_dataset_id)),
|
||||
"remote tribe cannot read private dataset without permission"
|
||||
)
|
||||
|
||||
assert_ok(
|
||||
match?(
|
||||
{:error, %{body: %{"error" => error}}} when is_binary(error),
|
||||
kobold(@origin, "kobold.records.upsert", %{
|
||||
"dataset_id" => private_dataset_id,
|
||||
"resource_name" => "SeedVariety",
|
||||
"remote_tribe_pubkey" => edge_pubkey,
|
||||
"fields" => %{"name" => "Denied private bean", "germination_days" => 11}
|
||||
})
|
||||
),
|
||||
"remote tribe cannot write private dataset without permission"
|
||||
)
|
||||
end
|
||||
|
||||
defp assert_remote_can_read_private!(run_id, private_dataset_id, record_id, edge_pubkey) do
|
||||
{:ok, state} =
|
||||
kobold(@origin, "kobold.state", %{
|
||||
"run_id" => run_id,
|
||||
"remote_tribe_pubkey" => edge_pubkey
|
||||
})
|
||||
|
||||
assert_ok(
|
||||
Enum.any?(Map.get(state, "datasets", []), &(&1["id"] == private_dataset_id)) and
|
||||
Enum.any?(Map.get(state, "records", []), &(&1["record_id"] == record_id)),
|
||||
"remote tribe can read explicitly shared private dataset"
|
||||
)
|
||||
end
|
||||
|
||||
defp assert_origin_state!(run_id, public_record_id, private_record_id) do
|
||||
{:ok, state} = kobold(@origin, "kobold.state", %{"run_id" => run_id})
|
||||
records = Map.get(state, "records", [])
|
||||
@@ -135,16 +198,16 @@ defmodule TribeOne.TribesPlugin.Kobold.E2ERunner do
|
||||
end)
|
||||
end
|
||||
|
||||
defp assert_plugin_loaded!(base_url, label) do
|
||||
defp assert_plugin_loaded!(base_url, label, plugin_name) do
|
||||
{:ok, plugins} = admin_with_retry(base_url, "plugin_list", %{}, attempts: 30, delay_ms: 1_000)
|
||||
|
||||
loaded? =
|
||||
Enum.any?(plugins["plugins"], fn
|
||||
%{"name" => "kobold", "status" => "loaded"} -> true
|
||||
%{"name" => ^plugin_name, "status" => "loaded"} -> true
|
||||
_other -> false
|
||||
end)
|
||||
|
||||
assert_ok(loaded?, "#{label} kobold plugin loaded")
|
||||
assert_ok(loaded?, "#{label} #{plugin_name} plugin loaded")
|
||||
end
|
||||
|
||||
defp assert_kobold_schema_ready!(base_url, label) do
|
||||
@@ -158,6 +221,35 @@ defmodule TribeOne.TribesPlugin.Kobold.E2ERunner do
|
||||
)
|
||||
end
|
||||
|
||||
defp establish_unknown_tribe_trust(base_url, edge_pubkey) do
|
||||
with {:ok, _relationship} <-
|
||||
trust(base_url, "trust.hello.receive", %{
|
||||
"from_tribe" => edge_pubkey,
|
||||
"name" => "Kobold edge",
|
||||
"api_url" => @edge <> "/api/tribes/v1",
|
||||
"relay_urls" => ["ws://kobold-edge:4000/nostr/relay"],
|
||||
"capabilities" => ["org.tribes.kobold.dataset@1"],
|
||||
"requested_capabilities" => ["org.tribes.kobold.dataset.read@1"]
|
||||
}) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp grant_dataset_access(base_url, dataset_id, subject_id, action) do
|
||||
with {:ok, _rule} <-
|
||||
kobold(base_url, "kobold.access.rules.create", %{
|
||||
"resource_type" => "kobold.dataset",
|
||||
"resource_id" => dataset_id,
|
||||
"action" => action,
|
||||
"subject_type" => "tribe",
|
||||
"subject_id" => subject_id,
|
||||
"effect" => "allow",
|
||||
"condition" => "none"
|
||||
}) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp kobold(base_url, method, params) do
|
||||
admin(base_url, "plugin.call", %{
|
||||
"plugin" => "kobold",
|
||||
@@ -167,6 +259,15 @@ defmodule TribeOne.TribesPlugin.Kobold.E2ERunner do
|
||||
})
|
||||
end
|
||||
|
||||
defp trust(base_url, method, params) do
|
||||
admin(base_url, "plugin.call", %{
|
||||
"plugin" => "trust",
|
||||
"method" => method,
|
||||
"version" => "1",
|
||||
"params" => params
|
||||
})
|
||||
end
|
||||
|
||||
defp connect_cluster_pair(left_base_url, left_node_info, right_base_url, right_node_info) do
|
||||
with {:ok, _} <- upsert_cluster_node(left_base_url, right_node_info),
|
||||
{:ok, _} <- upsert_cluster_node(right_base_url, left_node_info) do
|
||||
|
||||
@@ -4,15 +4,19 @@ set -euo pipefail
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
||||
HOST_ROOT="${TRIBES_HOST_ROOT:-$ROOT_DIR/../tribes}"
|
||||
HOST_ROOT="$(cd "$HOST_ROOT" && pwd -P)"
|
||||
TRUST_ROOT="${TRIBES_TRUST_ROOT:-$ROOT_DIR/../tribes-plugin-trust}"
|
||||
TRUST_ROOT="$(cd "$TRUST_ROOT" && pwd -P)"
|
||||
COMPOSE_FILE="$ROOT_DIR/test/e2e/compose.kobold.yaml"
|
||||
PROJECT="kobold-e2e-$(date +%s)"
|
||||
IMAGE_LINK="$ROOT_DIR/.kobold-e2e-image"
|
||||
HOST_SOURCE_TMP=""
|
||||
TRUST_SOURCE_TMP=""
|
||||
|
||||
export COMPOSE_PROJECT_NAME="$PROJECT"
|
||||
export E2E_COMPOSE_FILE="$COMPOSE_FILE"
|
||||
export KOBOLD_PLUGIN_ROOT="$ROOT_DIR"
|
||||
export TRIBES_HOST_ROOT="$HOST_ROOT"
|
||||
export TRIBES_TRUST_ROOT="$TRUST_ROOT"
|
||||
export TRIBES_KOBOLD_IMAGE="${TRIBES_KOBOLD_IMAGE:-tribes-kobold-e2e:latest}"
|
||||
|
||||
cleanup() {
|
||||
@@ -25,6 +29,10 @@ cleanup() {
|
||||
if [[ -n "$HOST_SOURCE_TMP" ]]; then
|
||||
rm -rf "$HOST_SOURCE_TMP"
|
||||
fi
|
||||
|
||||
if [[ -n "$TRUST_SOURCE_TMP" ]]; then
|
||||
rm -rf "$TRUST_SOURCE_TMP"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
@@ -34,10 +42,11 @@ command -v docker >/dev/null 2>&1 || {
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
|
||||
echo "==> preparing filtered Tribes host source"
|
||||
HOST_SOURCE_TMP="$(mktemp -d -t kobold-tribes-host.XXXXXX)"
|
||||
tar -C "$HOST_ROOT" \
|
||||
copy_filtered_source() {
|
||||
local from="$1"
|
||||
local to="$2"
|
||||
|
||||
tar -C "$from" \
|
||||
--exclude=.git \
|
||||
--exclude=.devenv \
|
||||
--exclude=.direnv \
|
||||
@@ -46,12 +55,23 @@ if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
|
||||
--exclude=node_modules \
|
||||
--exclude=result \
|
||||
--exclude='result-*' \
|
||||
-cf - . | tar -C "$HOST_SOURCE_TMP" -xf -
|
||||
-cf - . | tar -C "$to" -xf -
|
||||
}
|
||||
|
||||
if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
|
||||
echo "==> preparing filtered Tribes host source"
|
||||
HOST_SOURCE_TMP="$(mktemp -d -t kobold-tribes-host.XXXXXX)"
|
||||
copy_filtered_source "$HOST_ROOT" "$HOST_SOURCE_TMP"
|
||||
|
||||
echo "==> preparing filtered Trust plugin source"
|
||||
TRUST_SOURCE_TMP="$(mktemp -d -t kobold-trust-plugin.XXXXXX)"
|
||||
copy_filtered_source "$TRUST_ROOT" "$TRUST_SOURCE_TMP"
|
||||
|
||||
echo "==> building kobold e2e image"
|
||||
image_path="$(
|
||||
nix build --impure "$ROOT_DIR#koboldDockerE2E" \
|
||||
--override-input tribes "path:$HOST_SOURCE_TMP" \
|
||||
--override-input trust "path:$TRUST_SOURCE_TMP" \
|
||||
--no-write-lock-file \
|
||||
--out-link "$IMAGE_LINK" \
|
||||
--print-out-paths \
|
||||
|
||||
@@ -61,6 +61,71 @@ defmodule TribeOne.TribesPlugin.Kobold.APITest do
|
||||
assert [%{fields: %{"species" => "Tomato"}}] = records
|
||||
end
|
||||
|
||||
test "remote tribe access uses generic permission rules" do
|
||||
run_id = "kobold-access-#{System.unique_integer([:positive])}"
|
||||
remote = "remote-access-tribe"
|
||||
|
||||
assert {:ok, %{dataset: dataset}} =
|
||||
API.create_dataset(%{
|
||||
"run_id" => run_id,
|
||||
"name" => "Shared notes",
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
assert {:ok, %{resource: _resource}} =
|
||||
API.create_resource_definition(%{
|
||||
"dataset_id" => dataset.id,
|
||||
"name" => "Entry",
|
||||
"fields" => %{"title" => %{"type" => "string", "required" => true}}
|
||||
})
|
||||
|
||||
assert {:ok, %{datasets: []}} =
|
||||
API.state(%{"run_id" => run_id, "remote_tribe_pubkey" => remote})
|
||||
|
||||
assert {:error, :access_denied} =
|
||||
API.upsert_record(%{
|
||||
"dataset_id" => dataset.id,
|
||||
"resource_name" => "Entry",
|
||||
"remote_tribe_pubkey" => remote,
|
||||
"fields" => %{"title" => "Denied"}
|
||||
})
|
||||
|
||||
assert {:ok, _rule} =
|
||||
Tribes.Access.put_rule(%{
|
||||
plugin_name: "kobold",
|
||||
resource_type: "kobold.dataset",
|
||||
resource_id: dataset.id,
|
||||
action: "read",
|
||||
subject_type: "tribe",
|
||||
subject_id: remote,
|
||||
effect: "allow"
|
||||
})
|
||||
|
||||
assert {:ok, %{datasets: [%{id: dataset_id}]}} =
|
||||
API.state(%{"run_id" => run_id, "remote_tribe_pubkey" => remote})
|
||||
|
||||
assert dataset_id == dataset.id
|
||||
|
||||
assert {:ok, _rule} =
|
||||
Tribes.Access.put_rule(%{
|
||||
plugin_name: "kobold",
|
||||
resource_type: "kobold.dataset",
|
||||
resource_id: dataset.id,
|
||||
action: "write",
|
||||
subject_type: "tribe",
|
||||
subject_id: remote,
|
||||
effect: "allow"
|
||||
})
|
||||
|
||||
assert {:ok, %{record: %{fields: %{"title" => "Allowed"}}}} =
|
||||
API.upsert_record(%{
|
||||
"dataset_id" => dataset.id,
|
||||
"resource_name" => "Entry",
|
||||
"remote_tribe_pubkey" => remote,
|
||||
"fields" => %{"title" => "Allowed"}
|
||||
})
|
||||
end
|
||||
|
||||
test "record validation uses resource definition fields" do
|
||||
run_id = "kobold-validation-#{System.unique_integer([:positive])}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user