ext_session.py 5.7 KB

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