Coverage for app / security / routes.py: 59%
59 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-06 04:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-06 04:49 +0000
1"""
2This module handles user registration, role-based access, and admin functionality.
4It defines custom views for managing users and roles using Flask-Admin, enforces security
5permissions, and manages registration workflows. Admin utilities include user and role
6management, menu links, and secure endpoints. This module also defines a route for the
7custom registration page accessible only to admin users.
9Classes:
10- UserModelView: Custom admin view for user management.
11- RoleModelView: Custom admin view for role management.
13Functions:
14- register_admin_views: Registers admin views and appends custom menu links.
15- custom_register: Handles user registration for admin users.
17Dependencies:
18- Flask, Flask-Admin, Flask-Security modules for routing and security features.
19- Flask-SQLAlchemy for database integration.
20"""
21from flask import request, render_template, redirect, after_this_request, Blueprint
22from flask_admin import Admin
23from flask_admin.menu import MenuLink
24from flask_login import login_required
25from flask_security import roles_required
26from flask_security.forms import build_form_from_request
27from flask_security.registerable import register_user, register_existing
28from flask_security.utils import view_commit, get_post_register_redirect, config_value as cv
29from flask_sqlalchemy import SQLAlchemy
31from app.limiter import limiter
32from app.security.models import SecureModelView, User, Role
34registration_bp = Blueprint('registration', __name__)
37class UserModelView(SecureModelView):
38 """
39 Provides a customized view for managing user models within a secure
40 administrative interface. This view restricts the creation of new users
41 and customizes the templates for listing and editing users.
42 """
43 can_create = False
44 column_list = ['email', 'confirmed_at', 'active', 'roles']
45 form_columns = ['email', 'confirmed_at', 'active', 'roles']
47 def create_view(self):
48 # Redirect to the /register page
49 return redirect("/register")
50 list_template = 'security/list_user.html'
51 edit_template = 'security/edit_user.html'
54class RoleModelView(SecureModelView):
55 """
56 Represents the RoleModelView class, which extends SecureModelView for managing
57 roles in an administrative interface.
59 The class provides functionality to display and manage data related to roles
60 such as their names, descriptions, and associated users. It specifies columns
61 to be displayed in lists and forms for user interaction.
62 """
63 column_list = ['name', 'description']
64 form_columns = ['name', 'description', 'users']
65 list_template = 'admin_model_list.html'
68def register_admin_views(db: SQLAlchemy, admin: Admin):
69 """
70 Registers views and menu links to the Flask-Admin interface.
72 This function integrates specified model views and additional menu links
73 into your Flask-Admin interface, allowing administrators to manage
74 database entities and access custom links conveniently.
76 :param db: The SQLAlchemy database instance used for database operations.
77 :type db: SQLAlchemy
78 :param admin: The Flask-Admin instance to which the views and links will
79 be added.
80 :type admin: Admin
81 :return: None
82 """
83 admin.add_link(RoleBasedMenuLink(name='About', url='/about', roles=['admin']))
84 admin.add_link(MenuLink(name='Home', url='/'))
85 admin.add_view(UserModelView(User, db.session))
86 admin.add_view(RoleModelView(Role, db.session))
88 from app.models import Tag # pylint: disable=import-outside-toplevel
89 from app.security.tag_views import UserTagModelView # pylint: disable=import-outside-toplevel
90 # Add Tag model view with restrictions for logged-in users
91 admin.add_view(UserTagModelView(Tag, db.session, name="My Tags"))
94# Custom MenuLink with role-based access
95class RoleBasedMenuLink(MenuLink):
96 """
97 Represents a role-based menu link.
99 This class extends a standard menu link providing role-based access control.
100 It allows defining menu links that are visible and accessible only to users
101 having specific roles. This can be useful in applications where some parts of
102 the navigation menu should be restricted to specific user groups.
104 :ivar roles: List of roles required to access the menu link. If empty, the link
105 is accessible to all authenticated users.
106 :type roles: list[str]
107 """
108 def __init__(self, name, url=None, endpoint=None, roles=None, **kwargs):
109 super().__init__(name, url, endpoint, **kwargs)
110 self.roles = roles or []
112 def is_accessible(self):
113 """Check if the link is accessible to the current user."""
114 # pylint: disable=import-outside-toplevel
115 from flask_login import current_user
117 if not current_user.is_authenticated:
118 return False
119 if not self.roles: # If no roles specified, allow access
120 return True
121 # Check if the user has any of the required roles
122 return any(role.name in self.roles for role in current_user.roles)
125@registration_bp.route('/register', methods=['GET', 'POST'])
126@login_required # Ensure only logged-in users can access
127@roles_required('admin') # Restrict to users with the 'admin' role
128@limiter.limit("1 per second")
129def custom_register():
130 """
131 Handles user registration functionality.
133 This function is associated with the '/register' endpoint and allows users with
134 the 'admin' role to register new users. It supports both GET and POST methods.
135 The function uses a form generated based on the request. On successful form
136 submission via POST, a new user is registered, and the system redirects to a
137 post-registration URL. In the case of a GET request or a failure to validate the
138 form, the registration template is rendered for further input.
140 :parameters:
141 No parameters are passed explicitly to this function, as it is tied to an
142 HTTP endpoint and handles the request context internally.
144 :return:
145 A redirect to a post-registration URL upon successful user registration, or
146 renders the registration template with the form for further input when a
147 GET request is made or validation fails.
149 """
150 form = build_form_from_request("register_form")
151 if request.method == 'POST' and form.validate_on_submit():
152 after_this_request(view_commit)
153 user = register_user(form)
154 form.user = user
156 return redirect(get_post_register_redirect())
158 # Here on GET or failed validate
159 if request.method == "POST" and cv("RETURN_GENERIC_RESPONSES"):
160 gr = register_existing(form)
161 if gr:
162 return redirect(get_post_register_redirect())
164 # Get
165 return render_template(cv("REGISTER_USER_TEMPLATE"), register_user_form=form)