Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
session.py415 linesDownload Raw Back to utils
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10"""11Implements the server-side session management.12 13Credit/Reference: http://flask.pocoo.org/snippets/109/14 15Modified to support both Python 2.6+ & Python 3.x16"""17 18import base6419import datetime20import hmac21import hashlib22import os23import secrets24import string25import time26import config27from uuid import uuid428from threading import Lock29from flask import current_app, request, flash, redirect30from flask_login import login_url31 32from pickle import dump, load33from collections import OrderedDict34 35from flask.sessions import SessionInterface, SessionMixin36from werkzeug.datastructures import CallbackDict37from werkzeug.security import safe_join38from werkzeug.exceptions import InternalServerError39 40from pgadmin.utils.ajax import make_json_response41 42 43def _calc_hmac(body, secret):44    return base64.b64encode(45        hmac.new(46            secret.encode(), body.encode(), hashlib.sha25647        ).digest()48    ).decode()49 50 51sess_lock = Lock()52LAST_CHECK_SESSION_FILES = None53 54 55class ManagedSession(CallbackDict, SessionMixin):56    def __init__(self, initial=None, sid=None, new=False, randval=None,57                 hmac_digest=None):58        def on_update(self):59            self.modified = True60 61        CallbackDict.__init__(self, initial, on_update)62        self.sid = sid63        self.new = new64        self.modified = False65        self.randval = randval66        self.last_write = None67        self.force_write = False68        self.hmac_digest = hmac_digest69        self.permanent = True70 71    def sign(self, secret):72        if not self.hmac_digest:73            population = string.ascii_lowercase + string.digits74 75            self.randval = ''.join(76                secrets.choice(population) for i in range(20))77            self.hmac_digest = _calc_hmac(78                '%s:%s' % (self.sid, self.randval), secret)79 80 81class SessionManager():82    def new_session(self):83        'Create a new session'84        raise NotImplementedError85 86    def exists(self, sid):87        'Does the given session-id exist?'88        raise NotImplementedError89 90    def remove(self, sid):91        'Remove the session'92        raise NotImplementedError93 94    def get(self, sid, digest):95        'Retrieve a managed session by session-id, checking the HMAC digest'96        raise NotImplementedError97 98    def put(self, session):99        'Store a managed session'100        raise NotImplementedError101 102 103class CachingSessionManager(SessionManager):104    def __init__(self, parent, num_to_store, skip_paths=None):105        self.parent = parent106        self.num_to_store = num_to_store107        self._cache = OrderedDict()108        self.skip_paths = [] if skip_paths is None else skip_paths109 110    def _normalize(self):111        if len(self._cache) > self.num_to_store:112            # Flush 20% of the cache113            with sess_lock:114                while len(self._cache) > (self.num_to_store * 0.8):115                    self._cache.popitem(False)116 117    def new_session(self):118        session = self.parent.new_session()119 120        # Do not store the session if skip paths121        for sp in self.skip_paths:122            if request.path.startswith(sp):123                return session124 125        with sess_lock:126            self._cache[session.sid] = session127        self._normalize()128 129        return session130 131    def remove(self, sid):132        with sess_lock:133            self.parent.remove(sid)134            if sid in self._cache:135                del self._cache[sid]136 137    def exists(self, sid):138        with sess_lock:139            if sid in self._cache:140                return True141            return self.parent.exists(sid)142 143    def get(self, sid, digest):144        session = None145        with sess_lock:146            if sid in self._cache:147                session = self._cache[sid]148                if session and session.hmac_digest != digest:149                    session = None150 151                # reset order in Dict152                del self._cache[sid]153 154            if not session:155                session = self.parent.get(sid, digest)156 157            # Do not store the session if skip paths158            for sp in self.skip_paths:159                if request.path.startswith(sp):160                    return session161 162            self._cache[sid] = session163        self._normalize()164 165        return session166 167    def put(self, session):168        with sess_lock:169            self.parent.put(session)170 171            # Do not store the session if skip paths172            for sp in self.skip_paths:173                if request.path.startswith(sp):174                    return175 176            if session.sid in self._cache:177                try:178                    del self._cache[session.sid]179                except Exception:180                    pass181 182            self._cache[session.sid] = session183        self._normalize()184 185 186class FileBackedSessionManager(SessionManager):187 188    def __init__(self, path, secret, disk_write_delay, skip_paths=None):189        self.path = path190        self.secret = secret191        self.disk_write_delay = disk_write_delay192        if not os.path.exists(self.path):193            os.makedirs(self.path)194        self.skip_paths = [] if skip_paths is None else skip_paths195 196    def exists(self, sid):197        fname = safe_join(self.path, sid)198        return fname is not None and os.path.exists(fname)199 200    def remove(self, sid):201        fname = safe_join(self.path, sid)202        if fname is not None and os.path.exists(fname):203            os.unlink(fname)204 205    def new_session(self):206        sid = str(uuid4())207        fname = safe_join(self.path, sid)208 209        while fname is not None and os.path.exists(fname):210            sid = str(uuid4())211            fname = safe_join(self.path, sid)212 213        # Do not store the session if skip paths214        for sp in self.skip_paths:215            if request.path.startswith(sp):216                return ManagedSession(sid=sid)217 218        if fname is None:219            raise InternalServerError('Failed to create new session')220 221        # touch the file222        with open(fname, 'wb'):223            return ManagedSession(sid=sid)224 225        return ManagedSession(sid=sid)226 227    def get(self, sid, digest):228        'Retrieve a managed session by session-id, checking the HMAC digest'229 230        fname = safe_join(self.path, sid)231        data = None232        hmac_digest = None233        randval = None234 235        if fname is not None and os.path.exists(fname):236            try:237                with open(fname, 'rb') as f:238                    randval, hmac_digest, data = load(f)239            except Exception:240                pass241 242        if not data:243            return self.new_session()244 245        # This assumes the file is correct, if you really want to246        # make sure the session is good from the server side, you247        # can re-calculate the hmac248 249        if hmac_digest != digest:250            return self.new_session()251 252        return ManagedSession(253            data, sid=sid, randval=randval, hmac_digest=hmac_digest254        )255 256    def put(self, session):257        """Store a managed session"""258        current_time = time.time()259        if not session.hmac_digest:260            session.sign(self.secret)261        elif not session.force_write and session.last_write is not None and \262            (current_time - float(session.last_write)) < \263                self.disk_write_delay:264            return265 266        session.last_write = current_time267        session.force_write = False268 269        # Do not store the session if skip paths270        for sp in self.skip_paths:271            if request.path.startswith(sp):272                return273 274        fname = safe_join(self.path, session.sid)275 276        if fname is None:277            raise InternalServerError('Failed to update the session')278 279        with open(fname, 'wb') as f:280            dump(281                (session.randval, session.hmac_digest, dict(session)),282                f283            )284 285 286class ManagedSessionInterface(SessionInterface):287    def __init__(self, manager):288        self.manager = manager289 290    def open_session(self, app, request):291        cookie_val = request.cookies.get(app.config['SESSION_COOKIE_NAME'])292 293        if not cookie_val or '!' not in cookie_val:294            return self.manager.new_session()295 296        sid, digest = cookie_val.split('!', 1)297 298        if self.manager.exists(sid):299            return self.manager.get(sid, digest)300 301        return self.manager.new_session()302 303    def save_session(self, app, session, response):304        domain = self.get_cookie_domain(app)305        if not session:306            self.manager.remove(session.sid)307            if session.modified:308                response.delete_cookie(app.config['SESSION_COOKIE_NAME'],309                                       domain=domain)310            return311 312        if not session.modified:313            # No need to save an unaltered session314            # TODO: put logic here to test if the cookie is older than N days,315            # if so, update the expiration date316            return317 318        self.manager.put(session)319        session.modified = False320 321        cookie_exp = self.get_expiration_time(app, session)322        response.set_cookie(323            app.config['SESSION_COOKIE_NAME'],324            '%s!%s' % (session.sid, session.hmac_digest),325            expires=cookie_exp,326            path=config.SESSION_COOKIE_PATH,327            secure=config.SESSION_COOKIE_SECURE,328            httponly=config.SESSION_COOKIE_HTTPONLY,329            samesite=config.SESSION_COOKIE_SAMESITE,330            domain=domain331        )332 333 334def create_session_interface(app, skip_paths=[]):335    return ManagedSessionInterface(336        CachingSessionManager(337            FileBackedSessionManager(338                app.config['SESSION_DB_PATH'],339                app.config['SECRET_KEY'],340                app.config.get('PGADMIN_SESSION_DISK_WRITE_DELAY', 10),341                skip_paths342            ),343            1000,344            skip_paths345        ))346 347 348def pga_unauthorised():349 350    lm = current_app.login_manager351    login_message = None352 353    if lm.login_message:354        if lm.localize_callback is not None:355            login_message = lm.localize_callback(lm.login_message)356        else:357            login_message = lm.login_message358 359    if not lm.login_view:360        # Only 401 is not enough to distinguish pgAdmin login is required.361        # There are other cases when we return 401. For eg. wrong password362        # supplied while connecting to server.363        # So send additional 'info' message.364        return make_json_response(365            status=401,366            success=0,367            errormsg=login_message,368            info='PGADMIN_LOGIN_REQUIRED'369        )370 371    # flash messages are only required if the request was from a372    # security page, otherwise it will be redirected to login page373    # anyway374    if login_message and 'security' in request.endpoint:375        flash(login_message, category=lm.login_message_category)376 377    return redirect(login_url(lm.login_view, request.url))378 379 380def cleanup_session_files():381    """382    This function will iterate through session directory and check the last383    modified time, if it older than (session expiration time + 1) days then384    delete that file.385    """386    iterate_session_files = False387 388    global LAST_CHECK_SESSION_FILES389    if LAST_CHECK_SESSION_FILES is None or \390        datetime.datetime.now() >= LAST_CHECK_SESSION_FILES + \391            datetime.timedelta(hours=config.CHECK_SESSION_FILES_INTERVAL):392        iterate_session_files = True393        LAST_CHECK_SESSION_FILES = datetime.datetime.now()394 395    if iterate_session_files:396        for root, dirs, files in os.walk(397                current_app.config['SESSION_DB_PATH']):398            for file_name in files:399                absolute_file_name = os.path.join(root, file_name)400                st = os.stat(absolute_file_name)401 402                # Get the last modified time of the session file403                last_modified_time = \404                    datetime.datetime.fromtimestamp(st.st_mtime)405 406                # Calculate session file expiry time.407                file_expiration_time = \408                    last_modified_time + \409                    current_app.permanent_session_lifetime + \410                    datetime.timedelta(days=1)411 412                if file_expiration_time <= datetime.datetime.now() and \413                        os.path.exists(absolute_file_name):414                    os.unlink(absolute_file_name)415 
codekingpro/portable-devtools · Team Ai