A corporation manager and dashboard for EVE Online
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

195 lignes
7.1 KiB

  1. # -*- coding: utf-8 -*-
  2. from datetime import datetime
  3. import random
  4. import sqlite3
  5. from flask import g
  6. from werkzeug.local import LocalProxy
  7. __all__ = ["Database"]
  8. class Database:
  9. """Database manager for low-level authentication actions."""
  10. MAX_SESSION_STALENESS = 2 * 60 * 60 # 2 hours
  11. MAX_SESSION_AGE = 24 * 60 * 60 # 24 hours
  12. SESSION_GRACE = 60 * 60 # 1 hour
  13. path = None
  14. def __init__(self):
  15. if self.path is None:
  16. raise RuntimeError("Database.path not set")
  17. self._conn = sqlite3.connect(self.path)
  18. def __enter__(self):
  19. return self._conn.__enter__()
  20. def __exit__(self, exc_type, exc_value, trace):
  21. return self._conn.__exit__(exc_type, exc_value, trace)
  22. @classmethod
  23. def _get(cls):
  24. """Return the current database, or allocate a new one if necessary."""
  25. if not hasattr(g, "_db"):
  26. g._db = cls()
  27. return g._db
  28. @classmethod
  29. def pre_hook(cls):
  30. """Hook to be called before a request context.
  31. Sets up the g.db proxy.
  32. """
  33. g.db = LocalProxy(cls._get)
  34. @classmethod
  35. def post_hook(cls, exc):
  36. """Hook to be called when tearing down an application context.
  37. Closes the database if necessary.
  38. """
  39. if hasattr(g, "_db"):
  40. g._db.close()
  41. def close(self):
  42. """Close the database connection."""
  43. return self._conn.close()
  44. def _clear_old_sessions(self):
  45. """Remove old sessions from the database.
  46. Sessions can expire if they are not touched (accessed) in a certain
  47. period of time, or if their absolute age exceeds some number. We don't
  48. actually remove them until a bit after this time.
  49. """
  50. query = """DELETE FROM session WHERE
  51. strftime("%s", "now") - strftime("%s", session_created) >= {} OR
  52. strftime("%s", "now") - strftime("%s", session_touched) >= {}"""
  53. create_thresh = self.MAX_SESSION_AGE + self.SESSION_GRACE
  54. touch_thresh = self.MAX_SESSION_STALENESS + self.SESSION_GRACE
  55. with self._conn as conn:
  56. conn.execute(query.format(create_thresh, touch_thresh))
  57. def new_session(self):
  58. """Allocate a new session in the database and return its ID."""
  59. with self._conn as conn:
  60. cur = conn.execute("INSERT INTO session DEFAULT VALUES")
  61. return cur.lastrowid
  62. def has_session(self, sid):
  63. """Return whether the given session ID exists in the database.
  64. Will only be True for non-expired sessions. This function randomly does
  65. database maintenance; very old expired sessions may be cleared.
  66. """
  67. if random.random() <= 0.2:
  68. self._clear_old_sessions()
  69. query = """SELECT 1 FROM session
  70. WHERE session_id = ? AND
  71. strftime("%s", "now") - strftime("%s", session_created) < {} AND
  72. strftime("%s", "now") - strftime("%s", session_touched) < {}"""
  73. query = query.format(self.MAX_SESSION_AGE, self.MAX_SESSION_STALENESS)
  74. cur = self._conn.execute(query, (sid,))
  75. return bool(cur.fetchall())
  76. def read_session(self, sid):
  77. """Return the character associated with the given session, or None."""
  78. query = """SELECT session_character FROM session
  79. WHERE session_id = ? AND
  80. strftime("%s", "now") - strftime("%s", session_created) < {} AND
  81. strftime("%s", "now") - strftime("%s", session_touched) < {}"""
  82. query = query.format(self.MAX_SESSION_AGE, self.MAX_SESSION_STALENESS)
  83. res = self._conn.execute(query, (sid,)).fetchall()
  84. return res[0][0] if res else None
  85. def touch_session(self, sid):
  86. """Update the given session's last access timestamp."""
  87. query = """UPDATE session
  88. SET session_touched = CURRENT_TIMESTAMP
  89. WHERE session_id = ?"""
  90. with self._conn as conn:
  91. conn.execute(query, (sid,))
  92. def attach_session(self, sid, cid):
  93. """Attach the given session to a character. Does not touch it."""
  94. query = """UPDATE session
  95. SET session_character = ?
  96. WHERE session_id = ?"""
  97. with self._conn as conn:
  98. conn.execute(query, (cid, sid))
  99. def drop_session(self, sid):
  100. """Remove the given session from the database."""
  101. with self._conn as conn:
  102. conn.execute("DELETE FROM session WHERE session_id = ?", (sid,))
  103. def put_character(self, cid, name):
  104. """Put a character into the database if they don't already exist."""
  105. with self._conn as conn:
  106. cur = conn.execute("BEGIN TRANSACTION")
  107. cur.execute(
  108. """UPDATE character SET character_name = ?
  109. WHERE character_id = ?""", (name, cid))
  110. if cur.rowcount == 0:
  111. cur.execute(
  112. """INSERT INTO character (character_id, character_name)
  113. VALUES (?, ?)""", (cid, name))
  114. def read_character(self, cid):
  115. """Return a dictionary of properties for the given character."""
  116. query = """SELECT character_name, character_style
  117. FROM character WHERE character_id = ?"""
  118. res = self._conn.execute(query, (cid,)).fetchall()
  119. return {"name": res[0][0], "style": res[0][1]} if res else {}
  120. def set_character_style(self, cid, style):
  121. """Update a character's style setting."""
  122. with self._conn as conn:
  123. conn.execute("""UPDATE character SET character_style = ?
  124. WHERE character_id = ?""", (style, cid))
  125. def set_auth(self, cid, token, expires, refresh):
  126. """Set the authentication info for the given character."""
  127. with self._conn as conn:
  128. conn.execute("""INSERT OR REPLACE INTO auth
  129. (auth_character, auth_token, auth_token_expiry, auth_refresh)
  130. VALUES (?, ?, ?, ?)""", (cid, token, expires, refresh))
  131. def update_auth(self, cid, token, expires, refresh):
  132. """Update the authentication info for the given character.
  133. Functionally equivalent to set_auth provided that the character has an
  134. existing auth entry, but is more efficient.
  135. """
  136. with self._conn as conn:
  137. conn.execute("""UPDATE auth
  138. SET auth_token = ?, auth_token_expiry = ?, auth_refresh = ?
  139. WHERE auth_character = ?""", (token, expires, refresh, cid))
  140. def get_auth(self, cid):
  141. """Return authentication info for the given character.
  142. Return a 3-tuple of (access_token, token_expiry, refresh_token), or
  143. None if there is no auth info.
  144. """
  145. query = """SELECT auth_token, auth_token_expiry, auth_refresh
  146. FROM auth WHERE auth_character = ?"""
  147. res = self._conn.execute(query, (cid,)).fetchall()
  148. if not res:
  149. return None
  150. token, expiry, refresh = res[0]
  151. expires = datetime.strptime(expiry, "%Y-%m-%d %H:%M:%S.%f")
  152. return token, expires, refresh
  153. def drop_auth(self, cid):
  154. """Drop any authentication info for the given character."""
  155. with self._conn as conn:
  156. conn.execute("DELETE FROM auth WHERE auth_character = ?", (cid,))