- Problem description
In an enterprise role- and group-based permission system (RBAC), there are three kinds of entities: users (User), groups (Group), and cameras (Camera). The input is a list of permission relations, perms_data, where every element is a triple (source, relationship, destination). There are two core relationships:
"camera_owner": the format is (entity, "camera_owner", camera_id). It means the entity (a user or a group) directly owns access to and management of that camera.
"group_member": the format is (entity, "group_member", group_id). It means the entity (a user or a sub-group) belongs to the target group. Permissions are transitive: a member automatically inherits all the camera permissions held by that group and by the groups above it in the nesting.
The function to implement:
def find_admin_user(perms_data: list[tuple[str, str, str]]) -> str | None:
...
Find the one super admin user (ID starting with "user_") who has access to every camera in the whole organization. If no single user can cover all the cameras, return None.
- Input and output examples
Example 1
perms_data = [
("user_admin", "group_member", "group_sec"),
("group_sec", "camera_owner", "camera_1"),
("group_sec", "camera_owner", "camera_2"),
("user_admin", "camera_owner", "camera_3"),
("user_bob", "camera_owner", "camera_1"),
]
Output: "user_admin". The set of all cameras in the system is {"camera_1", "camera_2", "camera_3"}. user_admin directly owns camera_3 and inherits camera_1 and camera_2 through group_sec, so it covers all the cameras.
Example 2 (multi-level nested groups)
perms_data = [
("user_super", "group_member", "group_exec"),
("group_exec", "group_member", "group_ops"),
("group_ops", "camera_owner", "camera_hq"),
("user_super", "camera_owner", "camera_branch"),
]
Output: "user_super". user_super inherits permissions through the multi-hop chain user_super -> group_exec -> group_ops -> camera_hq, and also directly owns camera_branch.
Example 3 (no super admin)
perms_data = [
("user_alice", "camera_owner", "camera_1"),
("user_bob", "camera_owner", "camera_2"),
]
Output: None. No single user covers 100% of the cameras.
- Constraints and edge cases
Naming rules: users are prefixed with "user_", groups with "group_", and cameras with "camera_". Admin identity: the super admin that gets returned has to be a user entity (user_*), never a group. Circular dependencies: groups can have two-way or circular membership (for example A belongs to B and B belongs to A), so the algorithm must not loop forever. Edge case: if the input has no cameras at all, return None.
- Core solution
Graph modeling: treat the entities as nodes and build a directed graph. Every triple (src, rel, dst) becomes a directed edge from src to dst, representing the direction permissions flow.
Extract the target sets: go through the data once to get the full set of cameras, total_cams (everything that appears as the target of a "camera_owner" relationship), and the set of candidate users, which is every entity prefixed with "user_".
Reachability search (DFS / BFS): for each candidate user, run a traversal and use a seen set to record visited nodes so cycles don't cause infinite loops, collecting every reachable camera_*.
Set matching: check whether the candidate user's reachable camera set equals total_cams, and if it does, return that user's ID. If the traversal finishes with no match, return None.
Discussion
Loading comments…