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

1""" 

2This module handles user registration, role-based access, and admin functionality. 

3 

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. 

8 

9Classes: 

10- UserModelView: Custom admin view for user management. 

11- RoleModelView: Custom admin view for role management. 

12 

13Functions: 

14- register_admin_views: Registers admin views and appends custom menu links. 

15- custom_register: Handles user registration for admin users. 

16 

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 

30 

31from app.limiter import limiter 

32from app.security.models import SecureModelView, User, Role 

33 

34registration_bp = Blueprint('registration', __name__) 

35 

36 

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'] 

46 

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' 

52 

53 

54class RoleModelView(SecureModelView): 

55 """ 

56 Represents the RoleModelView class, which extends SecureModelView for managing 

57 roles in an administrative interface. 

58 

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' 

66 

67 

68def register_admin_views(db: SQLAlchemy, admin: Admin): 

69 """ 

70 Registers views and menu links to the Flask-Admin interface. 

71 

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. 

75 

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)) 

87 

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")) 

92 

93 

94# Custom MenuLink with role-based access 

95class RoleBasedMenuLink(MenuLink): 

96 """ 

97 Represents a role-based menu link. 

98 

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. 

103 

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 [] 

111 

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 

116 

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) 

123 

124 

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. 

132 

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. 

139 

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. 

143 

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. 

148 

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 

155 

156 return redirect(get_post_register_redirect()) 

157 

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()) 

163 

164 # Get 

165 return render_template(cv("REGISTER_USER_TEMPLATE"), register_user_form=form)