diff --git a/trustgraph-base/trustgraph/messaging/translators/iam.py b/trustgraph-base/trustgraph/messaging/translators/iam.py index 1d7bf21c..9e16d3df 100644 --- a/trustgraph-base/trustgraph/messaging/translators/iam.py +++ b/trustgraph-base/trustgraph/messaging/translators/iam.py @@ -5,6 +5,7 @@ from ...schema import ( UserInput, UserRecord, WorkspaceInput, WorkspaceRecord, ApiKeyInput, ApiKeyRecord, + GroupInput, GrantInput, ) from .base import MessageTranslator @@ -43,6 +44,25 @@ def _api_key_input_from_dict(d): ) +def _group_input_from_dict(d): + if d is None: + return None + return GroupInput( + name=d.get("name", ""), + description=d.get("description", ""), + enabled=d.get("enabled", True), + ) + + +def _grant_input_from_dict(d): + if d is None: + return None + return GrantInput( + capability=d.get("capability", ""), + workspace=d.get("workspace", ""), + ) + + def _user_record_to_dict(r): if r is None: return None @@ -102,6 +122,15 @@ class IamRequestTranslator(MessageTranslator): data.get("workspace_record") ), key=_api_key_input_from_dict(data.get("key")), + group_id=data.get("group_id", ""), + member_type=data.get("member_type", ""), + member_id=data.get("member_id", ""), + group=_group_input_from_dict(data.get("group")), + grant=_grant_input_from_dict(data.get("grant")), + capability=data.get("capability", ""), + resource_json=data.get("resource_json", ""), + parameters_json=data.get("parameters_json", ""), + authorise_checks=data.get("authorise_checks", ""), ) def encode(self, obj: IamRequest) -> Dict[str, Any]: @@ -109,6 +138,9 @@ class IamRequestTranslator(MessageTranslator): for fname in ( "workspace", "actor", "user_id", "username", "key_id", "api_key", "password", "new_password", + "group_id", "member_type", "member_id", + "capability", "resource_json", "parameters_json", + "authorise_checks", ): v = getattr(obj, fname, "") if v: @@ -135,6 +167,17 @@ class IamRequestTranslator(MessageTranslator): "name": obj.key.name, "expires": obj.key.expires, } + if obj.group is not None: + result["group"] = { + "name": obj.group.name, + "description": obj.group.description, + "enabled": obj.group.enabled, + } + if obj.grant is not None: + result["grant"] = { + "capability": obj.grant.capability, + "workspace": obj.grant.workspace, + } return result @@ -190,6 +233,23 @@ class IamResponseTranslator(MessageTranslator): # setup, so it can't be dropped by a truthy-only filter. result["bootstrap_available"] = bool(obj.bootstrap_available) + # authorise / authorise-many outputs. + if obj.decision_allow: + result["decision_allow"] = obj.decision_allow + if obj.decision_ttl_seconds: + result["decision_ttl_seconds"] = obj.decision_ttl_seconds + if obj.decisions_json: + result["decisions_json"] = obj.decisions_json + + # Enterprise IAM outputs. + for fname in ( + "group_json", "groups_json", "members_json", + "grants_json", "effective_permissions_json", + ): + v = getattr(obj, fname, "") + if v: + result[fname] = v + return result def encode_with_completion( diff --git a/trustgraph-base/trustgraph/schema/services/iam.py b/trustgraph-base/trustgraph/schema/services/iam.py index 797d6203..6a657d62 100644 --- a/trustgraph-base/trustgraph/schema/services/iam.py +++ b/trustgraph-base/trustgraph/schema/services/iam.py @@ -74,6 +74,21 @@ class ApiKeyRecord: last_used: str = "" +# ---- Enterprise IAM types (additive) ---- + +@dataclass +class GroupInput: + name: str = "" + description: str = "" + enabled: bool = True + + +@dataclass +class GrantInput: + capability: str = "" + workspace: str = "" + + @dataclass class IamRequest: operation: str = "" @@ -99,6 +114,13 @@ class IamRequest: workspace_record: WorkspaceInput | None = None key: ApiKeyInput | None = None + # ---- Enterprise IAM inputs (additive) ---- + group_id: str = "" + member_type: str = "" + member_id: str = "" + group: GroupInput | None = None + grant: GrantInput | None = None + # ---- authorise / authorise-many inputs ---- # Capability string from the vocabulary in capabilities.md. capability: str = "" @@ -164,6 +186,14 @@ class IamResponse: # authorise_checks. decisions_json: str = "" + # ---- Enterprise IAM outputs (additive) ---- + # JSON-serialised payloads for enterprise group/grant operations. + group_json: str = "" + groups_json: str = "" + members_json: str = "" + grants_json: str = "" + effective_permissions_json: str = "" + error: Error | None = None diff --git a/trustgraph-flow/trustgraph/gateway/registry.py b/trustgraph-flow/trustgraph/gateway/registry.py index ca235315..4fba7920 100644 --- a/trustgraph-flow/trustgraph/gateway/registry.py +++ b/trustgraph-flow/trustgraph/gateway/registry.py @@ -506,18 +506,18 @@ _FLOW_SERVICES = { "text-completion": "llm", "prompt": "llm", "mcp-tool": "mcp", - "graph-rag": "graph:read", - "document-rag": "documents:read", + "graph-rag": "graph-rag:read", + "document-rag": "document-rag:read", "embeddings": "embeddings", - "graph-embeddings": "graph:read", - "document-embeddings": "documents:read", - "triples": "graph:read", + "graph-embeddings": "graph-embeddings:read", + "document-embeddings": "document-embeddings:read", + "triples": "triples:read", "rows": "rows:read", - "nlp-query": "rows:read", - "structured-query": "rows:read", - "structured-diag": "rows:read", - "row-embeddings": "rows:read", - "sparql": "graph:read", + "nlp-query": "nlp-query:read", + "structured-query": "structured-query:read", + "structured-diag": "structured-query:read", + "row-embeddings": "row-embeddings:read", + "sparql": "sparql:read", } for _kind, _cap in _FLOW_SERVICES.items(): _register_flow_kind("flow-service", _kind, _cap) @@ -525,10 +525,10 @@ for _kind, _cap in _FLOW_SERVICES.items(): # Streaming import socket endpoints. _FLOW_IMPORTS = { - "triples": "graph:write", - "graph-embeddings": "graph:write", - "document-embeddings": "documents:write", - "entity-contexts": "documents:write", + "triples": "triples:write", + "graph-embeddings": "graph-embeddings:write", + "document-embeddings": "document-embeddings:write", + "entity-contexts": "entity-contexts:write", "rows": "rows:write", } for _kind, _cap in _FLOW_IMPORTS.items(): @@ -537,10 +537,35 @@ for _kind, _cap in _FLOW_IMPORTS.items(): # Streaming export socket endpoints. _FLOW_EXPORTS = { - "triples": "graph:read", - "graph-embeddings": "graph:read", - "document-embeddings": "documents:read", - "entity-contexts": "documents:read", + "triples": "triples:read", + "graph-embeddings": "graph-embeddings:read", + "document-embeddings": "document-embeddings:read", + "entity-contexts": "entity-contexts:read", } for _kind, _cap in _FLOW_EXPORTS.items(): _register_flow_kind("flow-export", _kind, _cap) + + +# --------------------------------------------------------------------------- +# Enterprise IAM operations. +# +# These are additive — they register alongside the OSS IAM operations. +# When the OSS regime receives an unknown operation it returns an error; +# when the enterprise regime is running, it handles them. +# --------------------------------------------------------------------------- + +for _op in ( + "create-group", "get-group", "list-groups", + "update-group", "delete-group", + "add-group-member", "remove-group-member", "list-group-members", + "add-group-grant", "remove-group-grant", "list-group-grants", + "add-user-grant", "remove-user-grant", "list-user-grants", + "resolve-effective-permissions", +): + register(Operation( + name=_op, + capability="iam:admin", + resource_level=ResourceLevel.SYSTEM, + extract_resource=_empty_resource, + extract_parameters=_no_parameters, + )) diff --git a/trustgraph-flow/trustgraph/iam/service/iam.py b/trustgraph-flow/trustgraph/iam/service/iam.py index 5f86e688..037b6bb0 100644 --- a/trustgraph-flow/trustgraph/iam/service/iam.py +++ b/trustgraph-flow/trustgraph/iam/service/iam.py @@ -58,8 +58,18 @@ AUTHZ_CACHE_TTL_SECONDS = 60 _READER_CAPS = { "agent", "graph:read", + "triples:read", + "sparql:read", + "graph-rag:read", + "graph-embeddings:read", "documents:read", + "document-rag:read", + "document-embeddings:read", + "entity-contexts:read", "rows:read", + "nlp-query:read", + "structured-query:read", + "row-embeddings:read", "llm", "embeddings", "mcp", @@ -73,6 +83,10 @@ _READER_CAPS = { _WRITER_CAPS = _READER_CAPS | { "graph:write", + "triples:write", + "graph-embeddings:write", + "document-embeddings:write", + "entity-contexts:write", "documents:write", "rows:write", "collections:write",