validators.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import re
  2. from typing import Optional
  3. from flask import current_app
  4. from flask_appbuilder.exceptions import PasswordComplexityValidationError
  5. from flask_appbuilder.models.base import BaseInterface
  6. from flask_babel import gettext
  7. from wtforms import Field, Form, ValidationError
  8. password_complexity_regex = re.compile(
  9. r"""(
  10. ^(?=.*[A-Z].*[A-Z]) # at least two capital letters
  11. (?=.*[^0-9a-zA-Z]) # at least one of these special characters
  12. (?=.*[0-9].*[0-9]) # at least two numeric digits
  13. (?=.*[a-z].*[a-z].*[a-z]) # at least three lower case letters
  14. .{10,} # at least 10 total characters
  15. $
  16. )""",
  17. re.VERBOSE,
  18. )
  19. class Unique:
  20. """
  21. WTForm field validator. Checks if field value is unique against
  22. a specified table field.
  23. """
  24. field_flags = {"unique": True}
  25. def __init__(
  26. self, datamodel: BaseInterface, col_name: str, message: Optional[str] = None
  27. ) -> None:
  28. """
  29. :param datamodel: The datamodel class, abstract layer for backend
  30. :param col_name: The unique column name.
  31. :param message: The error message.
  32. """
  33. self.datamodel = datamodel
  34. self.col_name = col_name
  35. self.message = message
  36. def __call__(self, form: Form, field: Field) -> None:
  37. filters = self.datamodel.get_filters().add_filter(
  38. self.col_name, self.datamodel.FilterEqual, field.data
  39. )
  40. count, obj = self.datamodel.query(filters)
  41. if count > 0:
  42. # only test if Unique, if pk value is different on update.
  43. if not hasattr(form, "_id") or form._id != self.datamodel.get_keys(obj)[0]:
  44. if self.message is None:
  45. self.message = field.gettext("Already exists.")
  46. raise ValidationError(self.message)
  47. class PasswordComplexityValidator:
  48. """
  49. WTForm field validator. Calls a custom password validator, useful for imposing
  50. password complexity for database Auth users.
  51. """
  52. def __call__(self, form: Form, field: Field) -> None:
  53. if current_app.config.get("FAB_PASSWORD_COMPLEXITY_ENABLED", False):
  54. password_complexity_validator = current_app.config.get(
  55. "FAB_PASSWORD_COMPLEXITY_VALIDATOR", None
  56. )
  57. if password_complexity_validator is not None:
  58. try:
  59. password_complexity_validator(field.data)
  60. except PasswordComplexityValidationError as exc:
  61. raise ValidationError(str(exc))
  62. else:
  63. try:
  64. default_password_complexity(field.data)
  65. except PasswordComplexityValidationError as exc:
  66. raise ValidationError(str(exc))
  67. def default_password_complexity(password: str) -> None:
  68. """
  69. FAB's default password complexity validator, set FAB_PASSWORD_COMPLEXITY_ENABLED
  70. to True to enable it
  71. """
  72. match = re.search(password_complexity_regex, password)
  73. if not match:
  74. raise PasswordComplexityValidationError(
  75. gettext(
  76. "Must have at least two capital letters,"
  77. " one special character, two digits, three lower case letters and"
  78. " a minimal length of 10."
  79. )
  80. )