fix: RBAC bootstrap anthropic for user, FeatureAccess response, workspace UI repair, user access overview, RBAC tests

Made-with: Cursor
This commit is contained in:
ValueOn AG 2026-03-23 10:29:23 +01:00
parent 2345ff669a
commit f796ae3807
6 changed files with 360 additions and 331 deletions

View file

@ -226,6 +226,8 @@ def _syncTemplateRolesToDb() -> int:
if createdCount > 0:
logger.info(f"Feature '{FEATURE_CODE}': Created {createdCount} template roles")
_repairWorkspaceUserInstanceUiNav(rootInterface)
return createdCount
except Exception as e:
@ -233,6 +235,57 @@ def _syncTemplateRolesToDb() -> int:
return 0
def _repairWorkspaceUserInstanceUiNav(rootInterface) -> int:
"""
Ensure every instance-scoped workspace-user role grants UI view on Editor and Settings.
Covers older instance roles copied before template updates (bootstrap / app startup).
"""
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext, Role
workspaceNavObjectKeys = (
"ui.feature.workspace.editor",
"ui.feature.workspace.settings",
)
repairCount = 0
try:
userRoleRecords = rootInterface.db.getRecordset(
Role,
recordFilter={"featureCode": FEATURE_CODE, "roleLabel": "workspace-user"},
)
for roleRec in userRoleRecords or []:
if not roleRec.get("featureInstanceId"):
continue
roleId = str(roleRec.get("id"))
accessRules = rootInterface.getAccessRulesByRole(roleId)
rulesByUiKey = {(r.context, r.item): r for r in accessRules}
for objectKey in workspaceNavObjectKeys:
uiKey = (AccessRuleContext.UI, objectKey)
existingRule = rulesByUiKey.get(uiKey)
if existingRule is None:
newRule = AccessRule(
roleId=roleId,
context=AccessRuleContext.UI,
item=objectKey,
view=True,
read=None,
create=None,
update=None,
delete=None,
)
rootInterface.db.recordCreate(AccessRule, newRule.model_dump())
repairCount += 1
elif not existingRule.view:
rootInterface.db.recordModify(AccessRule, str(existingRule.id), {"view": True})
repairCount += 1
if repairCount:
logger.info(
f"Feature '{FEATURE_CODE}': Repaired {repairCount} UI AccessRules for instance workspace-user roles (Editor/Settings)"
)
except Exception as e:
logger.warning(f"Feature '{FEATURE_CODE}': workspace-user UI nav repair skipped: {e}")
return repairCount
def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: List[Dict[str, Any]]) -> int:
"""Ensure AccessRules exist for a role based on templates."""
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext

View file

@ -1871,7 +1871,7 @@ def _createAicoreProviderRules(db: DatabaseConnector) -> None:
Provider access per role:
- admin: all providers allowed
- user: all providers EXCEPT anthropic (view=False)
- user: all providers allowed
- viewer: NO provider access (viewer has no RESOURCE permissions)
NOTE: Provider list is dynamically discovered from AICore model registry.
@ -1916,7 +1916,7 @@ def _createAicoreProviderRules(db: DatabaseConnector) -> None:
read=None, create=None, update=None, delete=None,
))
# User: access to all providers EXCEPT anthropic
# User: access to all providers (same provider keys as admin)
userId = _getRoleId(db, "user")
if userId:
for provider in providers:
@ -1930,13 +1930,11 @@ def _createAicoreProviderRules(db: DatabaseConnector) -> None:
}
)
if not existingRules:
# Anthropic is not allowed for user role
isAllowed = provider != "anthropic"
providerRules.append(AccessRule(
roleId=userId,
context=AccessRuleContext.RESOURCE,
item=resourceKey,
view=isAllowed,
view=True,
read=None, create=None, update=None, delete=None,
))

View file

@ -1408,7 +1408,7 @@ def update_feature_instance_user_roles(
"userId": userId,
"featureInstanceId": instanceId,
"roleIds": data.roleIds,
"enabled": data.enabled if data.enabled is not None else existingAccess[0].get("enabled", True)
"enabled": data.enabled if data.enabled is not None else bool(existingAccess.enabled),
}
except HTTPException:

View file

@ -278,6 +278,7 @@ def getUserAccessOverview(
# Get mandate name
mandate = interface.getMandate(umMandateId)
mandateName = mandate.name if mandate else umMandateId
mandateLabel = (mandate.label or None) if mandate else None
# Get roles for this UserMandate using interface method
umRoles = interface.getUserMandateRoles(umId)
@ -368,6 +369,7 @@ def getUserAccessOverview(
mandatesInfo.append({
"id": umMandateId,
"name": mandateName,
"label": mandateLabel,
"roleIds": mandateRoleIds,
"featureInstances": featureInstancesInfo,
})

View file

@ -5,18 +5,17 @@ Unit tests for RBAC bootstrap initialization.
Tests that bootstrap creates correct rules and initial data.
"""
import pytest
from unittest.mock import Mock, MagicMock, patch
from unittest.mock import Mock, patch
from modules.interfaces.interfaceBootstrap import (
initBootstrap,
initRootMandate,
initAdminUser,
initEventUser,
initRbacRules,
createDefaultRoleRules,
createTableSpecificRules
_createDefaultRoleRules,
_createTableSpecificRules,
)
from modules.datamodels.datamodelUam import UserInDB, Mandate, AuthAuthority
from modules.datamodels.datamodelUam import UserInDB, Mandate
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
from modules.datamodels.datamodelUam import AccessLevel
@ -27,7 +26,7 @@ class TestRbacBootstrap:
def testInitRootMandateCreatesIfNotExists(self):
"""Test that initRootMandate creates mandate if it doesn't exist."""
db = Mock()
db.getRecordset = Mock(return_value=[]) # No existing mandates
db.getRecordset = Mock(return_value=[])
db.recordCreate = Mock(return_value={"id": "mandate1", "name": "Root"})
mandateId = initRootMandate(db)
@ -36,7 +35,8 @@ class TestRbacBootstrap:
db.recordCreate.assert_called_once()
callArgs = db.recordCreate.call_args
assert isinstance(callArgs[0][1], Mandate)
assert callArgs[0][1].name == "Root"
assert callArgs[0][1].name == "root"
assert callArgs[0][1].label == "Root"
def testInitRootMandateReturnsExisting(self):
"""Test that initRootMandate returns existing mandate ID."""
@ -49,12 +49,12 @@ class TestRbacBootstrap:
db.recordCreate.assert_not_called()
def testInitAdminUserCreatesWithSysadminRole(self):
"""Test that initAdminUser creates user with sysadmin role."""
"""Test that initAdminUser creates user with isSysAdmin=True."""
db = Mock()
db.getRecordset = Mock(return_value=[]) # No existing users
db.getRecordset = Mock(return_value=[])
db.recordCreate = Mock(return_value={"id": "admin1", "username": "admin"})
with patch('modules.interfaces.interfaceBootstrap._getPasswordHash', return_value="hashed"):
with patch("modules.interfaces.interfaceBootstrap._getPasswordHash", return_value="hashed"):
userId = initAdminUser(db, "mandate1")
assert userId == "admin1"
@ -63,15 +63,15 @@ class TestRbacBootstrap:
user = callArgs[0][1]
assert isinstance(user, UserInDB)
assert user.username == "admin"
assert "sysadmin" in user.roleLabels
assert user.isSysAdmin is True
def testInitEventUserCreatesWithSysadminRole(self):
"""Test that initEventUser creates user with sysadmin role."""
"""Test that initEventUser creates user with isSysAdmin=True."""
db = Mock()
db.getRecordset = Mock(return_value=[]) # No existing users
db.getRecordset = Mock(return_value=[])
db.recordCreate = Mock(return_value={"id": "event1", "username": "event"})
with patch('modules.interfaces.interfaceBootstrap._getPasswordHash', return_value="hashed"):
with patch("modules.interfaces.interfaceBootstrap._getPasswordHash", return_value="hashed"):
userId = initEventUser(db, "mandate1")
assert userId == "event1"
@ -80,90 +80,80 @@ class TestRbacBootstrap:
user = callArgs[0][1]
assert isinstance(user, UserInDB)
assert user.username == "event"
assert "sysadmin" in user.roleLabels
assert user.isSysAdmin is True
def testCreateDefaultRoleRules(self):
"""Test that createDefaultRoleRules creates correct default rules."""
"""Test that _createDefaultRoleRules creates admin + viewer generic DATA rules."""
db = Mock()
db.recordCreate = Mock()
createDefaultRoleRules(db)
with patch(
"modules.interfaces.interfaceBootstrap._getRoleId",
side_effect=lambda d, label: f"rid-{label}",
):
_createDefaultRoleRules(db)
# Should create 4 default rules (sysadmin, admin, user, viewer)
assert db.recordCreate.call_count == 4
assert db.recordCreate.call_count == 2
created = [c[0][1] for c in db.recordCreate.call_args_list]
byRoleId = {r.roleId: r for r in created}
# Check sysadmin rule
sysadminCall = [call for call in db.recordCreate.call_args_list
if call[0][1].roleLabel == "sysadmin"][0]
sysadminRule = sysadminCall[0][1]
assert sysadminRule.context == AccessRuleContext.DATA
assert sysadminRule.item is None
assert sysadminRule.view == True
assert sysadminRule.read == AccessLevel.ALL
assert sysadminRule.create == AccessLevel.ALL
adminRule = byRoleId["rid-admin"]
assert adminRule.context == AccessRuleContext.DATA
assert adminRule.item is None
assert adminRule.view is True
assert adminRule.read == AccessLevel.GROUP
assert adminRule.create == AccessLevel.GROUP
# Check user rule
userCall = [call for call in db.recordCreate.call_args_list
if call[0][1].roleLabel == "user"][0]
userRule = userCall[0][1]
assert userRule.read == AccessLevel.MY
assert userRule.create == AccessLevel.MY
viewerRule = byRoleId["rid-viewer"]
assert viewerRule.read == AccessLevel.GROUP
assert viewerRule.create == AccessLevel.NONE
def testCreateTableSpecificRules(self):
"""Test that createTableSpecificRules creates table-specific rules."""
"""Test that _createTableSpecificRules creates table-specific rules."""
db = Mock()
db.recordCreate = Mock()
createTableSpecificRules(db)
with patch(
"modules.interfaces.interfaceBootstrap._getRoleId",
side_effect=lambda d, label: f"rid-{label}",
):
_createTableSpecificRules(db)
# Should create multiple rules for different tables
assert db.recordCreate.call_count > 0
# Check that Mandate table rules are created with full objectKey (UAM namespace)
mandateCalls = [call for call in db.recordCreate.call_args_list
if call[0][1].item == "data.uam.Mandate"]
mandateCalls = [
call for call in db.recordCreate.call_args_list if call[0][1].item == "data.uam.Mandate"
]
assert len(mandateCalls) > 0
# Check that all roles have view=False and no access for Mandate
# (SysAdmin bypasses RBAC via isSysAdmin flag, not via roles)
for call in mandateCalls:
rule = call[0][1]
assert rule.view == False
assert rule.view is False
assert rule.read == AccessLevel.NONE
def testInitRbacRulesSkipsIfExists(self):
"""Test that initRbacRules skips default rule creation if rules already exist, but adds missing table-specific rules."""
"""When AccessRules already exist, init skips full init; ensure-* hooks are not exercised here."""
db = Mock()
# Mock existing rules - include rules for ChatWorkflow and AutomationDefinition to prevent adding missing rules
# Need rules for all required roles to fully prevent creation
# Using semantic namespace format: data.chat.{TableName}, data.automation.{TableName}
existingRules = []
for table in ["data.chat.ChatWorkflow", "data.automation.AutomationDefinition"]:
for role in ["admin", "user", "viewer"]:
existingRules.append({
"id": f"rule_{table}_{role}",
"item": table,
"context": AccessRuleContext.DATA.value,
"roleLabel": role
})
db.getRecordset = Mock(return_value=existingRules)
db.getRecordset = Mock(return_value=[{"id": "existing_rule"}])
db.recordCreate = Mock()
initRbacRules(db)
with patch("modules.interfaces.interfaceBootstrap._ensureUiContextRules"):
with patch("modules.interfaces.interfaceBootstrap._ensureDataContextRules"):
initRbacRules(db)
# Should not create new rules since all required tables already have rules for all roles
db.recordCreate.assert_not_called()
def testInitRbacRulesCreatesIfNotExists(self):
"""Test that initRbacRules creates rules if they don't exist."""
"""Test that initRbacRules creates rules when the AccessRule table is empty."""
db = Mock()
db.getRecordset = Mock(side_effect=[
[], # No existing rules
[] # After creating default rules
])
db.recordCreate = Mock()
db.recordModify = Mock()
db.getRecordset = Mock(return_value=[])
initRbacRules(db)
with patch(
"modules.interfaces.interfaceBootstrap._getRoleId",
side_effect=lambda d, label: f"rid-{label}",
):
initRbacRules(db)
# Should create rules
assert db.recordCreate.call_count > 0

View file

@ -5,12 +5,21 @@ Unit tests for RBAC permission resolution.
Tests rule specificity, multiple roles, and permission combination logic.
"""
import pytest
from modules.datamodels.datamodelUam import User, AccessLevel, UserPermissions
from unittest.mock import Mock
from modules.datamodels.datamodelUam import User, AccessLevel
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
from modules.security.rbac import RbacClass
from modules.connectors.connectorDbPostgre import DatabaseConnector
from unittest.mock import Mock, MagicMock
_TEST_ROLE_USER = "test-rid-user"
_TEST_ROLE_VIEWER = "test-rid-viewer"
def _patchRbacResolution(rbac, roleIds, rulesWithPriority):
"""Stub multi-tenant role loading and rule fetch so tests exercise merge logic only."""
rbac._getRoleIdsForUser = Mock(return_value=roleIds)
rbac._getRulesForRoleIds = Mock(return_value=rulesWithPriority)
class TestRbacPermissionResolution:
@ -18,48 +27,41 @@ class TestRbacPermissionResolution:
def testSingleRoleGenericRule(self):
"""Test permission resolution with a single role and generic rule."""
# Mock database connector
db = Mock(spec=DatabaseConnector)
dbApp = Mock(spec=DatabaseConnector)
# Create RBAC interface
rbac = RbacClass(db, dbApp=dbApp)
# Create user with single role
user = User(
id="user1",
username="testuser",
roleLabels=["user"],
mandateId="mandate1"
mandateId="mandate1",
)
# Mock rules for "user" role
def mockGetRulesForRole(roleLabel, context):
if roleLabel == "user" and context == AccessRuleContext.DATA:
return [
AccessRule(
roleLabel="user",
context=AccessRuleContext.DATA,
item=None, # Generic rule
view=True,
read=AccessLevel.MY,
create=AccessLevel.MY,
update=AccessLevel.MY,
delete=AccessLevel.MY
)
]
return []
rules = [
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item=None,
view=True,
read=AccessLevel.MY,
create=AccessLevel.MY,
update=AccessLevel.MY,
delete=AccessLevel.MY,
),
)
]
_patchRbacResolution(rbac, [_TEST_ROLE_USER], rules)
rbac._getRulesForRole = mockGetRulesForRole
# Get permissions for generic table
permissions = rbac.getUserPermissions(
user,
AccessRuleContext.DATA,
"SomeTable"
"SomeTable",
)
assert permissions.view == True
assert permissions.view is True
assert permissions.read == AccessLevel.MY
assert permissions.create == AccessLevel.MY
assert permissions.update == AccessLevel.MY
@ -75,98 +77,92 @@ class TestRbacPermissionResolution:
id="user1",
username="testuser",
roleLabels=["user"],
mandateId="mandate1"
mandateId="mandate1",
)
def mockGetRulesForRole(roleLabel, context):
if roleLabel == "user" and context == AccessRuleContext.DATA:
return [
AccessRule(
roleLabel="user",
context=AccessRuleContext.DATA,
item=None, # Generic rule
view=True,
read=AccessLevel.GROUP,
create=AccessLevel.GROUP,
update=AccessLevel.GROUP,
delete=AccessLevel.GROUP
),
AccessRule(
roleLabel="user",
context=AccessRuleContext.DATA,
item="data.uam.UserInDB", # Specific rule with UAM namespace
view=True,
read=AccessLevel.MY,
create=AccessLevel.NONE,
update=AccessLevel.MY,
delete=AccessLevel.NONE
)
]
return []
rules = [
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item=None,
view=True,
read=AccessLevel.GROUP,
create=AccessLevel.GROUP,
update=AccessLevel.GROUP,
delete=AccessLevel.GROUP,
),
),
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item="data.uam.UserInDB",
view=True,
read=AccessLevel.MY,
create=AccessLevel.NONE,
update=AccessLevel.MY,
delete=AccessLevel.NONE,
),
),
]
_patchRbacResolution(rbac, [_TEST_ROLE_USER], rules)
rbac._getRulesForRole = mockGetRulesForRole
# Get permissions for UserInDB table - should use specific rule
# Using UAM namespace: data.uam.UserInDB
permissions = rbac.getUserPermissions(
user,
AccessRuleContext.DATA,
"data.uam.UserInDB"
"data.uam.UserInDB",
)
# Most specific rule should win
assert permissions.read == AccessLevel.MY
assert permissions.create == AccessLevel.NONE
assert permissions.update == AccessLevel.MY
assert permissions.delete == AccessLevel.NONE
def testMultipleRolesUnionLogic(self):
"""Test that multiple roles use union (opening) logic."""
"""Test that multiple roles use union (opening) logic for view."""
db = Mock(spec=DatabaseConnector)
dbApp = Mock(spec=DatabaseConnector)
rbac = RbacClass(db, dbApp=dbApp)
# User with multiple roles
user = User(
id="user1",
username="testuser",
roleLabels=["user", "viewer"],
mandateId="mandate1"
mandateId="mandate1",
)
def mockGetRulesForRole(roleLabel, context):
if context == AccessRuleContext.UI:
if roleLabel == "user":
return [
AccessRule(
roleLabel="user",
context=AccessRuleContext.UI,
item="playground",
view=False # User role hides playground
)
]
elif roleLabel == "viewer":
return [
AccessRule(
roleLabel="viewer",
context=AccessRuleContext.UI,
item="playground",
view=True # Viewer role shows playground
)
]
return []
rules = [
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.UI,
item="playground",
view=False,
),
),
(
1,
AccessRule(
roleId=_TEST_ROLE_VIEWER,
context=AccessRuleContext.UI,
item="playground",
view=True,
),
),
]
_patchRbacResolution(rbac, [_TEST_ROLE_USER, _TEST_ROLE_VIEWER], rules)
rbac._getRulesForRole = mockGetRulesForRole
# Get permissions - union logic should make playground visible
permissions = rbac.getUserPermissions(
user,
AccessRuleContext.UI,
"playground"
"playground",
)
# Union logic: if ANY role has view=true, then view=true
assert permissions.view == True
assert permissions.view is True
def testViewFalseOverridesGeneric(self):
"""Test that specific view=false overrides generic view=true."""
@ -178,59 +174,60 @@ class TestRbacPermissionResolution:
id="user1",
username="testuser",
roleLabels=["user"],
mandateId="mandate1"
mandateId="mandate1",
)
def mockGetRulesForRole(roleLabel, context):
if roleLabel == "user" and context == AccessRuleContext.UI:
return [
AccessRule(
roleLabel="user",
context=AccessRuleContext.UI,
item=None, # Generic: view all UI
view=True
),
AccessRule(
roleLabel="user",
context=AccessRuleContext.UI,
item="playground.voice.settings", # Specific: hide this
view=False
)
]
return []
rules = [
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.UI,
item=None,
view=True,
),
),
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.UI,
item="playground.voice.settings",
view=False,
),
),
]
_patchRbacResolution(rbac, [_TEST_ROLE_USER], rules)
rbac._getRulesForRole = mockGetRulesForRole
# Get permissions for specific UI element
permissions = rbac.getUserPermissions(
user,
AccessRuleContext.UI,
"playground.voice.settings"
"playground.voice.settings",
)
# Specific rule should override generic
assert permissions.view == False
assert permissions.view is False
def testNoRolesReturnsNoAccess(self):
"""Test that user with no roles gets no access."""
"""Test that user with no resolved role IDs gets no access."""
db = Mock(spec=DatabaseConnector)
dbApp = Mock(spec=DatabaseConnector)
rbac = RbacClass(db, dbApp=dbApp)
dbApp.getRecordset = Mock(return_value=[])
user = User(
id="user1",
username="testuser",
roleLabels=[], # No roles
mandateId="mandate1"
roleLabels=[],
mandateId="mandate1",
)
permissions = rbac.getUserPermissions(
user,
AccessRuleContext.DATA,
"SomeTable"
"SomeTable",
)
assert permissions.view == False
assert permissions.view is False
assert permissions.read == AccessLevel.NONE
assert permissions.create == AccessLevel.NONE
assert permissions.update == AccessLevel.NONE
@ -244,41 +241,38 @@ class TestRbacPermissionResolution:
rules = [
AccessRule(
roleLabel="user",
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item=None, # Generic
item=None,
view=True,
read=AccessLevel.GROUP
read=AccessLevel.GROUP,
),
AccessRule(
roleLabel="user",
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item="data.uam.UserInDB", # Table-level with UAM namespace
item="data.uam.UserInDB",
view=True,
read=AccessLevel.MY
read=AccessLevel.MY,
),
AccessRule(
roleLabel="user",
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item="data.uam.UserInDB.email", # Field-level - most specific
item="data.uam.UserInDB.email",
view=True,
read=AccessLevel.NONE
)
read=AccessLevel.NONE,
),
]
# Test exact match
rule = rbac.findMostSpecificRule(rules, "data.uam.UserInDB.email")
assert rule is not None
assert rule.item == "data.uam.UserInDB.email"
assert rule.read == AccessLevel.NONE
# Test table-level match
rule = rbac.findMostSpecificRule(rules, "data.uam.UserInDB")
assert rule is not None
assert rule.item == "data.uam.UserInDB"
assert rule.read == AccessLevel.MY
# Test generic fallback
rule = rbac.findMostSpecificRule(rules, "OtherTable")
assert rule is not None
assert rule.item is None
@ -290,57 +284,53 @@ class TestRbacPermissionResolution:
dbApp = Mock(spec=DatabaseConnector)
rbac = RbacClass(db, dbApp=dbApp)
# Valid: Read=MY, Create=MY (allowed)
rule1 = AccessRule(
roleLabel="user",
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item="data.uam.UserInDB",
view=True,
read=AccessLevel.MY,
create=AccessLevel.MY,
update=AccessLevel.MY,
delete=AccessLevel.MY
delete=AccessLevel.MY,
)
assert rbac.validateAccessRule(rule1) == True
assert rbac.validateAccessRule(rule1) is True
# Invalid: Read=MY, Create=GROUP (not allowed - GROUP > MY)
rule2 = AccessRule(
roleLabel="user",
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item="data.uam.UserInDB",
view=True,
read=AccessLevel.MY,
create=AccessLevel.GROUP, # Not allowed
create=AccessLevel.GROUP,
update=AccessLevel.MY,
delete=AccessLevel.MY
delete=AccessLevel.MY,
)
assert rbac.validateAccessRule(rule2) == False
assert rbac.validateAccessRule(rule2) is False
# Valid: Read=GROUP, Create=GROUP (allowed)
rule3 = AccessRule(
roleLabel="admin",
roleId="test-rid-admin",
context=AccessRuleContext.DATA,
item="data.uam.UserInDB",
view=True,
read=AccessLevel.GROUP,
create=AccessLevel.GROUP,
update=AccessLevel.GROUP,
delete=AccessLevel.GROUP
delete=AccessLevel.GROUP,
)
assert rbac.validateAccessRule(rule3) == True
assert rbac.validateAccessRule(rule3) is True
# Invalid: Read=NONE, Create=MY (not allowed - no read access)
rule4 = AccessRule(
roleLabel="user",
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.DATA,
item="data.uam.UserInDB",
view=True,
read=AccessLevel.NONE,
create=AccessLevel.MY, # Not allowed without read
create=AccessLevel.MY,
update=AccessLevel.MY,
delete=AccessLevel.MY
delete=AccessLevel.MY,
)
assert rbac.validateAccessRule(rule4) == False
assert rbac.validateAccessRule(rule4) is False
def testUiContextOnlyViewMatters(self):
"""Test that UI context only checks view permission."""
@ -352,32 +342,29 @@ class TestRbacPermissionResolution:
id="user1",
username="testuser",
roleLabels=["user"],
mandateId="mandate1"
mandateId="mandate1",
)
def mockGetRulesForRole(roleLabel, context):
if roleLabel == "user" and context == AccessRuleContext.UI:
return [
AccessRule(
roleLabel="user",
context=AccessRuleContext.UI,
item="playground",
view=True
# No read/create/update/delete for UI context
)
]
return []
rbac._getRulesForRole = mockGetRulesForRole
rules = [
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.UI,
item="playground",
view=True,
),
)
]
_patchRbacResolution(rbac, [_TEST_ROLE_USER], rules)
permissions = rbac.getUserPermissions(
user,
AccessRuleContext.UI,
"playground"
"playground",
)
assert permissions.view == True
# Other permissions don't matter for UI context
assert permissions.view is True
def testResourceContextOnlyViewMatters(self):
"""Test that RESOURCE context only checks view permission."""
@ -389,27 +376,26 @@ class TestRbacPermissionResolution:
id="user1",
username="testuser",
roleLabels=["user"],
mandateId="mandate1"
mandateId="mandate1",
)
def mockGetRulesForRole(roleLabel, context):
if roleLabel == "user" and context == AccessRuleContext.RESOURCE:
return [
AccessRule(
roleLabel="user",
context=AccessRuleContext.RESOURCE,
item="ai.model.anthropic",
view=True
)
]
return []
rbac._getRulesForRole = mockGetRulesForRole
rules = [
(
1,
AccessRule(
roleId=_TEST_ROLE_USER,
context=AccessRuleContext.RESOURCE,
item="ai.model.anthropic",
view=True,
),
)
]
_patchRbacResolution(rbac, [_TEST_ROLE_USER], rules)
permissions = rbac.getUserPermissions(
user,
AccessRuleContext.RESOURCE,
"ai.model.anthropic"
"ai.model.anthropic",
)
assert permissions.view == True
assert permissions.view is True