Initial SFERA platform baseline
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# sfera-security-core
|
||||
|
||||
Security primitives for SFERA.
|
||||
|
||||
Provides:
|
||||
|
||||
- RBAC roles, grants, permission checks, and effective permissions;
|
||||
- default admin/developer/viewer policy;
|
||||
- privacy modes and classifications;
|
||||
- project/target scoped privacy markers;
|
||||
- AI usage policy configuration.
|
||||
@@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "sfera-security-core"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"pydantic>=2.0",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
@@ -0,0 +1,147 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Permission(str, Enum):
|
||||
INDEX_PROJECT = "INDEX_PROJECT"
|
||||
READ_GRAPH = "READ_GRAPH"
|
||||
WRITE_KNOWLEDGE = "WRITE_KNOWLEDGE"
|
||||
MANAGE_TASKS = "MANAGE_TASKS"
|
||||
MANAGE_USERS = "MANAGE_USERS"
|
||||
ADMIN = "ADMIN"
|
||||
|
||||
|
||||
class PrivacyMode(str, Enum):
|
||||
LOCAL_ONLY = "LOCAL_ONLY"
|
||||
WORKSPACE_SHARED = "WORKSPACE_SHARED"
|
||||
EXTERNAL_ALLOWED = "EXTERNAL_ALLOWED"
|
||||
|
||||
|
||||
class PrivacyClassification(str, Enum):
|
||||
PUBLIC = "PUBLIC"
|
||||
INTERNAL = "INTERNAL"
|
||||
CONFIDENTIAL = "CONFIDENTIAL"
|
||||
PERSONAL_DATA = "PERSONAL_DATA"
|
||||
SECRET = "SECRET"
|
||||
|
||||
|
||||
class PrivacyMarker(BaseModel):
|
||||
project_id: str
|
||||
target_id: str
|
||||
classification: PrivacyClassification
|
||||
reason: str | None = None
|
||||
attributes: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
class Role(BaseModel):
|
||||
role_id: str
|
||||
name: str
|
||||
permissions: set[Permission] = Field(default_factory=set)
|
||||
|
||||
|
||||
class UserAccess(BaseModel):
|
||||
user_id: str
|
||||
roles: set[str] = Field(default_factory=set)
|
||||
|
||||
|
||||
class RbacPolicy(BaseModel):
|
||||
roles: dict[str, Role] = Field(default_factory=dict)
|
||||
users: dict[str, UserAccess] = Field(default_factory=dict)
|
||||
|
||||
def grant_role(self, user_id: str, role_id: str) -> None:
|
||||
if role_id not in self.roles:
|
||||
raise KeyError(f"Unknown role: {role_id}")
|
||||
access = self.users.setdefault(user_id, UserAccess(user_id=user_id))
|
||||
access.roles.add(role_id)
|
||||
|
||||
def is_allowed(self, user_id: str, permission: Permission) -> bool:
|
||||
access = self.users.get(user_id)
|
||||
if access is None:
|
||||
return False
|
||||
for role_id in access.roles:
|
||||
role = self.roles.get(role_id)
|
||||
if role and (Permission.ADMIN in role.permissions or permission in role.permissions):
|
||||
return True
|
||||
return False
|
||||
|
||||
def effective_permissions(self, user_id: str) -> set[Permission]:
|
||||
access = self.users.get(user_id)
|
||||
if access is None:
|
||||
return set()
|
||||
permissions: set[Permission] = set()
|
||||
for role_id in access.roles:
|
||||
role = self.roles.get(role_id)
|
||||
if role is None:
|
||||
continue
|
||||
if Permission.ADMIN in role.permissions:
|
||||
return set(Permission)
|
||||
permissions.update(role.permissions)
|
||||
return permissions
|
||||
|
||||
|
||||
class AiUsagePolicy(BaseModel):
|
||||
privacy_mode: PrivacyMode = PrivacyMode.LOCAL_ONLY
|
||||
allow_code_context: bool = False
|
||||
allow_external_calls: bool = False
|
||||
token_limit_per_day: int | None = None
|
||||
|
||||
|
||||
class InMemoryPrivacyStore:
|
||||
def __init__(self) -> None:
|
||||
self.markers: dict[str, PrivacyMarker] = {}
|
||||
|
||||
def upsert_marker(self, marker: PrivacyMarker) -> PrivacyMarker:
|
||||
self.markers[self._key(marker.project_id, marker.target_id)] = marker
|
||||
return marker
|
||||
|
||||
def markers_for_project(self, project_id: str) -> list[PrivacyMarker]:
|
||||
return sorted(
|
||||
[
|
||||
marker
|
||||
for marker in self.markers.values()
|
||||
if marker.project_id == project_id
|
||||
],
|
||||
key=lambda item: item.target_id,
|
||||
)
|
||||
|
||||
def marker_for_target(self, project_id: str, target_id: str) -> PrivacyMarker | None:
|
||||
return self.markers.get(self._key(project_id, target_id))
|
||||
|
||||
def _key(self, project_id: str, target_id: str) -> str:
|
||||
return f"{project_id}:{target_id}"
|
||||
|
||||
|
||||
def default_rbac_policy() -> RbacPolicy:
|
||||
return RbacPolicy(
|
||||
roles={
|
||||
"admin": Role(role_id="admin", name="Administrator", permissions={Permission.ADMIN}),
|
||||
"developer": Role(
|
||||
role_id="developer",
|
||||
name="Developer",
|
||||
permissions={
|
||||
Permission.INDEX_PROJECT,
|
||||
Permission.READ_GRAPH,
|
||||
Permission.WRITE_KNOWLEDGE,
|
||||
Permission.MANAGE_TASKS,
|
||||
},
|
||||
),
|
||||
"viewer": Role(role_id="viewer", name="Viewer", permissions={Permission.READ_GRAPH}),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AiUsagePolicy",
|
||||
"InMemoryPrivacyStore",
|
||||
"Permission",
|
||||
"PrivacyClassification",
|
||||
"PrivacyMarker",
|
||||
"PrivacyMode",
|
||||
"RbacPolicy",
|
||||
"Role",
|
||||
"UserAccess",
|
||||
"default_rbac_policy",
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
from security_core import (
|
||||
InMemoryPrivacyStore,
|
||||
Permission,
|
||||
PrivacyClassification,
|
||||
PrivacyMarker,
|
||||
default_rbac_policy,
|
||||
)
|
||||
|
||||
|
||||
def test_rbac_allows_permissions_from_granted_role():
|
||||
policy = default_rbac_policy()
|
||||
policy.grant_role("user.1", "developer")
|
||||
|
||||
assert policy.is_allowed("user.1", Permission.INDEX_PROJECT)
|
||||
assert not policy.is_allowed("user.1", Permission.MANAGE_USERS)
|
||||
assert policy.effective_permissions("user.1") == {
|
||||
Permission.INDEX_PROJECT,
|
||||
Permission.READ_GRAPH,
|
||||
Permission.WRITE_KNOWLEDGE,
|
||||
Permission.MANAGE_TASKS,
|
||||
}
|
||||
|
||||
|
||||
def test_admin_effective_permissions_expand_to_all_permissions():
|
||||
policy = default_rbac_policy()
|
||||
policy.grant_role("user.1", "admin")
|
||||
|
||||
assert policy.effective_permissions("user.1") == set(Permission)
|
||||
|
||||
|
||||
def test_privacy_store_is_project_and_target_scoped():
|
||||
store = InMemoryPrivacyStore()
|
||||
marker = store.upsert_marker(
|
||||
PrivacyMarker(
|
||||
project_id="demo",
|
||||
target_id="lineage.attribute.phone",
|
||||
classification=PrivacyClassification.PERSONAL_DATA,
|
||||
reason="Phone number",
|
||||
)
|
||||
)
|
||||
|
||||
assert store.markers_for_project("demo") == [marker]
|
||||
assert store.marker_for_target("demo", "lineage.attribute.phone") == marker
|
||||
Reference in New Issue
Block a user