Spaces:
Running
Running
Ensure workspace roles retain baseline read scopes
Browse files
app/security/studio_identity.py
CHANGED
|
@@ -10,6 +10,32 @@ from app.security.context import AuthContext
|
|
| 10 |
from app.security.scopes import DEFAULT_ROLE_SCOPES
|
| 11 |
from app.security.tenancy import TenantPrincipal
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
_HEX_SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
| 14 |
|
| 15 |
|
|
@@ -79,10 +105,11 @@ def constrain_context_to_membership(
|
|
| 79 |
effective_role = role_map.get(raw_role)
|
| 80 |
if effective_role is None:
|
| 81 |
raise ValueError("Unsupported workspace membership role.")
|
|
|
|
| 82 |
return replace(
|
| 83 |
context,
|
| 84 |
role=effective_role,
|
| 85 |
-
scopes=frozenset(context.scopes.intersection(
|
| 86 |
workspace_id=principal.workspace_id,
|
| 87 |
user_id=principal.user_id,
|
| 88 |
membership_id=principal.membership_id,
|
|
|
|
| 10 |
from app.security.scopes import DEFAULT_ROLE_SCOPES
|
| 11 |
from app.security.tenancy import TenantPrincipal
|
| 12 |
|
| 13 |
+
# Every workspace role is allowed the same baseline read capabilities. Keep this
|
| 14 |
+
# explicit so new UI read endpoints do not fail closed merely because a role's
|
| 15 |
+
# mutation scopes differ.
|
| 16 |
+
STUDIO_BASELINE_READ_SCOPES = frozenset({
|
| 17 |
+
"templates:read",
|
| 18 |
+
"operations:read",
|
| 19 |
+
"jobs:read",
|
| 20 |
+
"assets:read",
|
| 21 |
+
"mcp:read",
|
| 22 |
+
"system:read",
|
| 23 |
+
"social:accounts:read",
|
| 24 |
+
"social:posts:read",
|
| 25 |
+
"social:schedules:read",
|
| 26 |
+
"social:analytics:read",
|
| 27 |
+
"analytics:read",
|
| 28 |
+
"generation:providers:read",
|
| 29 |
+
"generation:requests:read",
|
| 30 |
+
"ai:read",
|
| 31 |
+
"copilot:read",
|
| 32 |
+
"projects:read",
|
| 33 |
+
"members:read",
|
| 34 |
+
"teams:read",
|
| 35 |
+
"comments:read",
|
| 36 |
+
"approvals:read",
|
| 37 |
+
})
|
| 38 |
+
|
| 39 |
_HEX_SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
| 40 |
|
| 41 |
|
|
|
|
| 105 |
effective_role = role_map.get(raw_role)
|
| 106 |
if effective_role is None:
|
| 107 |
raise ValueError("Unsupported workspace membership role.")
|
| 108 |
+
role_scopes = DEFAULT_ROLE_SCOPES[effective_role] | STUDIO_BASELINE_READ_SCOPES
|
| 109 |
return replace(
|
| 110 |
context,
|
| 111 |
role=effective_role,
|
| 112 |
+
scopes=frozenset(context.scopes.intersection(role_scopes)),
|
| 113 |
workspace_id=principal.workspace_id,
|
| 114 |
user_id=principal.user_id,
|
| 115 |
membership_id=principal.membership_id,
|