/src/auth.py

https://github.com/jwoschitz/SimpleAuth · Python · 134 lines · 110 code · 24 blank · 0 comment · 10 complexity · f482a7341a3322ff72ac3200121012a9 MD5 · raw file

  1. from config import Config
  2. from database import DbContext, EmailAlreadyInUseError, EmailTooLongError
  3. from exception import Error
  4. from mail import TemplateMailDispatcher, MailDispatcher, MailConfig
  5. import validator
  6. import crypto
  7. class LoginResult:
  8. NONE = 0
  9. SUCCESS = 1
  10. USER_OR_PASSWORD_WRONG = 2
  11. USER_IS_NOT_ACTIVATED = 3
  12. USER_IS_LOCKED_OUT = 4
  13. class PasswordToShortError(Error):
  14. def __init__(self, min_chars):
  15. super(self.__class__, self).__init__("The password must be at least {0} characters long.".format(min_chars))
  16. class EmailIsInvalidError(Error):
  17. def __init__(self, email):
  18. super(self.__class__, self).__init__("The email address '{0}' contains invalid characters.".format(email))
  19. class Settings(object):
  20. def __init__(self, config = None):
  21. self._set_attr_from_config("password_min_length", config)
  22. self._set_attr_from_config("login_attempt_expire", config)
  23. self._set_attr_from_config("login_max_attempts", config)
  24. self._set_attr_from_config("mail_activation_expire", config)
  25. self._set_attr_from_config("mail_from", config)
  26. self._set_attr_from_config("mail_subject", config)
  27. self._set_attr_from_config("mail_body", config)
  28. self._set_attr_from_config("mail_body_html", config)
  29. def _set_attr_from_config(self, attr_name, config):
  30. value = getattr(config,attr_name) if config else None
  31. setattr(self, attr_name, value)
  32. class AuthHandler(object):
  33. def __init__(self, config_or_file_path):
  34. config = self._get_config(config_or_file_path)
  35. self._configure(config)
  36. def _get_config(self, config_or_file_path):
  37. if isinstance(config_or_file_path, Config):
  38. return config_or_file_path
  39. else:
  40. return Config(config_or_file_path)
  41. def _configure(self, config):
  42. self._db_context = DbContext(config)
  43. self.settings = Settings(config)
  44. self.mail_dispatcher = TemplateMailDispatcher(MailConfig(config), self.settings.mail_body, self.settings.mail_body_html)
  45. def create_user(self, email, password):
  46. user = self.get_user(email)
  47. user.create(password)
  48. return user
  49. def activate_user(self, encoded_email, activation_token):
  50. email = crypto.b64_decode(encoded_email)
  51. user = self.get_user(email)
  52. activated = user.activate(activation_token)
  53. return activated
  54. def resend_activation_email(self, email):
  55. user = self.get_user(email)
  56. user.resend_activation_email()
  57. return user
  58. def get_user(self, email):
  59. return User(email, self._db_context, self.settings, self.mail_dispatcher)
  60. def login(self, email, password):
  61. user = self.get_user(email)
  62. user.login(password)
  63. return user
  64. class User(object):
  65. def __init__(self, email, db_context, settings, mail_dispatcher):
  66. self._db_context = db_context
  67. self.settings = settings
  68. self.mail_dispatcher = mail_dispatcher
  69. self.email = email
  70. self.is_logged_in = False
  71. self.login_result = LoginResult.NONE
  72. def get_is_activated(self):
  73. return self._db_context.user.is_activated(self.email)
  74. is_activated = property(get_is_activated)
  75. def create(self, password):
  76. if not validator.is_valid_email(self.email):
  77. raise EmailIsInvalidError(self.email)
  78. if len(password) < int(self.settings.password_min_length):
  79. raise PasswordToShortError(self.settings.password_min_length)
  80. activation_token = self._db_context.user.create(self.email, password)
  81. self._send_activation_token(activation_token)
  82. def resend_activation_email(self):
  83. activation_token = self._db_context.user.renew_activation_token(self.email)
  84. self._send_activation_token(activation_token)
  85. def _send_activation_token(self, activation_token):
  86. if activation_token:
  87. encoded_email = crypto.b64_encode(self.email)
  88. self.mail_dispatcher.send_mail(
  89. self.settings.mail_from,
  90. self.email,
  91. self.settings.mail_subject,
  92. {"{ACTIVATION_TOKEN}": activation_token, "{EMAIL_IDENTIFIER}": encoded_email})
  93. def activate(self, activation_token):
  94. activated = self._db_context.user.activate(self.email, activation_token, self.settings.mail_activation_expire)
  95. return activated
  96. def login(self, password):
  97. if self._db_context.login_attempt.is_locked_out(self.email, self.settings.login_attempt_expire, self.settings.login_max_attempts):
  98. self.login_result = LoginResult.USER_IS_LOCKED_OUT
  99. self._db_context.login_attempt.log_failed_attempt(self.email)
  100. return False
  101. if not self.is_activated:
  102. self.login_result = LoginResult.USER_IS_NOT_ACTIVATED
  103. return False
  104. self.is_logged_in = self._db_context.user.login(self.email, password)
  105. if self.is_logged_in:
  106. self.login_result = LoginResult.SUCCESS
  107. return True
  108. self.login_result = LoginResult.USER_OR_PASSWORD_WRONG
  109. self._db_context.login_attempt.log_failed_attempt(self.email)
  110. return False