Files
aaPanel/class/flask_session/sessions.py
T
jose 3ac20e08ce v6.8.12
1. Add disk IO information to the homepage
2. Add a clear list button in the upload window
3. Optimize the background task overhead of the panel
4. Optimize the disk information caching mechanism
5. Panel Pro edition is online
6. Optimize panel resource usage
7. Optimize SSL certificate renewal
8. Fix the problem of reporting an error when the security entrance is empty
9. Fix the problem of infinite recursion when copying directories in extreme cases
2021-06-11 11:50:57 +08:00

564 lines
20 KiB
Python

# -*- coding: utf-8 -*-
"""
flaskext.session.sessions
~~~~~~~~~~~~~~~~~~~~~~~~~
Server-side Sessions and SessionInterfaces.
:copyright: (c) 2014 by Shipeng Feng.
:license: BSD, see LICENSE for more details.
"""
import sys
import time
from datetime import datetime
from uuid import uuid4
try:
import cPickle as pickle
except ImportError:
import pickle
from flask.sessions import SessionInterface as FlaskSessionInterface
from flask.sessions import SessionMixin
from werkzeug.datastructures import CallbackDict
from itsdangerous import Signer, BadSignature, want_bytes
PY2 = sys.version_info[0] == 2
if not PY2:
text_type = str
else:
text_type = unicode
def total_seconds(td):
return td.days * 60 * 60 * 24 + td.seconds
class ServerSideSession(CallbackDict, SessionMixin):
"""Baseclass for server-side based sessions."""
def __init__(self, initial=None, sid=None, permanent=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.sid = sid
if permanent:
self.permanent = permanent
self.modified = False
class RedisSession(ServerSideSession):
pass
class MemcachedSession(ServerSideSession):
pass
class FileSystemSession(ServerSideSession):
pass
class MongoDBSession(ServerSideSession):
pass
class SqlAlchemySession(ServerSideSession):
pass
class SessionInterface(FlaskSessionInterface):
def _generate_sid(self):
return str(uuid4())
def _get_signer(self, app):
if not app.secret_key:
return None
return Signer(app.secret_key, salt='flask-sessions',
key_derivation='hmac')
class NullSessionInterface(SessionInterface):
"""Used to open a :class:`flask.sessions.NullSession` instance.
"""
def open_session(self, app, request):
return None
class RedisSessionInterface(SessionInterface):
"""Uses the Redis key-value store as a session backend.
.. versionadded:: 0.2
The `use_signer` parameter was added.
:param redis: A ``redis.Redis`` instance.
:param key_prefix: A prefix that is added to all Redis store keys.
:param use_signer: Whether to sign the session id cookie or not.
:param permanent: Whether to use permanent session or not.
"""
serializer = pickle
session_class = RedisSession
def __init__(self, redis, key_prefix, use_signer=False, permanent=True):
if redis is None:
from redis import Redis
redis = Redis()
self.redis = redis
self.key_prefix = key_prefix
self.use_signer = use_signer
self.permanent = permanent
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if self.use_signer:
signer = self._get_signer(app)
if signer is None:
return None
try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if not PY2 and not isinstance(sid, text_type):
sid = sid.decode('utf-8', 'strict')
val = self.redis.get(self.key_prefix + sid)
if val is not None:
try:
data = self.serializer.loads(val)
return self.session_class(data, sid=sid)
except:
return self.session_class(sid=sid, permanent=self.permanent)
return self.session_class(sid=sid, permanent=self.permanent)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
if not session:
if session.modified:
self.redis.delete(self.key_prefix + session.sid)
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
# Modification case. There are upsides and downsides to
# emitting a set-cookie header each request. The behavior
# is controlled by the :meth:`should_set_cookie` method
# which performs a quick check to figure out if the cookie
# should be set or not. This is controlled by the
# SESSION_REFRESH_EACH_REQUEST config flag as well as
# the permanent flag on the session itself.
# if not self.should_set_cookie(app, session):
# return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
val = self.serializer.dumps(dict(session))
self.redis.setex(name=self.key_prefix + session.sid, value=val,
time=total_seconds(app.permanent_session_lifetime))
if self.use_signer:
session_id = self._get_signer(app).sign(want_bytes(session.sid))
else:
session_id = session.sid
response.set_cookie(app.session_cookie_name, session_id,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
class MemcachedSessionInterface(SessionInterface):
"""A Session interface that uses memcached as backend.
.. versionadded:: 0.2
The `use_signer` parameter was added.
:param client: A ``memcache.Client`` instance.
:param key_prefix: A prefix that is added to all Memcached store keys.
:param use_signer: Whether to sign the session id cookie or not.
:param permanent: Whether to use permanent session or not.
"""
serializer = pickle
session_class = MemcachedSession
def __init__(self, client, key_prefix, use_signer=False, permanent=True):
if client is None:
client = self._get_preferred_memcache_client()
if client is None:
raise RuntimeError('no memcache module found')
self.client = client
self.key_prefix = key_prefix
self.use_signer = use_signer
self.permanent = permanent
def _get_preferred_memcache_client(self):
servers = ['127.0.0.1:11211']
try:
import pylibmc
except ImportError:
pass
else:
return pylibmc.Client(servers)
try:
import memcache
except ImportError:
pass
else:
return memcache.Client(servers)
def _get_memcache_timeout(self, timeout):
"""
Memcached deals with long (> 30 days) timeouts in a special
way. Call this function to obtain a safe value for your timeout.
"""
if timeout > 2592000: # 60*60*24*30, 30 days
# See http://code.google.com/p/memcached/wiki/FAQ
# "You can set expire times up to 30 days in the future. After that
# memcached interprets it as a date, and will expire the item after
# said date. This is a simple (but obscure) mechanic."
#
# This means that we have to switch to absolute timestamps.
timeout += int(time.time())
return timeout
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if self.use_signer:
signer = self._get_signer(app)
if signer is None:
return None
try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
full_session_key = self.key_prefix + sid
if PY2 and isinstance(full_session_key, unicode):
full_session_key = full_session_key.encode('utf-8')
val = self.client.get(full_session_key)
if val is not None:
try:
if not PY2:
val = want_bytes(val)
data = self.serializer.loads(val)
return self.session_class(data, sid=sid)
except:
return self.session_class(sid=sid, permanent=self.permanent)
return self.session_class(sid=sid, permanent=self.permanent)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
full_session_key = self.key_prefix + session.sid
if PY2 and isinstance(full_session_key, unicode):
full_session_key = full_session_key.encode('utf-8')
if not session:
if session.modified:
self.client.delete(full_session_key)
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
if not PY2:
val = self.serializer.dumps(dict(session), 0)
else:
val = self.serializer.dumps(dict(session))
self.client.set(full_session_key, val, self._get_memcache_timeout(
total_seconds(app.permanent_session_lifetime)))
if self.use_signer:
session_id = self._get_signer(app).sign(want_bytes(session.sid))
else:
session_id = session.sid
response.set_cookie(app.session_cookie_name, session_id,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
class FileSystemSessionInterface(SessionInterface):
"""Uses the :class:`werkzeug.contrib.cache.FileSystemCache` as a session
backend.
.. versionadded:: 0.2
The `use_signer` parameter was added.
:param cache_dir: the directory where session files are stored.
:param threshold: the maximum number of items the session stores before it
starts deleting some.
:param mode: the file mode wanted for the session files, default 0600
:param key_prefix: A prefix that is added to FileSystemCache store keys.
:param use_signer: Whether to sign the session id cookie or not.
:param permanent: Whether to use permanent session or not.
"""
session_class = FileSystemSession
def __init__(self, cache_dir, threshold, mode, key_prefix,
use_signer=False, permanent=True):
from werkzeug.contrib.cache import FileSystemCache
self.cache = FileSystemCache(cache_dir, threshold=threshold, mode=mode)
self.key_prefix = key_prefix
self.use_signer = use_signer
self.permanent = permanent
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if self.use_signer:
signer = self._get_signer(app)
if signer is None:
return None
try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
data = self.cache.get(self.key_prefix + sid)
if data is not None:
return self.session_class(data, sid=sid)
return self.session_class(sid=sid, permanent=self.permanent)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
if not session:
if session.modified:
self.cache.delete(self.key_prefix + session.sid)
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
data = dict(session)
self.cache.set(self.key_prefix + session.sid, data,
total_seconds(app.permanent_session_lifetime))
if self.use_signer:
session_id = self._get_signer(app).sign(want_bytes(session.sid))
else:
session_id = session.sid
response.set_cookie(app.session_cookie_name, session_id,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
class MongoDBSessionInterface(SessionInterface):
"""A Session interface that uses mongodb as backend.
.. versionadded:: 0.2
The `use_signer` parameter was added.
:param client: A ``pymongo.MongoClient`` instance.
:param db: The database you want to use.
:param collection: The collection you want to use.
:param key_prefix: A prefix that is added to all MongoDB store keys.
:param use_signer: Whether to sign the session id cookie or not.
:param permanent: Whether to use permanent session or not.
"""
serializer = pickle
session_class = MongoDBSession
def __init__(self, client, db, collection, key_prefix, use_signer=False,
permanent=True):
if client is None:
from pymongo import MongoClient
client = MongoClient()
self.client = client
self.store = client[db][collection]
self.key_prefix = key_prefix
self.use_signer = use_signer
self.permanent = permanent
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if self.use_signer:
signer = self._get_signer(app)
if signer is None:
return None
try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
store_id = self.key_prefix + sid
document = self.store.find_one({'id': store_id})
if document and document.get('expiration') <= datetime.utcnow():
# Delete expired session
self.store.remove({'id': store_id})
document = None
if document is not None:
try:
val = document['val']
data = self.serializer.loads(want_bytes(val))
return self.session_class(data, sid=sid)
except:
return self.session_class(sid=sid, permanent=self.permanent)
return self.session_class(sid=sid, permanent=self.permanent)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
store_id = self.key_prefix + session.sid
if not session:
if session.modified:
self.store.remove({'id': store_id})
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
val = self.serializer.dumps(dict(session))
self.store.update({'id': store_id},
{'id': store_id,
'val': val,
'expiration': expires}, True)
if self.use_signer:
session_id = self._get_signer(app).sign(want_bytes(session.sid))
else:
session_id = session.sid
response.set_cookie(app.session_cookie_name, session_id,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
class SqlAlchemySessionInterface(SessionInterface):
"""Uses the Flask-SQLAlchemy from a flask app as a session backend.
.. versionadded:: 0.2
:param app: A Flask app instance.
:param db: A Flask-SQLAlchemy instance.
:param table: The table name you want to use.
:param key_prefix: A prefix that is added to all store keys.
:param use_signer: Whether to sign the session id cookie or not.
:param permanent: Whether to use permanent session or not.
"""
serializer = pickle
session_class = SqlAlchemySession
def __init__(self, app, db, table, key_prefix, use_signer=False,
permanent=True):
if db is None:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
self.db = db
self.key_prefix = key_prefix
self.use_signer = use_signer
self.permanent = permanent
class Session(self.db.Model):
__tablename__ = table
id = self.db.Column(self.db.Integer, primary_key=True)
session_id = self.db.Column(self.db.String(256), unique=True)
data = self.db.Column(self.db.LargeBinary)
expiry = self.db.Column(self.db.DateTime)
def __init__(self, session_id, data, expiry):
self.session_id = session_id
self.data = data
self.expiry = expiry
def __repr__(self):
return '<Session data %s>' % self.data
self.db.create_all()
self.sql_session_model = Session
def open_session(self, app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if self.use_signer:
signer = self._get_signer(app)
if signer is None:
return None
try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
store_id = self.key_prefix + sid
saved_session = self.sql_session_model.query.filter_by(
session_id=store_id).first()
if saved_session and saved_session.expiry <= datetime.utcnow():
# Delete expired session
self.db.session.delete(saved_session)
self.db.session.commit()
saved_session = None
if saved_session:
try:
val = saved_session.data
data = self.serializer.loads(want_bytes(val))
return self.session_class(data, sid=sid)
except:
return self.session_class(sid=sid, permanent=self.permanent)
return self.session_class(sid=sid, permanent=self.permanent)
def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
store_id = self.key_prefix + session.sid
saved_session = self.sql_session_model.query.filter_by(
session_id=store_id).first()
if not session:
if session.modified:
if saved_session:
self.db.session.delete(saved_session)
self.db.session.commit()
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
val = self.serializer.dumps(dict(session))
if saved_session:
saved_session.data = val
saved_session.expiry = expires
self.db.session.commit()
else:
new_session = self.sql_session_model(store_id, val, expires)
self.db.session.add(new_session)
self.db.session.commit()
if self.use_signer:
session_id = self._get_signer(app).sign(want_bytes(session.sid))
else:
session_id = session.sid
response.set_cookie(app.session_cookie_name, session_id,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)