Authorization

Beyond authentication (verifying who a user is), authorization controls what an authenticated user is allowed to do.

The framework provides a flexible and robust system to define and enforce permissions, ensuring users can only access the resources and functionalities they are authorized for.

1. Permissions System: Role-Based Access Control (RBAC) for Standard Users

The core of the standard user authorization system is built around a Role-Based Access Control (RBAC) model, allowing you to define granular permissions and organize them effectively for your application’s regular users.

  • ``UserPermission`` Model: This model represents individual, distinct permissions (e.g., blog.add_post, users.view_profile). You can define as many specific permissions as your application needs for its regular user base.

  • ``Group`` Model: Groups act as roles for standard users. You can create groups (e.g., “Editors”, “Moderators”, “Managers”) and assign a collection of UserPermission objects to each group. This simplifies permission management, as you assign users to groups rather than individually assigning many permissions to each user.

  • ``User`` Model: Users are then assigned to one or more Group objects. A user inherits all permissions from the groups they belong to. The User model includes a robust has_permission(permission_name: str) -> bool method which efficiently checks if a user possesses a specific permission, considering their group memberships.

Key Features (Standard Users):

  • Granular Control: Define specific permissions that represent actions or access rights for standard users.

  • Efficient Checks: The has_permission method on the User model uses caching to optimize performance, reducing database queries for frequently checked permissions.

2. Managing Permissions, Groups, and Users for Standard Users

The framework’s generic Admin Panel provides a convenient interface for managing your standard user authorization structure:

  • Adding Permissions: Through the Admin Panel, you can define new UserPermission entries, giving them a unique name and an optional description. These represent the individual capabilities in your system.

  • Creating Groups: You can create new Group objects (roles) and assign a name and description.

  • Assigning Permissions to Groups: Crucially, the Admin Panel allows you to easily associate any defined UserPermission with specific Group objects. For example, you could create an “Editor” group and assign it blog.add_post, blog.edit_post, and blog.delete_post permissions.

  • Assigning Users to Groups: Finally, you can assign individual User accounts to one or more Group objects. A user will then inherit all permissions from the groups they are a member of.

This integrated approach means you don’t have to write custom code for basic permission management; it’s all handled through the Admin Panel.

3. Admin User Permissions and Hierarchy

The framework implements a distinct and robust permission system specifically for administrative users, operating separately from the standard user permission structure. This section details the permission hierarchy for admin users and how these permissions interact with the generic admin panel views.

#### Admin User Types and Their Privileges

The administrative users in your system are categorized into three distinct types, each with varying levels of access and control:

  1. Supreme Admin (User ID 1): The Ultimate Authority * Identification: This is typically the very first admin user created in the system, uniquely identified by having an id of 1 in the database. * Permissions: This user automatically possesses all possible permissions across the entire system. They bypass all explicit permission checks and can perform any action, including creating, modifying, and deleting any data or user, including other admin users (Super-admin and Regular Admin types). This user represents the absolute highest level of administrative control.

  2. Super-admin (``is_superuser=True``): Elevated Administrative Control * Identification: These admin users are designated by having their is_superuser flag explicitly set to True in their admin user profile. * Permissions: A Super-admin user inherently receives a broad set of elevated permissions across the system. They can manage their own data and, crucially, have the authority to modify and delete data of Regular Admin users. However, they cannot modify or delete the Supreme Admin (ID 1) or other Super-admin users, unless explicitly granted specific additional permissions via the Admin User Groups. * Control: They are typically responsible for managing the daily administrative operations and overseeing Regular Administrators.

  3. Regular Admin: Standard Administrative Access * Identification: These are administrative users whose is_superuser flag is False. Their administrative access is governed by the specific permissions assigned to them through their associated Admin User Groups. * Permissions: Their access is strictly limited to the permissions explicitly granted to them. For example, a Regular Admin might have add_product and view_product permissions but lack delete_product. * Control: They can only manage the data and sections for which they have explicit permissions. They cannot modify or delete other admin users (neither Supreme Admin, Super-admin, nor other Regular Admins) unless specific administrative management permissions are assigned to their Admin User Groups (which is generally restricted to higher-tier admins).

#### Generic Admin Panel Permissions Enforcement (@PermissionRequired)

The framework’s generic admin panel views are protected by the @PermissionRequired decorator. This decorator dynamically checks if the authenticated admin user has the necessary permission to access a specific view or perform an action on a model.

The permissions required for these generic views are dynamically constructed based on the action and the model_name being accessed. Here, model_name refers to the lowercase name of the database model (e.g., user, product, category) that the view is interacting with.

  • Admin Dashboard Access:
    • Permission Required: @PermissionRequired("view_dashboard")

    • Description: Grants access to the main administrative dashboard.

  • Add Object View:
    • Permission Required: @PermissionRequired(lambda request: f"add_{request.path_params.get('model_name').lower()}" if request.path_params and request.path_params.get('model_name') else "add_unknown_model")

    • Example: To add a new Product via the admin panel, the admin user needs the add_product permission.

    • Description: Allows access to the form for creating a new instance of a specified model.

  • Change Object View:
    • Permission Required: @PermissionRequired(lambda request: f"change_{request.path_params.get('model_name').lower()}" if request.path_params and request.path_params.get('model_name') else "change_unknown_model")

    • Example: To edit an existing User through the admin panel, the admin user needs the change_user permission.

    • Description: Allows access to the form for modifying an existing instance of a specified model.

  • List Objects View:
    • Permission Required: @PermissionRequired(lambda request: f"view_{request.path_params.get('model_name').lower()}" if request.path_params and request.path_params.get('model_name') else "view_unknown_model")

    • Example: To view the list of Products in the admin panel, the admin user needs the view_product permission.

    • Description: Allows admin users to view a list of all instances for a specified model.

  • Detail Object View:
    • Permission Required: @PermissionRequired(lambda request: f"view_{request.path_params.get('model_name').lower()}" if request.path_params and request.path_params.get('model_name') else "view_unknown_model")

    • Example: To view the detailed information of a single User in the admin panel, the admin user needs the view_user permission.

    • Description: Allows admin users to view the detailed information of a single instance of a specified model.

  • Delete Object View:
    • Permission Required: @PermissionRequired(lambda request: f"delete_{request.path_params.get('model_name').lower()}" if request.path_params and request.path_params.get('model_name') else "delete_unknown_model")

    • Example: To delete a Category via the admin panel, the admin user needs the delete_category permission.

    • Description: Allows admin users to delete an instance of a specified model.

Important Note on Dynamic Permissions: The lambda function within @PermissionRequired dynamically constructs the required permission string based on the model_name extracted from the URL path parameters. This ensures that permissions are granular and model-specific. If model_name is not found, a default “unknown_model” permission is used, which typically denies access.

#### Admin Panel Group and Role Management for Admin Users

Admin user permissions are managed within the framework’s Admin Panel itself under the Authentication & Authorization section. This is where you configure the specific administrative roles and their corresponding permissions.

  • Create and Manage Admin User Groups: Define new groups specifically for administrative roles (e.g., Content_Administrators, Product_Supervisors, System_Auditors).

  • Assign Permissions to Admin Groups: Crucially, select specific administrative permissions (e.g., admin.add_user_permission, app_name.change_order, app_name.delete_product) and assign them to the relevant Admin User Groups.

  • Assign Admin Users to Admin Groups: Finally, assign individual Admin User accounts to one or more Admin User Groups. An admin user will then inherit all permissions from the admin groups they are a member of.

  • Manage ``is_superuser`` Flag: For Super-admin users, ensure their is_superuser flag is explicitly set to True in their individual admin user profile within the Admin Panel.

By carefully configuring Admin User Groups and assigning the appropriate permissions, you can precisely control access levels for all your administrative users, ensuring secure and efficient management of your application.

4. Enforcing Authorization with PermissionRequired Decorator (General Usage)

The primary way to enforce authorization on your views, for both standard users and admin users, is by using the PermissionRequired decorator. This decorator ensures that only users (of any type) with the necessary permissions can access a particular view.

How to Use:

You can apply the PermissionRequired decorator to your view functions or methods. It accepts one or more permission strings:

  • Single Permission:

    # myapp/views.py
    from lback.auth.permissions import PermissionRequired
    from lback.utils.shortcuts import render
    
    @PermissionRequired("blog.view_posts")
    def view_blog_posts(request):
        # This view requires the 'blog.view_posts' permission
        # ... fetch blog posts ...
        return render(request, "blog/list.html", {"posts": posts})
    
  • Multiple Permissions (User needs ALL of them):

    # myapp/views.py
    from lback.auth.permissions import PermissionRequired
    from lback.utils.shortcuts import render
    
    @PermissionRequired(["blog.add_post", "blog.publish_post"])
    def create_and_publish_post(request):
        # This view requires BOTH 'blog.add_post' AND 'blog.publish_post' permissions
        # ... logic to create and publish a post ...
        return render(request, "blog/new_post_success.html")
    
  • Dynamic Permissions (Permissions based on request context):

    For more complex scenarios, you can provide a callable (a function) to PermissionRequired. This function will receive the request object and should return the required permission(s) dynamically.

    # myapp/views.py
    from lback.auth.permissions import PermissionRequired
    from lback.utils.shortcuts import render
    
    def get_dynamic_edit_permission(request):
        # Example: permission based on the type of user or object being edited
        if request.user and request.user.is_staff: # Assuming 'is_staff' property on User model
            return "article.edit_all"
        return "article.edit_own"
    
    @PermissionRequired(get_dynamic_edit_permission)
    def edit_article_view(request, article_id):
        # Permissions are determined by the 'get_dynamic_edit_permission' function at runtime
        # ... logic to edit an article ...
        return render(request, "articles/edit.html", {"article_id": article_id})
    

5. Permission Check Flow & Denied Access Handling

When a view decorated with PermissionRequired is accessed:

  1. The framework attempts to retrieve the authenticated user object from the request.

  2. If the user is a Supreme Admin (User ID 1) or a Super-admin (is_superuser=True), access is immediately granted.

  3. Otherwise, the system calls the user.has_permission() method for each required permission.

  4. If the user lacks any of the specified permissions, access is denied.

  5. Denied Access Handling: * If the user is not authenticated at all, they are redirected to the login page (/auth/login/) with a flash message. * If the user is authenticated but lacks the required permissions, they are redirected to a 403 Forbidden page (return_403) with a flash message indicating denied access.

6. Signals for Authorization Flow

The authorization process also dispatches signals, allowing you to hook into the permission checking lifecycle for custom logic or logging:

  • permission_check_started: Broadcast when a permission check begins.
    • Sender: PermissionRequired instance

    • Kwargs: request, required_permissions (set of permissions being checked), user (the user object), view_func_name (name of the view function).

  • permission_check_succeeded: Broadcast when a user successfully passes a permission check.
    • Sender: PermissionRequired instance

    • Kwargs: request, required_permissions, user, view_func_name.

  • permission_check_failed: Broadcast when a user fails a permission check.
    • Sender: PermissionRequired instance

    • Kwargs: request, required_permissions, user, view_func_name, reason (e.g., “user_not_authenticated”, “permission_missing”).