A multipurpose python flask API server and administration SPA
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

253 lines
7.1 KiB

  1. """Service to handle user operations."""
  2. import logging
  3. import random
  4. import string
  5. from datetime import datetime
  6. from typing import Optional, Dict, Callable, Any, Tuple
  7. from flask_sqlalchemy import Pagination
  8. from iso8601 import iso8601
  9. from server import errors
  10. from server.db import db
  11. from server.model import User
  12. from server.service import role_service
  13. from server.service.authentication_service import validate_password_strength
  14. from server.service.transformation_service import (
  15. BaseTransformer,
  16. register_transformer
  17. )
  18. from server.service.validation_service import (
  19. BaseValidator,
  20. register_validator
  21. )
  22. from server.utility import authentication_utility
  23. LOGGER = logging.getLogger(__name__)
  24. @register_transformer
  25. class UserTransformer(BaseTransformer):
  26. """Serialize User model."""
  27. type = User
  28. def _deserializers(
  29. self) -> Dict[str, Callable[[User, Any], None]]:
  30. """Define the fields and the accompanying deserializer factory."""
  31. return {
  32. 'name': self.deserialize_name,
  33. 'creationTime': self.deserialize_creation_time,
  34. 'lastLoginTime': self.deserialize_last_login_time,
  35. 'version': self.deserialize_version,
  36. 'role': self.deserialize_role,
  37. }
  38. def _serializers(self) -> Dict[str, Callable[[], Any]]:
  39. """Define the fields and the accompanying serializer factory."""
  40. return {
  41. 'name': self.serialize_name,
  42. 'creationTime': self.serialize_creation_time,
  43. 'lastLoginTime': self.serialize_last_login_time,
  44. 'version': self.serialize_version,
  45. 'role': self.serialize_role,
  46. }
  47. def serialize_name(self) -> str:
  48. """User name."""
  49. return self.model.name
  50. @staticmethod
  51. def deserialize_name(model: User, name: str) -> None:
  52. """User name."""
  53. model.name = name
  54. def serialize_creation_time(self) -> datetime:
  55. """User creation time."""
  56. return self.model.creation_time
  57. @staticmethod
  58. def deserialize_creation_time(
  59. model: User, creation_time: datetime) -> None:
  60. """User creation time."""
  61. model.creation_time = iso8601.parse_date(creation_time)
  62. def serialize_last_login_time(self) -> datetime:
  63. """User last login time."""
  64. return self.model.last_login_time
  65. @staticmethod
  66. def deserialize_last_login_time(
  67. model: User, last_login_time: datetime) -> None:
  68. """User last login time."""
  69. model.last_login_time = iso8601.parse_date(last_login_time)
  70. def serialize_version(self) -> int:
  71. """User version."""
  72. return self.model.version
  73. @staticmethod
  74. def deserialize_version(model: User, version: int) -> None:
  75. """User version."""
  76. model.version = version
  77. def serialize_role(self) -> str:
  78. """User role."""
  79. return self.model.role.value
  80. @staticmethod
  81. def deserialize_role(model: User, role_value: str) -> None:
  82. """User role."""
  83. model.role = role_service.Role(role_value)
  84. @register_validator
  85. class UserValidator(BaseValidator):
  86. """Validate User model."""
  87. type = User
  88. def _validators(
  89. self) -> Dict[str, Callable[[Any], Tuple[bool, str]]]:
  90. return {
  91. 'id': self.no_validation,
  92. 'name': self.validate_name,
  93. 'role': self.validate_role,
  94. 'password_hash': self.no_validation,
  95. 'password_revision': self.no_validation,
  96. 'creation_time': self.no_validation,
  97. 'last_login_time': self.no_validation,
  98. 'version': self.validate_version
  99. }
  100. def validate_name(self, new_name: Any) -> Tuple[bool, str]:
  101. """
  102. Name changes are only allowed to be performed by an Admin.
  103. :param new_name:
  104. :return:
  105. """
  106. validation_result = (self.request_user.role == role_service.Role.ADMIN
  107. or new_name is None)
  108. if validation_result:
  109. return validation_result, ''
  110. return (validation_result,
  111. 'Names can only be changed by an administrator')
  112. def validate_role(self, new_role: Any) -> Tuple[bool, str]:
  113. """
  114. Roles can only be increased to the level of the request_user.
  115. :param new_role:
  116. :return:
  117. """
  118. acceptable_roles = role_service.ROLES.find_children_roles(
  119. self.request_user.role)
  120. role = new_role if new_role is not None else self.model.role
  121. if role in acceptable_roles:
  122. return True, ''
  123. return False, 'Role escalation is not permitted'
  124. def get_users(
  125. page: int, per_page: int = 20, max_per_page: int = 100) -> Pagination:
  126. """
  127. Page through users in the system.
  128. :param page: The page to request
  129. :param per_page: The number per page
  130. :param max_per_page:
  131. :return:
  132. """
  133. return User.query.paginate(page, per_page, True, max_per_page)
  134. def find_by_name(name: str) -> Optional[User]:
  135. """
  136. Find a user by name.
  137. :param name:
  138. :return:
  139. """
  140. return User.query.filter_by(name=name).first()
  141. def register(
  142. name: str,
  143. password: Optional[str],
  144. role: Optional[str],
  145. validate_password: bool = True) -> User:
  146. """
  147. Register a new user.
  148. :param name: Desired user name. Must be unique and not already registered
  149. :param password: Password to be hashed and stored for the user
  150. :param role: Role to assign the user [ROLE_USER, ROLE_ADMIN]
  151. :param validate_password: Perform password validation
  152. :return:
  153. """
  154. if validate_password and password is not None:
  155. validate_password_strength(password)
  156. password = password if password is not None else ''.join(
  157. random.choices(string.ascii_letters + string.digits, k=32))
  158. role = role if role is not None else User.ROLE_USER
  159. if find_by_name(name=name) is not None:
  160. raise errors.ValidationError('User name is already taken.')
  161. pw_hash, pw_revision = authentication_utility.get_password_hash(password)
  162. new_user = User(
  163. name=name,
  164. role=role,
  165. password_hash=pw_hash,
  166. password_revision=pw_revision,
  167. creation_time=datetime.now(),
  168. version=0)
  169. db.session.add(new_user)
  170. db.session.commit()
  171. LOGGER.info('Registered new user: %s with role: %s', name, role)
  172. return new_user
  173. def delete(user: User) -> bool:
  174. """
  175. Delete a user.
  176. :param user:
  177. :return:
  178. """
  179. existing_user = db.session.delete(user)
  180. if existing_user is None:
  181. db.session.commit()
  182. return True
  183. return False
  184. def update_last_login_time(user: User) -> None:
  185. """
  186. Bump the last login time for the user.
  187. :param user:
  188. :return:
  189. """
  190. if user is not None:
  191. user.last_login_time = datetime.now()
  192. db.session.commit()
  193. def update_password(user: User, password: str) -> None:
  194. """
  195. Change the user password.
  196. :param user:
  197. :param password:
  198. :return:
  199. """
  200. pw_hash, pw_revision = authentication_utility.get_password_hash(
  201. password)
  202. user.password_hash = pw_hash
  203. user.password_revision = pw_revision
  204. db.session.commit()