mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-14 12:16:20 +00:00
Roles audit and revamp
This commit is contained in:
1 parent
208d34abc7
commit
98afa5d93c
134 files changed
+13725
-1702
No files matched your search
@@ -0,0 +1,205 @@
|
||||
"""Tests for the single resource-access check (roles + per-asset switches)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from docsgpt.api.user.resource_access import (
|
||||
AccessDenied,
|
||||
allowed_actions,
|
||||
public_settings,
|
||||
require,
|
||||
resolve,
|
||||
set_settings,
|
||||
settings_for,
|
||||
settings_many,
|
||||
)
|
||||
from docsgpt.storage.db.repositories.agents import AgentsRepository
|
||||
from docsgpt.storage.db.repositories.team_members import TeamMembersRepository
|
||||
from docsgpt.storage.db.repositories.team_resource_grants import (
|
||||
TeamResourceGrantsRepository,
|
||||
)
|
||||
from docsgpt.storage.db.repositories.teams import TeamsRepository
|
||||
|
||||
|
||||
def _team(conn, owner="alice"):
|
||||
return TeamsRepository(conn).create("Acme", f"acme-{uuid.uuid4().hex[:8]}", owner)
|
||||
|
||||
|
||||
def _share(conn, team, resource_type, resource_id, level, member="bob", owner="alice"):
|
||||
TeamMembersRepository(conn).add_member(str(team["id"]), member)
|
||||
TeamResourceGrantsRepository(conn).grant(
|
||||
str(team["id"]), resource_type, resource_id, owner, owner, access_level=level
|
||||
)
|
||||
|
||||
|
||||
def _agent(conn, owner="alice"):
|
||||
return str(AgentsRepository(conn).create(owner, "A", "published")["id"])
|
||||
|
||||
|
||||
class TestAllowedActions:
|
||||
def test_owner_gets_everything(self):
|
||||
actions = allowed_actions("agent", "owner", {})
|
||||
assert {"delete", "share", "manage_settings", "move_folder", "edit"} <= actions
|
||||
|
||||
def test_agent_editor_defaults(self):
|
||||
actions = allowed_actions("agent", "editor", {})
|
||||
assert {"use", "edit", "publish", "view_logs", "manage_schedules", "export",
|
||||
"manage_access_details", "edit_policy"} <= actions
|
||||
assert not {"delete", "share", "manage_settings", "move_folder"} & actions
|
||||
|
||||
def test_agent_viewer_defaults(self):
|
||||
actions = allowed_actions("agent", "viewer", {})
|
||||
assert actions == {"use", "pin"}
|
||||
|
||||
def test_switches_widen_and_narrow(self):
|
||||
settings = {
|
||||
"editors_can_share": True,
|
||||
"editors_can_delete": True,
|
||||
"editors_can_manage_access_details": False,
|
||||
"viewers_can_see_logs": True,
|
||||
}
|
||||
editor = allowed_actions("agent", "editor", settings)
|
||||
assert {"share", "delete"} <= editor
|
||||
assert "manage_access_details" not in editor
|
||||
assert "view_logs" in allowed_actions("agent", "viewer", settings)
|
||||
|
||||
def test_switches_never_touch_owner_only_settings(self):
|
||||
# Even with every switch on, managing the switches stays owner-only.
|
||||
settings = {"editors_can_share": True, "editors_can_delete": True}
|
||||
assert "manage_settings" not in allowed_actions("agent", "editor", settings)
|
||||
|
||||
def test_tool_viewer_can_use_in_own_by_default(self):
|
||||
assert "use_in_own" in allowed_actions("tool", "viewer", {})
|
||||
assert "use_in_own" not in allowed_actions(
|
||||
"tool", "viewer", {"viewers_can_use_in_agents": False}
|
||||
)
|
||||
assert "edit_credentials" in allowed_actions("tool", "editor", {})
|
||||
assert "edit_credentials" not in allowed_actions(
|
||||
"tool", "editor", {"editors_can_change_credentials": False}
|
||||
)
|
||||
|
||||
def test_source_and_prompt_defaults(self):
|
||||
assert "view_config" in allowed_actions("source", "viewer", {})
|
||||
assert "edit" not in allowed_actions("source", "viewer", {})
|
||||
assert "edit" in allowed_actions("source", "editor", {})
|
||||
assert "reconnect" not in allowed_actions("source", "editor", {})
|
||||
assert "duplicate" in allowed_actions("prompt", "viewer", {})
|
||||
assert "edit" in allowed_actions("prompt", "editor", {})
|
||||
assert "edit" not in allowed_actions("prompt", "viewer", {})
|
||||
|
||||
def test_no_access_means_nothing(self):
|
||||
assert allowed_actions("agent", None, {}) == set()
|
||||
|
||||
|
||||
class TestSettings:
|
||||
def test_defaults_when_no_row(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
assert settings_for(pg_conn, "agent", aid) == {
|
||||
"editors_can_share": False,
|
||||
"editors_can_delete": False,
|
||||
"editors_can_manage_access_details": True,
|
||||
"viewers_can_see_logs": False,
|
||||
}
|
||||
|
||||
def test_set_and_read_back(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
set_settings(pg_conn, "agent", aid, {"viewers_can_see_logs": True}, "alice")
|
||||
assert settings_for(pg_conn, "agent", aid)["viewers_can_see_logs"] is True
|
||||
# A partial update keeps earlier values.
|
||||
set_settings(pg_conn, "agent", aid, {"editors_can_share": True}, "alice")
|
||||
merged = settings_for(pg_conn, "agent", aid)
|
||||
assert merged["viewers_can_see_logs"] is True
|
||||
assert merged["editors_can_share"] is True
|
||||
|
||||
def test_unknown_key_rejected(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
with pytest.raises(ValueError):
|
||||
set_settings(pg_conn, "agent", aid, {"viewers_can_delete": True}, "alice")
|
||||
with pytest.raises(ValueError):
|
||||
set_settings(pg_conn, "agent", aid, {"editors_can_share": "yes"}, "alice")
|
||||
|
||||
def test_settings_many(self, pg_conn):
|
||||
a1, a2 = _agent(pg_conn), _agent(pg_conn)
|
||||
set_settings(pg_conn, "agent", a1, {"editors_can_delete": True}, "alice")
|
||||
many = settings_many(pg_conn, "agent", [a1, a2, "not-a-uuid"])
|
||||
assert many[a1]["editors_can_delete"] is True
|
||||
assert many[a2]["editors_can_delete"] is False
|
||||
assert many["not-a-uuid"]["editors_can_delete"] is False
|
||||
|
||||
def test_public_settings_shape(self):
|
||||
rows = public_settings("prompt", {"editors_can_share": True})
|
||||
assert [r["key"] for r in rows] == ["editors_can_share", "viewers_can_duplicate"]
|
||||
assert rows[0] == {"key": "editors_can_share", "value": True, "default": False}
|
||||
|
||||
|
||||
class TestResolve:
|
||||
def test_owner(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
ra = resolve(pg_conn, "agent", aid, "alice")
|
||||
assert ra.access == "owner"
|
||||
assert ra.owner_id == "alice"
|
||||
assert ra.can("delete")
|
||||
|
||||
def test_editor_and_viewer_via_team(self, pg_conn):
|
||||
team = _team(pg_conn)
|
||||
aid = _agent(pg_conn)
|
||||
_share(pg_conn, team, "agent", aid, "editor", member="bob")
|
||||
ra = resolve(pg_conn, "agent", aid, "bob")
|
||||
assert ra.access == "editor"
|
||||
assert ra.owner_id == "alice"
|
||||
assert ra.can("view_logs") and not ra.can("delete")
|
||||
|
||||
aid2 = _agent(pg_conn)
|
||||
_share(pg_conn, team, "agent", aid2, "viewer", member="carol")
|
||||
rv = resolve(pg_conn, "agent", aid2, "carol")
|
||||
assert rv.access == "viewer"
|
||||
assert rv.actions == frozenset({"use", "pin"})
|
||||
|
||||
def test_switch_applies_to_grantee(self, pg_conn):
|
||||
team = _team(pg_conn)
|
||||
aid = _agent(pg_conn)
|
||||
_share(pg_conn, team, "agent", aid, "viewer")
|
||||
set_settings(pg_conn, "agent", aid, {"viewers_can_see_logs": True}, "alice")
|
||||
assert resolve(pg_conn, "agent", aid, "bob").can("view_logs")
|
||||
|
||||
def test_stranger_and_bad_ids(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
assert resolve(pg_conn, "agent", aid, "mallory") is None
|
||||
assert resolve(pg_conn, "agent", "not-a-uuid", "alice") is None
|
||||
assert resolve(pg_conn, "agent", str(uuid.uuid4()), "alice") is None
|
||||
assert resolve(pg_conn, "nope", aid, "alice") is None
|
||||
|
||||
def test_payload(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
payload = resolve(pg_conn, "agent", aid, "alice").payload()
|
||||
assert payload["access"] == "owner"
|
||||
assert "delete" in payload["allowed_actions"]
|
||||
assert payload["allowed_actions"] == sorted(payload["allowed_actions"])
|
||||
|
||||
|
||||
class TestRequire:
|
||||
def test_allowed_returns_access(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
assert require(pg_conn, "agent", aid, "alice", "delete").owner_id == "alice"
|
||||
|
||||
def test_visible_but_not_allowed_is_403(self, pg_conn):
|
||||
team = _team(pg_conn)
|
||||
aid = _agent(pg_conn)
|
||||
_share(pg_conn, team, "agent", aid, "viewer")
|
||||
with pytest.raises(AccessDenied) as exc:
|
||||
require(pg_conn, "agent", aid, "bob", "edit")
|
||||
assert exc.value.status == 403
|
||||
|
||||
def test_invisible_is_404(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
with pytest.raises(AccessDenied) as exc:
|
||||
require(pg_conn, "agent", aid, "mallory", "use")
|
||||
assert exc.value.status == 404
|
||||
|
||||
def test_unknown_action_is_a_bug(self, pg_conn):
|
||||
aid = _agent(pg_conn)
|
||||
with pytest.raises(KeyError):
|
||||
require(pg_conn, "agent", aid, "alice", "fly")
|
||||
Reference in new issue
Block a user