profile-scoped ACL policy and redaction

This commit is contained in:
2026-05-07 01:51:44 +02:00
parent e02f78d7e3
commit 88f9df6288
7 changed files with 136 additions and 1 deletions

View File

@@ -50,6 +50,7 @@ class CMISAction(str, Enum):
GET_CHILDREN = "get_children"
GET_OBJECT = "get_object"
GET_CONTENT_STREAM = "get_content_stream"
GET_ACL = "get_acl"
QUERY = "query"
GET_RELATIONSHIPS = "get_relationships"
GET_CHANGE_LOG = "get_change_log"
@@ -77,6 +78,7 @@ ACTION_CAPABILITIES: dict[CMISAction, CMISCapability] = {
CMISAction.GET_CHILDREN: CMISCapability.NAVIGATION,
CMISAction.GET_OBJECT: CMISCapability.OBJECT_READ,
CMISAction.GET_CONTENT_STREAM: CMISCapability.CONTENT_STREAM_READ,
CMISAction.GET_ACL: CMISCapability.ACL,
CMISAction.QUERY: CMISCapability.DISCOVERY_QUERY,
CMISAction.GET_RELATIONSHIPS: CMISCapability.RELATIONSHIPS,
CMISAction.GET_CHANGE_LOG: CMISCapability.CHANGE_LOG,
@@ -431,6 +433,7 @@ class CMISObjectProjection:
content_stream: dict[str, Any] | None = None
version: dict[str, Any] | None = None
relationships: tuple[str, ...] = ()
acl: dict[str, Any] | None = None
def to_dict(self) -> dict[str, Any]:
return compact_dict(
@@ -445,6 +448,7 @@ class CMISObjectProjection:
"content_stream": dict(self.content_stream or {}),
"version": dict(self.version or {}),
"relationships": list(self.relationships),
"acl": dict(self.acl or {}),
}
)
@@ -536,6 +540,7 @@ class CMISDomainMapper:
content_stream=content_stream,
version=self.version_properties(asset, current_version, versions),
relationships=tuple(relationship_ids),
acl=self.acl_for_asset(asset, context),
)
def map_relationship(
@@ -575,6 +580,36 @@ class CMISDomainMapper:
allowable_actions=(CMISAction.GET_OBJECT, CMISAction.GET_RELATIONSHIPS),
)
def acl_for_asset(self, asset: KnowledgeAsset, context: OperationContext) -> dict[str, Any] | None:
visibility = self.access_point.profile.decide_asset_visibility(asset, context)
if not visibility.allowed:
return None
permissions = ["cmis:read"]
if self.access_point.profile.allow_mutations:
permissions.extend(["cmis:write", "cmis:delete"])
entries = [
{
"principal_id": context.actor.id,
"permissions": permissions,
"direct": True,
}
]
if asset.classification.sensitivity == Sensitivity.PUBLIC:
entries.append(
{
"principal_id": "anyone",
"permissions": ["cmis:read"],
"direct": False,
}
)
return {
"object_id": self.asset_object_id(asset.id),
"is_exact": True,
"aces": entries,
"derived_from": "kontextual-profile-policy",
"profile": self.access_point.profile.name,
}
def asset_object_id(self, asset_id: str) -> str:
return f"cmis:asset:{asset_id}"
@@ -679,6 +714,7 @@ class CMISDomainMapper:
candidates = [
CMISAction.GET_OBJECT,
CMISAction.GET_CONTENT_STREAM,
CMISAction.GET_ACL,
CMISAction.GET_RELATIONSHIPS,
CMISAction.UPDATE_PROPERTIES,
CMISAction.DELETE_OBJECT,