ACL Package - Usage Guide
==========================
WHAT IS THIS PACKAGE?
---------------------
This is a custom Access Control List (ACL) Python package that manages
permissions for users through roles, groups, and object-level rules.
It is a pure Python package with no external dependencies.
HOW TO INSTALL
--------------
From your project folder, run:
pip install -e /path/to/aclforall
Or copy the acl_package folder into your project and import directly.
QUICK START
-----------
from acl_package import User, Role, Permission, Group, PermissionRegistry
# 1. Create permissions
read_perm = Permission(1, "blog.read", "Read Blog Posts")
write_perm = Permission(2, "blog.write", "Write Blog Posts")
# 2. Create a role and assign permissions
editor = Role(1, "editor")
editor.assign_permissions(read_perm, write_perm)
# 3. Create a user and assign the role
user = User(1, "alice", "alice@example.com", "hashed_pw", False, True)
user.assign_role(editor)
# 4. Check permissions
print(user.has_perm("blog.read")) # True
print(user.has_perm("blog.delete")) # False
CLASSES AND WHAT THEY DO
========================
1. Permission
-------------
Represents a single permission like "blog.read" or "admin.delete".
perm = Permission(id=1, name="blog.read", label="Read Blog", effect="allow")
Attributes:
id - unique identifier
permission_name - the permission string (e.g. "blog.read")
label - human-readable description
effect - "allow" or "deny" (default: "allow")
created_at - optional timestamp
Methods:
perm.grant() - set effect to "allow"
perm.deny() - set effect to "deny"
perm.is_allow() - returns True if effect is "allow"
perm.is_deny() - returns True if effect is "deny"
perm.matches("x.y") - checks if this perm matches a name (supports wildcards)
Wildcard example:
perm = Permission(1, "blog.*", "All Blog")
perm.matches("blog.read") # True
perm.matches("blog.write") # True
perm.matches("user.read") # False
2. Role
-------
A role groups multiple permissions together.
editor = Role(1, "editor")
Methods:
editor.assign_permission(perm) - add one permission
editor.assign_permissions(p1, p2) - add multiple
editor.remove_permission(perm) - remove a permission
editor.has_permission("blog.read") - check if role has permission
editor.get_permission_names() - list all permission names
editor.clear_permissions() - remove all permissions
3. Group
--------
A group collects multiple roles. Groups inherit PermissionMixin,
so they can check permissions directly.
team = Group(1, "developers")
Methods:
team.assign_role(role) - add a role
team.assign_roles(r1, r2) - add multiple roles
team.remove_role(role) - remove a role
team.has_role("editor") - check if group has role
team.get_roles() - list roles
team.clear_roles() - remove all roles
Groups can also check permissions directly:
team.has_perm("blog.read") - checks via inherited roles
4. User
-------
The main class. Users can have roles, groups, direct permissions,
and object-level permissions. Users inherit PermissionMixin.
user = User(1, "alice", "alice@test.com", "pw", False, True)
Attributes:
id - unique identifier
username - username string
email - email string
password - password (hashed in real apps)
is_super_admin - True bypasses all permission checks
is_active - account active flag
Role management:
user.assign_role(role) - assign a role
user.assign_roles(r1, r2) - assign multiple
user.remove_role(role) - remove a role
user.has_role("editor") - check if user has role
user.get_roles() - list roles
user.clear_roles() - remove all roles
Group management:
user.add_to_group(group) - add to a group
user.add_to_groups(g1, g2) - add to multiple
user.remove_from_group(group) - remove from group
user.is_in_group("developers") - check group membership
user.get_groups() - list groups
user.clear_groups() - leave all groups
Direct permission management:
user.assign_permission(perm) - assign permission directly
user.assign_permissions(p1, p2) - assign multiple
user.remove_permission(perm) - remove direct permission
user.clear_permissions() - remove all direct permissions
Object-level permission management:
user.assign_object_permission(obj_perm)
user.remove_object_permission(obj_perm)
user.get_object_permissions("document", "doc_123")
user.clear_object_permissions()
Permission checking:
user.has_perm("blog.read") - basic check
user.has_perm("blog.read", "document", "doc_123") - object-level check
user.has_perms("p1", "p2") - ALL must match
user.has_any_perm("p1", "p2") - ANY must match
user.get_all_permission_names() - list all effective perms
user.get_denied_permissions() - list denied perms
user.get_allowed_permissions() - list allowed perms
5. ObjectPermission
-------------------
A permission tied to a specific object instance.
obj_perm = ObjectPermission(
id=1, user=user, group_id=None,
object_type="document", object_id="doc_123",
permission_name="document.read", effect="allow"
)
Methods:
obj_perm.is_allow() - check if allow
obj_perm.is_deny() - check if deny
obj_perm.matches("document", "doc_123") - check if matches object
obj_perm.matches_permission("document.read") - check permission name
6. PermissionRegistry
---------------------
A central store for all defined permissions.
registry = PermissionRegistry()
Methods:
registry.add_permission(perm) - store a permission
registry.add_permissions(p1, p2) - store multiple
registry.get("blog.read") - retrieve by name
registry.exists("blog.read") - check if exists
registry.remove("blog.read") - remove by name
registry.list_all() - list all Permission objects
registry.list_names() - list all permission names
registry.count() - number of permissions
registry.search("blog") - search by partial name
registry.search_by_prefix("blog.") - search by prefix
registry.get_allow_permissions() - list all allow perms
registry.get_deny_permissions() - list all deny perms
registry.clear() - remove all
Singleton pattern:
registry = PermissionRegistry.get_instance() # shared instance
PermissionRegistry.reset_instance() # reset singleton
Supports "in" operator:
if "blog.read" in registry: ...
7. PermissionMixin
------------------
Provides permission checking logic to any class that inherits it.
Methods provided:
has_perm(permission_name, obj_type=None, obj_id=None)
has_perms(*permissions)
has_any_perm(*permissions)
get_all_permission_names()
get_denied_permissions()
get_allowed_permissions()
Used by: User, Group
8. BaseAPI / AdminAPI / ReadOnlyAPI
-----------------------------------
API base classes that inherit PermissionMixin. Use these as base
classes for your API handlers or view classes.
class BlogAPI(BaseAPI):
required_permission = "blog.read"
def list_posts(self):
if not self.allowed():
return {"error": "Access denied"}
return {"posts": ["Post 1", "Post 2"]}
def create_post(self):
if not self.check_permission("blog.write"):
return {"error": "Access denied"}
return {"status": "Post created"}
# Usage
user = User(1, "writer", "w@test.com", "pw", False, True)
user.assign_role(editor_role)
api = BlogAPI(user=user)
api.list_posts() # returns {"posts": ["Post 1", "Post 2"]}
api.create_post() # returns {"status": "Post created"}
BaseAPI methods:
api.set_user(user) - set the user
api.check_permission("blog.read") - check a permission
api.allowed() - check required_permission
api.get_user_permissions() - list user permissions
api.get_effective_permissions() - list all effective perms
AdminAPI:
Inherits BaseAPI. Adds admin_allowed() which checks is_super_admin
or required_permission = "admin.*"
ReadOnlyAPI:
Inherits BaseAPI. Adds read_allowed() and list_allowed() helpers.
HOW PERMISSION CHECKING WORKS (Flow)
=====================================
When you call user.has_perm("blog.create"):
1. Check if user.is_super_admin is True
- If yes, return True immediately (bypass)
2. Collect ALL permissions from:
a. user.roles -> role.permissions
b. user.groups -> group.roles -> role.permissions
c. user.permissions (direct permissions)
d. user.object_permissions (if obj_type/obj_id provided)
3. Deduplicate collected permissions
4. First pass - check for DENY:
- If any matching permission has effect="deny", return False
5. Second pass - check for ALLOW:
- If any matching permission has effect="allow", return True
6. If nothing matched, return False
Wildcard matching:
"blog.*" matches "blog.read", "blog.write", "blog.anything"
"api.user.*" matches "api.user.read", "api.user.write"
Exact match: "blog.read" matches only "blog.read"
PRACTICAL EXAMPLE - COMPLETE SETUP
===================================
from acl_package import User, Role, Permission, Group, PermissionRegistry
# Setup permissions
registry = PermissionRegistry()
perms = {
"blog.read": Permission(1, "blog.read", "Read Blog", "allow"),
"blog.write": Permission(2, "blog.write", "Write Blog", "allow"),
"blog.delete": Permission(3, "blog.delete", "Delete Blog", "deny"),
"admin.*": Permission(4, "admin.*", "All Admin", "allow"),
}
registry.add_permissions(*perms.values())
# Setup roles
editor = Role(1, "editor")
editor.assign_permissions(perms["blog.read"], perms["blog.write"])
viewer = Role(2, "viewer")
viewer.assign_permission(perms["blog.read"])
admin = Role(3, "admin")
admin.assign_permission(perms["admin.*"])
# Setup groups
content_team = Group(1, "content_team")
content_team.assign_role(editor)
# Setup users
alice = User(1, "alice", "alice@test.com", "pw", False, True)
alice.assign_role(viewer)
alice.add_to_group(content_team)
bob = User(2, "bob", "bob@test.com", "pw", False, True)
bob.assign_role(admin)
# Check permissions
alice.has_perm("blog.read") # True (via viewer role + group)
alice.has_perm("blog.write") # True (via content_team group -> editor role)
alice.has_perm("blog.delete") # False (no one has this)
bob.has_perm("blog.delete") # True (admin.* wildcard matches)
# Method chaining
carol = (User(3, "carol", "c@test.com", "pw", False, True)
.assign_role(editor)
.add_to_group(content_team))
carol.has_perm("blog.write") # True
USING IN ANOTHER PROJECT
========================
Option 1: Install as package
pip install -e /path/to/aclforall
# In your project
from acl_package import User, Role, Permission
Option 2: Copy folder
Copy the acl_package folder into your project.
# In your project
from acl_package import User, Role, Permission
Option 3: Add to sys.path
import sys
sys.path.append("/path/to/aclforall")
from acl_package import User, Role, Permission
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
aclforall-0.2.1.tar.gz
(19.2 kB
view details)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
aclforall-0.2.1-py3-none-any.whl
(19.8 kB
view details)
File details
Details for the file aclforall-0.2.1.tar.gz.
File metadata
- Download URL: aclforall-0.2.1.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04af2e4d5cffa977f47e14163c5a0149f4d64aca874f00f58179e9dc8fc3baa1
|
|
| MD5 |
f4c3f53a694b7342a8b19c054d44de74
|
|
| BLAKE2b-256 |
7a8f1aafc0bbcecf0947bfd0401d82a990d6947d40b048a6054efa8148d0ccce
|
File details
Details for the file aclforall-0.2.1-py3-none-any.whl.
File metadata
- Download URL: aclforall-0.2.1-py3-none-any.whl
- Upload date:
- Size: 19.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1039a95a993b9fda653c0b9b06fc644c369ca56c565d80cb74f1f36e63ecd217
|
|
| MD5 |
725486ee2a55f200a2acfbac92cb512a
|
|
| BLAKE2b-256 |
84a9f5db47e9b21542190fc7766ef03d787cde5e2d6456df7b840f019513e15a
|