ext_session.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import redis
  2. from redis.connection import SSLConnection, Connection
  3. from flask import request
  4. from flask_session import Session, SqlAlchemySessionInterface, RedisSessionInterface
  5. from flask_session.sessions import total_seconds
  6. from itsdangerous import want_bytes
  7. from extensions.ext_database import db
  8. sess = Session()
  9. def init_app(app):
  10. sqlalchemy_session_interface = CustomSqlAlchemySessionInterface(
  11. app,
  12. db,
  13. app.config.get('SESSION_SQLALCHEMY_TABLE', 'sessions'),
  14. app.config.get('SESSION_KEY_PREFIX', 'session:'),
  15. app.config.get('SESSION_USE_SIGNER', False),
  16. app.config.get('SESSION_PERMANENT', True)
  17. )
  18. session_type = app.config.get('SESSION_TYPE')
  19. if session_type == 'sqlalchemy':
  20. app.session_interface = sqlalchemy_session_interface
  21. elif session_type == 'redis':
  22. connection_class = Connection
  23. if app.config.get('SESSION_REDIS_USE_SSL', False):
  24. connection_class = SSLConnection
  25. sess_redis_client = redis.Redis()
  26. sess_redis_client.connection_pool = redis.ConnectionPool(**{
  27. 'host': app.config.get('SESSION_REDIS_HOST', 'localhost'),
  28. 'port': app.config.get('SESSION_REDIS_PORT', 6379),
  29. 'username': app.config.get('SESSION_REDIS_USERNAME', None),
  30. 'password': app.config.get('SESSION_REDIS_PASSWORD', None),
  31. 'db': app.config.get('SESSION_REDIS_DB', 2),
  32. 'encoding': 'utf-8',
  33. 'encoding_errors': 'strict',
  34. 'decode_responses': False
  35. }, connection_class=connection_class)
  36. app.extensions['session_redis'] = sess_redis_client
  37. app.session_interface = CustomRedisSessionInterface(
  38. sess_redis_client,
  39. app.config.get('SESSION_KEY_PREFIX', 'session:'),
  40. app.config.get('SESSION_USE_SIGNER', False),
  41. app.config.get('SESSION_PERMANENT', True)
  42. )
  43. class CustomSqlAlchemySessionInterface(SqlAlchemySessionInterface):
  44. def __init__(
  45. self,
  46. app,
  47. db,
  48. table,
  49. key_prefix,
  50. use_signer=False,
  51. permanent=True,
  52. sequence=None,
  53. autodelete=False,
  54. ):
  55. if db is None:
  56. from flask_sqlalchemy import SQLAlchemy
  57. db = SQLAlchemy(app)
  58. self.db = db
  59. self.key_prefix = key_prefix
  60. self.use_signer = use_signer
  61. self.permanent = permanent
  62. self.autodelete = autodelete
  63. self.sequence = sequence
  64. self.has_same_site_capability = hasattr(self, "get_cookie_samesite")
  65. class Session(self.db.Model):
  66. __tablename__ = table
  67. if sequence:
  68. id = self.db.Column( # noqa: A003, VNE003, A001
  69. self.db.Integer, self.db.Sequence(sequence), primary_key=True
  70. )
  71. else:
  72. id = self.db.Column( # noqa: A003, VNE003, A001
  73. self.db.Integer, primary_key=True
  74. )
  75. session_id = self.db.Column(self.db.String(255), unique=True)
  76. data = self.db.Column(self.db.LargeBinary)
  77. expiry = self.db.Column(self.db.DateTime)
  78. def __init__(self, session_id, data, expiry):
  79. self.session_id = session_id
  80. self.data = data
  81. self.expiry = expiry
  82. def __repr__(self):
  83. return f"<Session data {self.data}>"
  84. self.sql_session_model = Session
  85. def save_session(self, *args, **kwargs):
  86. if request.blueprint == 'service_api':
  87. return
  88. elif request.method == 'OPTIONS':
  89. return
  90. elif request.endpoint and request.endpoint == 'health':
  91. return
  92. return super().save_session(*args, **kwargs)
  93. class CustomRedisSessionInterface(RedisSessionInterface):
  94. def save_session(self, app, session, response):
  95. if request.blueprint == 'service_api':
  96. return
  97. elif request.method == 'OPTIONS':
  98. return
  99. elif request.endpoint and request.endpoint == 'health':
  100. return
  101. if not self.should_set_cookie(app, session):
  102. return
  103. domain = self.get_cookie_domain(app)
  104. path = self.get_cookie_path(app)
  105. if not session:
  106. if session.modified:
  107. self.redis.delete(self.key_prefix + session.sid)
  108. response.delete_cookie(
  109. app.config["SESSION_COOKIE_NAME"], domain=domain, path=path
  110. )
  111. return
  112. # Modification case. There are upsides and downsides to
  113. # emitting a set-cookie header each request. The behavior
  114. # is controlled by the :meth:`should_set_cookie` method
  115. # which performs a quick check to figure out if the cookie
  116. # should be set or not. This is controlled by the
  117. # SESSION_REFRESH_EACH_REQUEST config flag as well as
  118. # the permanent flag on the session itself.
  119. # if not self.should_set_cookie(app, session):
  120. # return
  121. conditional_cookie_kwargs = {}
  122. httponly = self.get_cookie_httponly(app)
  123. secure = self.get_cookie_secure(app)
  124. if self.has_same_site_capability:
  125. conditional_cookie_kwargs["samesite"] = self.get_cookie_samesite(app)
  126. expires = self.get_expiration_time(app, session)
  127. if session.permanent:
  128. value = self.serializer.dumps(dict(session))
  129. if value is not None:
  130. self.redis.setex(
  131. name=self.key_prefix + session.sid,
  132. value=value,
  133. time=total_seconds(app.permanent_session_lifetime),
  134. )
  135. if self.use_signer:
  136. session_id = self._get_signer(app).sign(want_bytes(session.sid)).decode("utf-8")
  137. else:
  138. session_id = session.sid
  139. response.set_cookie(
  140. app.config["SESSION_COOKIE_NAME"],
  141. session_id,
  142. expires=expires,
  143. httponly=httponly,
  144. domain=domain,
  145. path=path,
  146. secure=secure,
  147. **conditional_cookie_kwargs,
  148. )