/src/database.py

https://github.com/jwoschitz/SimpleAuth · Python · 174 lines · 148 code · 26 blank · 0 comment · 15 complexity · 719577082318054e8af65431ca1a7f87 MD5 · raw file

  1. import MySQLdb
  2. import crypto
  3. from exception import Error
  4. class EmailAlreadyInUseError(Error):
  5. def __init__(self, email):
  6. super(self.__class__, self).__init__("Email '{0}' is already registered.".format(email))
  7. class EmailTooLongError(Error):
  8. def __init__(self, email, max_chars):
  9. super(self.__class__, self).__init__("Your email '{0}' is too long. Email address must be short than {1} characters.".format(email,max_chars))
  10. class DbConnectionConfig(object):
  11. def __init__(self, db_host, db_user, db_password, db_catalog):
  12. self.db_host = db_host
  13. self.db_user = db_user
  14. self.db_password = db_password
  15. self.db_catalog = db_catalog
  16. class DbConnection(object):
  17. is_connected = None
  18. db_connection = None
  19. def __init__(self, db_connection_config):
  20. self.db_host = db_connection_config.db_host
  21. self.db_user = db_connection_config.db_user
  22. self.db_password = db_connection_config.db_password
  23. self.db_catalog = db_connection_config.db_catalog
  24. def connect(self):
  25. if not self.is_connected():
  26. self.db_connection = MySQLdb.connect(
  27. self.db_host,
  28. self.db_user,
  29. self.db_password,
  30. self.db_catalog
  31. )
  32. def execute_query_row(self, sql, params = None, keep_connection_open = False):
  33. return self.__query(sql, params,'row')
  34. def execute_query_all(self, sql, params = None, keep_connection_open = False):
  35. return self.__query(sql, params,'all')
  36. def execute_scalar(self, sql, params = None, keep_connection_open = False):
  37. row = self.__query(sql, params,'row')
  38. if row:
  39. return row[0]
  40. return None
  41. def execute_non_query(self, sql, params = None, keep_connection_open = False):
  42. self.__query(sql, params)
  43. def __query(self, sql, params = None, returnValue = None, keep_connection_open = False):
  44. try:
  45. self.connect()
  46. cursor = self.db_connection.cursor()
  47. cursor.execute(sql, params)
  48. result = {
  49. 'row' : cursor.fetchone(),
  50. 'all' : cursor.fetchall()
  51. }
  52. return result.get(returnValue)
  53. finally:
  54. if not keep_connection_open:
  55. self.close()
  56. def close(self):
  57. if self.is_connected():
  58. self.db_connection.close()
  59. self.db_connection = None
  60. def is_connected(self):
  61. if self.db_connection:
  62. return True
  63. return False
  64. class DbContext(object):
  65. def __init__(self, config):
  66. connection_config = DbConnectionConfig(
  67. config.db_host,
  68. config.db_user,
  69. config.db_password,
  70. config.db_catalog
  71. )
  72. self.db_connection = DbConnection(connection_config)
  73. self.user = User(self.db_connection)
  74. self.login_attempt = LoginAttempt(self.db_connection)
  75. class LoginAttempt(object):
  76. def __init__(self, db_connection):
  77. self._db_connection = db_connection
  78. def log_failed_attempt(self, email):
  79. self._db_connection.execute_non_query(
  80. "INSERT INTO login_attempts (login) VALUES (%s)", email
  81. )
  82. def is_locked_out(self, email, login_attempt_expire, login_max_attempts):
  83. if self.get_count(email, login_attempt_expire) >= login_max_attempts:
  84. return True
  85. return False
  86. def get_count(self, email, login_attempt_expire):
  87. result = self._db_connection.execute_scalar(
  88. "SELECT COUNT(id) FROM login_attempts WHERE login = %s AND timestamp > DATE_SUB(NOW(), INTERVAL %s SECOND)",
  89. (email, login_attempt_expire)
  90. )
  91. return int(result)
  92. class User(object):
  93. def __init__(self, db_connection):
  94. self._db_connection = db_connection
  95. def is_activated(self, email, keep_connection_open = False):
  96. return self._db_connection.execute_scalar("SELECT is_activated FROM users WHERE email = %s LIMIT 1", email, keep_connection_open)
  97. def exists(self, email, keep_connection_open = False):
  98. value = self._db_connection.execute_scalar("SELECT id FROM users WHERE email = %s LIMIT 1", email, keep_connection_open)
  99. return value is not None
  100. def create(self, email, password):
  101. if self.exists(email):
  102. raise EmailAlreadyInUseError(email)
  103. if len(email) > 100:
  104. raise EmailTooLongError(email, 100)
  105. activation_token = crypto.create_activation_token()
  106. salt = crypto.create_salt()
  107. hashed_password = crypto.create_hash(password,salt)
  108. self._db_connection.execute_non_query(
  109. ("INSERT INTO users (email, password, salt, activation_token, activation_token_requested, created)"
  110. "VALUES (%s, %s, %s, %s, NOW(), NOW())"),
  111. (email, hashed_password, salt, activation_token)
  112. )
  113. return activation_token
  114. def renew_activation_token(self, email):
  115. if not self.exists(email):
  116. return
  117. if self.is_activated(email):
  118. return
  119. activation_token = crypto.create_activation_token()
  120. self._db_connection.execute_non_query(
  121. ("UPDATE users SET activation_token = %s, activation_token_requested = NOW()"
  122. "WHERE email = %s"), (activation_token, email)
  123. )
  124. return activation_token
  125. def activate(self, email, activation_token, email_activation_expire):
  126. token = self._db_connection.execute_scalar(
  127. "SELECT activation_token FROM users WHERE email = %s AND activation_token_requested > DATE_SUB(NOW(), INTERVAL %s SECOND)",
  128. (email, email_activation_expire)
  129. )
  130. if not token == activation_token:
  131. return False
  132. self._db_connection.execute_non_query(
  133. "UPDATE users SET is_activated = 1 WHERE email = %s", email
  134. )
  135. return True
  136. def login(self, email, password):
  137. try:
  138. salt = self._get_salt_from_db(email, True)
  139. hashed_password = crypto.create_hash(password, salt)
  140. result = self._db_connection.execute_scalar(
  141. "SELECT id FROM users WHERE email = %s AND password = %s LIMIT 1",
  142. (email, hashed_password)
  143. )
  144. finally:
  145. self._db_connection.close()
  146. return result is not None
  147. def _get_salt_from_db(self, email, keep_connection_open = False):
  148. return self._db_connection.execute_scalar("SELECT salt FROM users WHERE email = %s LIMIT 1", email, keep_connection_open)