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
