registration.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.auth.services
  4. ~~~~~~~~~~~~~~~~~~~~~
  5. Implementation of services found in flaskbb.core.auth.services
  6. :copyright: (c) 2014-2018 the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details
  8. """
  9. from datetime import datetime
  10. from itertools import chain
  11. import attr
  12. from flask import flash
  13. from flask_babelplus import gettext as _
  14. from flask_login import login_user
  15. from pytz import UTC
  16. from sqlalchemy import func
  17. from ...core.auth.registration import (
  18. RegistrationPostProcessor,
  19. UserRegistrationService,
  20. UserValidator,
  21. )
  22. from ...core.exceptions import (
  23. PersistenceError,
  24. StopValidation,
  25. ValidationError,
  26. )
  27. from ...user.models import User
  28. __all__ = (
  29. "AutoActivateUserPostProcessor",
  30. "AutologinPostProcessor",
  31. "EmailUniquenessValidator",
  32. "RegistrationService",
  33. "SendActivationPostProcessor",
  34. "UsernameRequirements",
  35. "UsernameUniquenessValidator",
  36. "UsernameValidator",
  37. )
  38. @attr.s(hash=False, repr=True, frozen=True, cmp=False)
  39. class UsernameRequirements(object):
  40. """
  41. Configuration for username requirements, minimum and maximum length
  42. and disallowed names.
  43. """
  44. min = attr.ib()
  45. max = attr.ib()
  46. blacklist = attr.ib()
  47. class UsernameValidator(UserValidator):
  48. """
  49. Validates that the username for the registering user meets the minimum
  50. requirements (appropriate length, not a forbidden name).
  51. """
  52. def __init__(self, requirements):
  53. self._requirements = requirements
  54. def validate(self, user_info):
  55. if not (
  56. self._requirements.min
  57. <= len(user_info.username)
  58. <= self._requirements.max
  59. ):
  60. raise ValidationError(
  61. "username",
  62. _(
  63. "Username must be between %(min)s and %(max)s characters long", # noqa
  64. min=self._requirements.min,
  65. max=self._requirements.max,
  66. ),
  67. )
  68. is_blacklisted = user_info.username in self._requirements.blacklist
  69. if is_blacklisted: # pragma: no branch
  70. raise ValidationError(
  71. "username",
  72. _(
  73. "%(username)s is a forbidden username",
  74. username=user_info.username,
  75. ),
  76. )
  77. class UsernameUniquenessValidator(UserValidator):
  78. """
  79. Validates that the provided username is unique in the application.
  80. """
  81. def __init__(self, users):
  82. self.users = users
  83. def validate(self, user_info):
  84. count = self.users.query.filter(
  85. func.lower(self.users.username) == user_info.username
  86. ).count()
  87. if count != 0: # pragma: no branch
  88. raise ValidationError(
  89. "username",
  90. _(
  91. "%(username)s is already registered",
  92. username=user_info.username,
  93. ),
  94. )
  95. class EmailUniquenessValidator(UserValidator):
  96. """
  97. Validates that the provided email is unique in the application.
  98. """
  99. def __init__(self, users):
  100. self.users = users
  101. def validate(self, user_info):
  102. count = self.users.query.filter(
  103. func.lower(self.users.email) == user_info.email
  104. ).count()
  105. if count != 0: # pragma: no branch
  106. raise ValidationError(
  107. "email",
  108. _("%(email)s is already registered", email=user_info.email),
  109. )
  110. class SendActivationPostProcessor(RegistrationPostProcessor):
  111. """
  112. Sends an activation request after registration
  113. :param account_activator:
  114. :type account_activator: :class:`~flaskbb.core.auth.activation.AccountActivator`
  115. """ # noqa
  116. def __init__(self, account_activator):
  117. self.account_activator = account_activator
  118. def post_process(self, user):
  119. self.account_activator.initiate_account_activation(user.email)
  120. flash(
  121. _(
  122. "An account activation email has been sent to %(email)s",
  123. email=user.email,
  124. ),
  125. "success",
  126. )
  127. class AutologinPostProcessor(RegistrationPostProcessor):
  128. """
  129. Automatically logs a user in after registration
  130. """
  131. def post_process(self, user):
  132. login_user(user)
  133. flash(_("Thanks for registering."), "success")
  134. class AutoActivateUserPostProcessor(RegistrationPostProcessor):
  135. """
  136. Automatically marks the user as activated if activation isn't required
  137. for the forum.
  138. :param db: Configured Flask-SQLAlchemy extension object
  139. :param config: Current flaskbb configuration object
  140. """
  141. def __init__(self, db, config):
  142. self.db = db
  143. self.config = config
  144. def post_process(self, user):
  145. if not self.config['ACTIVATE_ACCOUNT']:
  146. user.activated = True
  147. self.db.session.commit()
  148. class RegistrationService(UserRegistrationService):
  149. """
  150. Default registration service for FlaskBB, runs the registration information
  151. against the provided validators and if it passes, creates the user.
  152. If any of the provided
  153. :class:`UserValidators<flaskbb.core.auth.registration.UserValidator>`
  154. raise a :class:`ValidationError<flaskbb.core.exceptions.ValidationError>`
  155. then the register method will raise a
  156. :class:`StopValidation<flaskbb.core.exceptions.StopValidation>` with all
  157. reasons why the registration was prevented.
  158. """
  159. def __init__(self, plugins, users, db):
  160. self.plugins = plugins
  161. self.users = users
  162. self.db = db
  163. def register(self, user_info):
  164. try:
  165. self._validate_registration(user_info)
  166. except StopValidation as e:
  167. self._handle_failure(user_info, e.reasons)
  168. raise
  169. user = self._store_user(user_info)
  170. self._post_process(user)
  171. return user
  172. def _validate_registration(self, user_info):
  173. failures = []
  174. validators = self.plugins.hook.flaskbb_gather_registration_validators()
  175. for v in chain.from_iterable(validators):
  176. try:
  177. v(user_info)
  178. except ValidationError as e:
  179. failures.append((e.attribute, e.reason))
  180. if failures:
  181. raise StopValidation(failures)
  182. def _handle_failure(self, user_info, failures):
  183. self.plugins.hook.flaskbb_registration_failure_handler(
  184. user_info=user_info, failures=failures
  185. )
  186. def _store_user(self, user_info):
  187. try:
  188. user = User(
  189. username=user_info.username,
  190. email=user_info.email,
  191. password=user_info.password,
  192. language=user_info.language,
  193. primary_group_id=user_info.group,
  194. date_joined=datetime.now(UTC),
  195. )
  196. self.db.session.add(user)
  197. self.db.session.commit()
  198. return user
  199. except Exception:
  200. self.db.session.rollback()
  201. raise PersistenceError("Could not persist user")
  202. def _post_process(self, user):
  203. self.plugins.hook.flaskbb_registration_post_processor(user=user)