mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-09-02 05:08:46 +02:00
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
564 lines
20 KiB
Python
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)
|