Implementing Role-Based Authentication in Python
This challenge focuses on building a fundamental component of secure web applications: role-based access control (RBAC). You will implement a system that assigns roles to users and allows you to check if a user, based on their assigned roles, has permission to perform specific actions. This is a crucial pattern for managing access in applications ranging from simple blogs to enterprise-level software.
Problem Description
Your task is to create a Python system that manages users, their assigned roles, and verifies permissions based on these roles. You will need to design data structures to represent users, roles, and the relationships between them. The core functionality will be a PermissionChecker class that can:
- Add users: Store user information and their associated roles.
- Assign roles to users: Allow adding multiple roles to a single user.
- Define permissions: Specify which actions are allowed for certain roles.
- Check permissions: Determine if a given user is authorized to perform a specific action.
Key Requirements:
- Users should be able to have zero, one, or multiple roles.
- Roles should be distinct entities that can be granted to users.
- Permissions should be associated with roles.
- The permission checking mechanism should be efficient and clear.
Expected Behavior:
- When checking permissions, if a user has any of the roles that grant access to a particular action, the permission should be granted.
- If a user has no roles, or none of their roles grant access to the action, permission should be denied.
Edge Cases to Consider:
- A user with no roles assigned.
- An action that no role is permitted to perform.
- Attempting to check permissions for a user that doesn't exist.
Examples
Example 1:
# Setup
permission_checker = PermissionChecker()
# Add roles and permissions
permission_checker.add_role("admin", ["create_post", "edit_post", "delete_post", "view_dashboard"])
permission_checker.add_role("editor", ["create_post", "edit_post", "view_dashboard"])
permission_checker.add_role("viewer", ["view_dashboard"])
# Add users
permission_checker.add_user("alice", ["admin"])
permission_checker.add_user("bob", ["editor", "viewer"])
permission_checker.add_user("charlie", []) # No roles initially
# Check permissions
print(permission_checker.check_permission("alice", "create_post"))
print(permission_checker.check_permission("bob", "create_post"))
print(permission_checker.check_permission("charlie", "create_post"))
print(permission_checker.check_permission("alice", "view_dashboard"))
print(permission_checker.check_permission("bob", "view_dashboard"))
print(permission_checker.check_permission("charlie", "view_dashboard"))
print(permission_checker.check_permission("alice", "delete_post"))
print(permission_checker.check_permission("bob", "delete_post"))
True
True
False
True
True
False
True
False
Explanation: Alice, with the "admin" role, can create posts. Bob, with the "editor" role, can also create posts. Charlie has no roles, so he cannot create posts. Alice and Bob can both view the dashboard. Charlie cannot. Alice can delete posts, but Bob, as an editor, cannot.
Example 2:
# Setup (Continuing from Example 1)
permission_checker = PermissionChecker()
permission_checker.add_role("admin", ["create_post", "edit_post", "delete_post", "view_dashboard"])
permission_checker.add_role("editor", ["create_post", "edit_post", "view_dashboard"])
permission_checker.add_role("viewer", ["view_dashboard"])
permission_checker.add_user("alice", ["admin"])
permission_checker.add_user("bob", ["editor", "viewer"])
permission_checker.add_user("charlie", [])
# Adding a new user and checking more permissions
permission_checker.add_user("david", ["viewer"])
print(permission_checker.check_permission("david", "view_dashboard"))
print(permission_checker.check_permission("david", "edit_post"))
# Checking permission for a non-existent user
print(permission_checker.check_permission("eve", "create_post"))
True
False
False
Explanation: David, with the "viewer" role, can view the dashboard but not edit posts. Eve is not a registered user, so any permission check for her will return False.
Constraints
- Usernames and role names will be strings.
- Action names will be strings.
- The number of users will not exceed 1,000.
- The number of roles will not exceed 50.
- The number of permissions per role will not exceed 100.
- The
check_permissionmethod should ideally complete in O(R * P) time in the worst case, where R is the number of roles a user has, and P is the number of permissions per role.
Notes
- Consider how you will store the relationship between users and roles, and between roles and permissions. Dictionaries are a good starting point.
- Think about how to efficiently look up permissions for a given action.
- You are encouraged to create a class structure that encapsulates this logic.
- For simplicity, assume that all actions are globally unique and don't need to be scoped to specific resources (e.g., "edit_post" is generic, not "edit_post_123").