|
1 | 1 | import logging |
2 | 2 | import uuid |
| 3 | +from collections import defaultdict |
3 | 4 | from collections.abc import Collection, Iterable, Sequence |
4 | 5 | from dataclasses import dataclass, field |
5 | 6 | from typing import Any, cast |
|
40 | 41 | BulkRoleRevocationFailure, |
41 | 42 | BulkRoleRevocationResultData, |
42 | 43 | BulkUserRoleRevocationInput, |
| 44 | + EffectivePermissionsInput, |
| 45 | + EffectivePermissionsResult, |
43 | 46 | ProjectRoleCount, |
44 | 47 | RoleListResult, |
45 | 48 | RolePermissionsUpdateInput, |
@@ -1088,6 +1091,88 @@ async def check_bulk_permission_with_scope_chain( |
1088 | 1091 | ) |
1089 | 1092 | return {eid: eid in granted for eid in data.target_entity_ids} |
1090 | 1093 |
|
| 1094 | + async def resolve_effective_permissions( |
| 1095 | + self, |
| 1096 | + data: EffectivePermissionsInput, |
| 1097 | + ) -> EffectivePermissionsResult: |
| 1098 | + """Resolve the effective permissions for a user across multiple entities. |
| 1099 | +
|
| 1100 | + Uses a single batched query that traverses the scope chain (AUTO edges) |
| 1101 | + and self-scope permissions to collect all operations the user can perform |
| 1102 | + on each entity. |
| 1103 | +
|
| 1104 | + Returns a mapping from entity ID to the set of permitted operations. |
| 1105 | + """ |
| 1106 | + if not data.target_entity_ids: |
| 1107 | + return EffectivePermissionsResult(permissions={}) |
| 1108 | + |
| 1109 | + association_entity_type = data.target_element_type.to_entity_type() |
| 1110 | + permission_entity_type = data.permission_entity_type or association_entity_type |
| 1111 | + target_scope_type = data.target_element_type.to_scope_type() |
| 1112 | + |
| 1113 | + perm = PermissionRow.__table__ |
| 1114 | + user_roles = UserRoleRow.__table__ |
| 1115 | + roles = RoleRow.__table__ |
| 1116 | + |
| 1117 | + scope_chain_cte = self._build_scope_chain_cte( |
| 1118 | + association_entity_type, data.target_entity_ids |
| 1119 | + ) |
| 1120 | + scope_chain_query = ( |
| 1121 | + sa.select( |
| 1122 | + scope_chain_cte.c.entity_id, |
| 1123 | + perm.c.operation, |
| 1124 | + ) |
| 1125 | + .select_from( |
| 1126 | + scope_chain_cte.join( |
| 1127 | + perm, |
| 1128 | + sa.and_( |
| 1129 | + perm.c.scope_type == scope_chain_cte.c.scope_type, |
| 1130 | + perm.c.scope_id == scope_chain_cte.c.scope_id, |
| 1131 | + ), |
| 1132 | + ) |
| 1133 | + .join(roles, roles.c.id == perm.c.role_id) |
| 1134 | + .join(user_roles, user_roles.c.role_id == roles.c.id) |
| 1135 | + ) |
| 1136 | + .where( |
| 1137 | + sa.and_( |
| 1138 | + user_roles.c.user_id == data.user_id, |
| 1139 | + roles.c.status == RoleStatus.ACTIVE, |
| 1140 | + perm.c.entity_type == permission_entity_type, |
| 1141 | + ) |
| 1142 | + ) |
| 1143 | + ) |
| 1144 | + |
| 1145 | + self_scope_query = ( |
| 1146 | + sa.select( |
| 1147 | + perm.c.scope_id.label("entity_id"), |
| 1148 | + perm.c.operation, |
| 1149 | + ) |
| 1150 | + .select_from( |
| 1151 | + perm.join(roles, roles.c.id == perm.c.role_id).join( |
| 1152 | + user_roles, user_roles.c.role_id == roles.c.id |
| 1153 | + ) |
| 1154 | + ) |
| 1155 | + .where( |
| 1156 | + sa.and_( |
| 1157 | + user_roles.c.user_id == data.user_id, |
| 1158 | + roles.c.status == RoleStatus.ACTIVE, |
| 1159 | + perm.c.scope_type == target_scope_type, |
| 1160 | + perm.c.scope_id.in_(data.target_entity_ids), |
| 1161 | + perm.c.entity_type == permission_entity_type, |
| 1162 | + ) |
| 1163 | + ) |
| 1164 | + ) |
| 1165 | + |
| 1166 | + combined_query = sa.union_all(scope_chain_query, self_scope_query) |
| 1167 | + |
| 1168 | + permissions: defaultdict[str, set[OperationType]] = defaultdict(set) |
| 1169 | + async with self._db.begin_readonly_session_read_committed() as db_session: |
| 1170 | + result = await db_session.execute(combined_query) |
| 1171 | + for row in result: |
| 1172 | + permissions[row.entity_id].add(OperationType(row.operation)) |
| 1173 | + |
| 1174 | + return EffectivePermissionsResult(permissions=permissions) |
| 1175 | + |
1091 | 1176 | async def bulk_assign_role( |
1092 | 1177 | self, bulk_creator: BulkCreator[UserRoleRow] |
1093 | 1178 | ) -> BulkCreatorResultWithFailures[UserRoleRow]: |
|
0 commit comments