Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
__init__.py938 linesDownload Raw Back to pgadmin
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"""The main pgAdmin module. This handles the application initialisation tasks,11such as setup of logging, dynamic loading of modules etc."""12import logging13import os14import sys15import re16import ipaddress17import traceback18import shutil19 20from types import MethodType21from collections import defaultdict22from importlib import import_module23 24from flask import Flask, abort, request, current_app, session, url_for25from flask_socketio import SocketIO26from werkzeug.exceptions import HTTPException27from flask_babel import Babel, gettext28from flask_babel import gettext as _29from flask_login import user_logged_in, user_logged_out30from flask_mail import Mail31from flask_paranoid import Paranoid32from flask_security import Security, SQLAlchemyUserDatastore, current_user33from flask_security.utils import login_user, logout_user34from flask_migrate import Migrate35from werkzeug.datastructures import ImmutableDict36from werkzeug.local import LocalProxy37from werkzeug.utils import find_modules38from jinja2 import select_autoescape39 40from pgadmin.model import db, Role, Server, SharedServer, ServerGroup, \41    User, Keys, Version, SCHEMA_VERSION as CURRENT_SCHEMA_VERSION42from pgadmin.utils import PgAdminModule, driver, KeyManager, heartbeat43from pgadmin.utils.preferences import Preferences44from pgadmin.utils.session import create_session_interface, pga_unauthorised45from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader46from datetime import timedelta, datetime47from pgadmin.setup import get_version, set_version, check_db_tables48from pgadmin.utils.ajax import internal_server_error, make_json_response49from pgadmin.utils.csrf import pgCSRFProtect50from pgadmin import authenticate51from pgadmin.utils.security_headers import SecurityHeaders52from pgadmin.utils.constants import KERBEROS, OAUTH2, INTERNAL, LDAP, WEBSERVER53from jsonformatter import JsonFormatter54 55# Explicitly set the mime-types so that a corrupted windows registry will not56# affect pgAdmin 4 to be load properly. This will avoid the issues that may57# occur due to security fix of X_CONTENT_TYPE_OPTIONS = "nosniff".58import mimetypes59 60mimetypes.add_type('application/javascript', '.js')61mimetypes.add_type('text/css', '.css')62 63 64winreg = None65if os.name == 'nt':66    import winreg67 68socketio = SocketIO(manage_session=False, async_mode='threading',69                    logger=False, engineio_logger=False, debug=False,70                    ping_interval=25, ping_timeout=120)71 72_INDEX_PATH = 'browser.index'73 74 75class PgAdmin(Flask):76    def __init__(self, *args, **kwargs):77        # Set the template loader to a postgres-version-aware loader78        self.jinja_options = ImmutableDict(79            autoescape=select_autoescape(enabled_extensions=('html', 'xml')),80            loader=VersionedTemplateLoader(self)81        )82        self.logout_hooks = []83        self.before_app_start = []84 85        super().__init__(*args, **kwargs)86 87    def find_submodules(self, basemodule):88        try:89            for module_name in find_modules(basemodule, True):90                if module_name in self.config['MODULE_BLACKLIST']:91                    self.logger.info(92                        'Skipping blacklisted module: %s' % module_name93                    )94                    continue95                self.logger.info(96                    'Examining potential module: %s' % module_name)97                module = import_module(module_name)98                for key in list(module.__dict__.keys()):99                    if isinstance(module.__dict__[key], PgAdminModule):100                        yield module.__dict__[key]101        except Exception:102            return []103 104    @property105    def submodules(self):106        for blueprint in self.blueprints.values():107            if isinstance(blueprint, PgAdminModule):108                yield blueprint109 110    @property111    def messages(self):112        messages = dict()113        for module in self.submodules:114            messages.update(getattr(module, "messages", dict()))115        return messages116 117    @property118    def exposed_endpoint_url_map(self):119        #############################################################120        # To handle WSGI paths121        # If user has setup application under WSGI alias122        # like 'localhost/pgadmin4' then we have to append '/pgadmin4'123        # into endpoints124        #############################################################125        wsgi_root_path = ''126        if url_for(_INDEX_PATH) != '/browser/':127            wsgi_root_path = url_for(_INDEX_PATH).replace(128                '/browser/', ''129            )130 131        def get_full_url_path(url):132            """133            Generate endpoint URL at per WSGI alias134            """135            return wsgi_root_path + url136 137        # Fetch all endpoints and their respective url138        for rule in current_app.url_map.iter_rules('static'):139            yield rule.endpoint, get_full_url_path(rule.rule)140 141        for module in self.submodules:142            for endpoint in module.exposed_endpoints:143                for rule in current_app.url_map.iter_rules(endpoint):144                    yield rule.endpoint, get_full_url_path(rule.rule)145 146        yield 'pgadmin.root', wsgi_root_path147 148    @property149    def menu_items(self):150        from operator import attrgetter151 152        menu_items = defaultdict(list)153        for module in self.submodules:154            for key, value in module.menu_items.items():155                menu_items[key].extend(value)156        menu_items = dict((key, sorted(value, key=attrgetter('priority')))157                          for key, value in menu_items.items())158        return menu_items159 160    def register_logout_hook(self, module):161        if hasattr(module, 'on_logout') and \162                isinstance(getattr(module, 'on_logout'), MethodType):163            self.logout_hooks.append(module)164 165    def register_before_app_start(self, callback):166        self.before_app_start.append(callback)167 168    def run_before_app_start(self):169        # call before app starts or is exported170        with self.app_context(), self.test_request_context():171            for callback in self.before_app_start:172                callback()173 174 175def _find_blueprint():176    if request.blueprint:177        return current_app.blueprints[request.blueprint]178 179 180current_blueprint = LocalProxy(_find_blueprint)181 182 183def create_app(app_name=None):184    # Configuration settings185    import config186    if not app_name:187        app_name = config.APP_NAME188 189    # Check if app is created for CLI operations or Web190    cli_mode = False191    if app_name.endswith('-cli'):192        cli_mode = True193 194    # Only enable password related functionality in server mode.195    if config.SERVER_MODE is True:196        # Some times we need to access these config params where application197        # context is not available (we can't use current_app.config in those198        # cases even with current_app.app_context())199        # So update these params in config itself.200        # And also these updated config values will picked up by application201        # since we are updating config before the application instance is202        # created.203 204        config.SECURITY_RECOVERABLE = True205        config.SECURITY_CHANGEABLE = True206        # Now we'll open change password page in dialog207        # we don't want it to redirect to main page after password208        # change operation so we will open the same password change page again.209        config.SECURITY_POST_CHANGE_VIEW = 'browser.change_password'210 211    """Create the Flask application, startup logging and dynamically load212    additional modules (blueprints) that are found in this directory."""213    app = PgAdmin(__name__, static_url_path='/static')214    # Removes unwanted whitespace from render_template function215    app.jinja_env.trim_blocks = True216    app.config.from_object(config)217    app.config.update(dict(PROPAGATE_EXCEPTIONS=True))218 219    config.SETTINGS_SCHEMA_VERSION = CURRENT_SCHEMA_VERSION220    ##########################################################################221    # Setup logging and log the application startup222    ##########################################################################223 224    # We won't care about errors in the logging system, we are more225    # interested in application errors.226    logging.raiseExceptions = False227 228    # Add SQL level logging, and set the base logging level229    logging.addLevelName(25, 'SQL')230    app.logger.setLevel(logging.DEBUG)231    app.logger.handlers = []232 233    # We also need to update the handler on the webserver in order to see234    # request. Setting the level prevents werkzeug from setting up it's own235    # stream handler thus ensuring all the logging goes through the pgAdmin236    # logger.237    logger = logging.getLogger('werkzeug')238    logger.setLevel(config.CONSOLE_LOG_LEVEL)239 240    # Set SQLITE_PATH to TEST_SQLITE_PATH while running test cases241    if (242        'PGADMIN_TESTING_MODE' in os.environ and243        os.environ['PGADMIN_TESTING_MODE'] == '1'244    ):245        config.SQLITE_PATH = config.TEST_SQLITE_PATH246        config.MASTER_PASSWORD_REQUIRED = False247        config.UPGRADE_CHECK_ENABLED = False248 249    if not cli_mode:250        # Ensure the various working directories exist251        from pgadmin.setup import create_app_data_directory252        create_app_data_directory(config)253 254        # File logging255        from pgadmin.utils.enhanced_log_rotation import \256            EnhancedRotatingFileHandler257        fh = EnhancedRotatingFileHandler(config.LOG_FILE,258                                         config.LOG_ROTATION_SIZE,259                                         config.LOG_ROTATION_AGE,260                                         config.LOG_ROTATION_MAX_LOG_FILES)261 262        fh.setLevel(config.FILE_LOG_LEVEL)263 264        if config.JSON_LOGGER:265            json_formatter = JsonFormatter(config.FILE_LOG_FORMAT_JSON)266            fh.setFormatter(json_formatter)267        else:268            fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT))269 270        app.logger.addHandler(fh)271        logger.addHandler(fh)272 273    # Console logging274    ch = logging.StreamHandler()275    ch.setLevel(config.CONSOLE_LOG_LEVEL)276 277    if config.JSON_LOGGER:278        json_formatter = JsonFormatter(config.CONSOLE_LOG_FORMAT_JSON)279        ch.setFormatter(json_formatter)280    else:281        ch.setFormatter(logging.Formatter(config.CONSOLE_LOG_FORMAT))282 283    app.logger.addHandler(ch)284    logger.addHandler(ch)285 286    # Log the startup287    app.logger.info('########################################################')288    app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION)289    app.logger.info('########################################################')290    app.logger.debug("Python syspath: %s", sys.path)291 292    ##########################################################################293    # Setup i18n294    ##########################################################################295 296    # Initialise i18n297    babel = Babel(app)298 299    def get_locale():300        """Get the language for the user."""301        language = 'en'302        if config.SERVER_MODE is False:303            # Get the user language preference from the miscellaneous module304            user_id = None305            if current_user and current_user.is_authenticated:306                user_id = current_user.id307            else:308                user = user_datastore.find_user(email=config.DESKTOP_USER)309                if user is not None:310                    user_id = user.id311            user_language = Preferences.raw_value(312                'misc', 'user_language', 'user_language', user_id313            )314            if user_language is not None:315                language = user_language316        else:317            # If language is available in get request then return the same318            # otherwise check the session or cookie319            data = request.form320            if 'language' in data:321                language = data['language'] or language322                setattr(session, 'PGADMIN_LANGUAGE', language)323            elif hasattr(session, 'PGADMIN_LANGUAGE'):324                language = getattr(session, 'PGADMIN_LANGUAGE', language)325            elif hasattr(request.cookies, 'PGADMIN_LANGUAGE'):326                language = getattr(327                    request.cookies, 'PGADMIN_LANGUAGE', language328                )329 330        return language331 332    babel.init_app(app, locale_selector=get_locale)333    ##########################################################################334    # Setup authentication335    ##########################################################################336    if config.CONFIG_DATABASE_URI is not None and \337            len(config.CONFIG_DATABASE_URI) > 0:338        app.config['SQLALCHEMY_DATABASE_URI'] = config.CONFIG_DATABASE_URI339    else:340        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///{0}?timeout={1}' \341            .format(config.SQLITE_PATH.replace('\\', '/'),342                    getattr(config, 'SQLITE_TIMEOUT', 500)343                    )344 345    # Override USER_DOES_NOT_EXIST and INVALID_PASSWORD messages from flask.346    app.config['SECURITY_MSG_USER_DOES_NOT_EXIST'] = \347        app.config['SECURITY_MSG_INVALID_PASSWORD'] = \348        (gettext("Incorrect username or password."), "error")349    app.config['SECURITY_PASSWORD_LENGTH_MIN'] = config.PASSWORD_LENGTH_MIN350 351    # Create database connection object and mailer352    db.init_app(app)353    Migrate(app, db)354 355    ##########################################################################356    # Upgrade the schema (if required)357    ##########################################################################358    from config import SQLITE_PATH359    from pgadmin.setup import db_upgrade360 361    def backup_db_file():362        """363        Create a backup of the current database file364        and create new database file with default settings.365        """366        backup_file_name = "{0}.{1}".format(367            SQLITE_PATH, datetime.now().strftime('%Y%m%d%H%M%S'))368        os.rename(SQLITE_PATH, backup_file_name)369        app.logger.error('Exception in database migration.')370        app.logger.info('Creating new database file.')371        try:372            db_upgrade(app)373            os.environ[374                'CORRUPTED_DB_BACKUP_FILE'] = backup_file_name375            app.logger.info('Database migration completed.')376        except Exception:377            app.logger.error('Database migration failed')378            app.logger.error(traceback.format_exc())379            raise RuntimeError('Migration failed')380 381    def upgrade_db():382        """383        Execute the migrations.384        """385        try:386            db_upgrade(app)387            os.environ['CORRUPTED_DB_BACKUP_FILE'] = ''388        except Exception:389            app.logger.error('Database migration failed')390            app.logger.error(traceback.format_exc())391            backup_db_file()392 393        # check all tables are present in the db.394        is_db_error, invalid_tb_names = check_db_tables()395        if is_db_error:396            app.logger.error(397                'Table(s) {0} are missing in the'398                ' database'.format(invalid_tb_names))399            backup_db_file()400 401    def run_migration_for_sqlite():402        with app.app_context():403            # Run migration for the first time i.e. create database404            # If version not available, user must have aborted. Tables are not405            # created and so its an empty db406            if not os.path.exists(SQLITE_PATH) or get_version() == -1:407                # If running in cli mode then don't try to upgrade, just raise408                # the exception409                if not cli_mode:410                    upgrade_db()411                else:412                    if not os.path.exists(SQLITE_PATH):413                        raise FileNotFoundError(414                            'SQLite database file "' + SQLITE_PATH +415                            '" does not exists.')416                    raise RuntimeError(417                        'The configuration database file is not valid.')418            else:419                schema_version = get_version()420 421                # Run migration if current schema version is greater than the422                # schema version stored in version table423                if CURRENT_SCHEMA_VERSION > schema_version:424                    # Take a backup of the old database file.425                    try:426                        prev_database_file_name = \427                            "{0}.prev.bak".format(SQLITE_PATH)428                        shutil.copyfile(SQLITE_PATH, prev_database_file_name)429                    except Exception as e:430                        app.logger.error(e)431 432                    upgrade_db()433                else:434                    # check all tables are present in the db.435                    is_db_error, invalid_tb_names = check_db_tables()436                    if is_db_error:437                        app.logger.error(438                            'Table(s) {0} are missing in the'439                            ' database'.format(invalid_tb_names))440                        backup_db_file()441 442                # Update schema version to the latest443                if CURRENT_SCHEMA_VERSION > schema_version:444                    set_version(CURRENT_SCHEMA_VERSION)445                    db.session.commit()446 447            if os.name != 'nt':448                os.chmod(config.SQLITE_PATH, 0o600)449 450    def run_migration_for_others():451        with app.app_context():452            # Run migration for the first time i.e. create database453            # If version not available, user must have aborted. Tables are not454            # created and so its an empty db455            if get_version() == -1:456                db_upgrade(app)457            else:458                schema_version = get_version()459 460                # Run migration if current schema version is greater than461                # the schema version stored in version table.462                if CURRENT_SCHEMA_VERSION > schema_version:463                    db_upgrade(app)464                    # Update schema version to the latest465                    set_version(CURRENT_SCHEMA_VERSION)466                    db.session.commit()467 468    # Run the migration as per specified by the user.469    if config.CONFIG_DATABASE_URI is not None and \470            len(config.CONFIG_DATABASE_URI) > 0:471        run_migration_for_others()472    else:473        run_migration_for_sqlite()474 475    Mail(app)476 477    # Don't bother paths when running in cli mode478    if not cli_mode:479        from pgadmin.utils import paths480        paths.init_app()481 482    # Setup Flask-Security483    user_datastore = SQLAlchemyUserDatastore(db, User, Role)484    security = Security(None, user_datastore)485 486    ##########################################################################487    # Setup security488    ##########################################################################489    with app.app_context():490        config.CSRF_SESSION_KEY = Keys.query.filter_by(491            name='CSRF_SESSION_KEY').first().value492        config.SECRET_KEY = Keys.query.filter_by(493            name='SECRET_KEY').first().value494        config.SECURITY_PASSWORD_SALT = Keys.query.filter_by(495            name='SECURITY_PASSWORD_SALT').first().value496 497    # Update the app.config with proper security keyes for signing CSRF data,498    # signing cookies, and the SALT for hashing the passwords.499    app.config.update(dict({500        'CSRF_SESSION_KEY': config.CSRF_SESSION_KEY,501        'SECRET_KEY': config.SECRET_KEY,502        'SECURITY_PASSWORD_SALT': config.SECURITY_PASSWORD_SALT,503        'SESSION_COOKIE_DOMAIN': config.SESSION_COOKIE_DOMAIN,504        # CSRF Token expiration till session expires505        'WTF_CSRF_TIME_LIMIT': getattr(config, 'CSRF_TIME_LIMIT', None),506        'WTF_CSRF_METHODS': ['GET', 'POST', 'PUT', 'DELETE'],507        # Disable deliverable check for email addresss508        'SECURITY_EMAIL_VALIDATOR_ARGS': config.SECURITY_EMAIL_VALIDATOR_ARGS,509        # Disable CSRF for unauthenticated endpoints510        'SECURITY_CSRF_IGNORE_UNAUTH_ENDPOINTS': True511    }))512 513    app.config.update(dict({514        'INTERNAL': INTERNAL,515        'LDAP': LDAP,516        'KERBEROS': KERBEROS,517        'OAUTH2': OAUTH2,518        'WEBSERVER': WEBSERVER519    }))520 521    security.init_app(app, user_datastore)522 523    # register custom unauthorised handler.524    if sys.version_info < (3, 8):525        app.login_manager.unauthorized_handler(pga_unauthorised)526    else:527        # Flask-Security-Too > 5.4.* requires custom unauth handeler528        # to be registeres with it.529        security.unauthn_handler(pga_unauthorised)530 531    # Set the permanent session lifetime to the specified value in config file.532    app.permanent_session_lifetime = timedelta(533        days=config.SESSION_EXPIRATION_TIME)534 535    if not cli_mode:536        app.session_interface = create_session_interface(537            app, config.SESSION_SKIP_PATHS538        )539 540    # Make the Session more secure against XSS & CSRF when running in web mode541    if config.SERVER_MODE and config.ENHANCED_COOKIE_PROTECTION:542        paranoid = Paranoid(app)543        paranoid.redirect_view = _INDEX_PATH544 545    ##########################################################################546    # Load all available server drivers547    ##########################################################################548    driver.init_app(app)549    authenticate.init_app(app)550    heartbeat.init_app(app)551 552    ##########################################################################553    # Register language to the preferences after login554    ##########################################################################555    @user_logged_in.connect_via(app)556    def register_language(sender, user):557        # After logged in, set the language in the preferences if we get from558        # the login page559        data = request.form560        if 'language' in data:561            language = data['language']562 563            # Set the user language preference564            misc_preference = Preferences.module('misc')565            user_languages = misc_preference.preference(566                'user_language'567            )568 569            if user_languages and language:570                language = user_languages.set(language)571 572    ##########################################################################573    # Register any local servers we can discover574    ##########################################################################575    @user_logged_in.connect_via(app)576    def on_user_logged_in(sender, user):577 578        # If Auto Discover servers is turned off then return from the579        # function.580        if not config.AUTO_DISCOVER_SERVERS:581            return582 583        # Keep hold of the user ID584        user_id = user.id585 586        # Get the first server group for the user587        servergroup_id = 1588        servergroups = ServerGroup.query.filter_by(589            user_id=user_id590        ).order_by("id")591 592        if int(servergroups.count()) > 0:593            servergroup = servergroups.first()594            servergroup_id = servergroup.id595 596        '''Add a server to the config database'''597 598        def add_server(user_id, servergroup_id, name, superuser, port,599                       discovery_id, comment):600            # Create a server object if needed, and store it.601            servers = Server.query.filter_by(602                user_id=user_id,603                discovery_id=svr_discovery_id604            ).order_by("id")605 606            if int(servers.count()) > 0:607                return608 609            svr = Server(user_id=user_id,610                         servergroup_id=servergroup_id,611                         name=name,612                         host='localhost',613                         port=port,614                         maintenance_db='postgres',615                         username=superuser,616                         connection_params={'sslmode': 'prefer',617                                            'connect_timeout': 10},618                         comment=comment,619                         discovery_id=discovery_id)620 621            db.session.add(svr)622            db.session.commit()623 624        # Figure out what servers are present625        if winreg is not None:626            arch_keys = set()627            proc_arch = os.environ['PROCESSOR_ARCHITECTURE'].lower()628 629            try:630                proc_arch64 = os.environ['PROCESSOR_ARCHITEW6432'].lower()631            except Exception:632                proc_arch64 = None633 634            if proc_arch == 'x86' and not proc_arch64:635                arch_keys.add(0)636            elif proc_arch == 'x86' or proc_arch == 'amd64':637                arch_keys.add(winreg.KEY_WOW64_32KEY)638                arch_keys.add(winreg.KEY_WOW64_64KEY)639 640            for arch_key in arch_keys:641                for server_type in ('PostgreSQL', 'EnterpriseDB'):642                    try:643                        root_key = winreg.OpenKey(644                            winreg.HKEY_LOCAL_MACHINE,645                            "SOFTWARE\\" + server_type + "\\Services", 0,646                            winreg.KEY_READ | arch_key647                        )648                        for i in range(0, winreg.QueryInfoKey(root_key)[0]):649                            inst_id = winreg.EnumKey(root_key, i)650                            inst_key = winreg.OpenKey(root_key, inst_id)651 652                            svr_name = winreg.QueryValueEx(653                                inst_key, 'Display Name'654                            )[0]655                            svr_superuser = winreg.QueryValueEx(656                                inst_key, 'Database Superuser'657                            )[0]658                            svr_port = winreg.QueryValueEx(inst_key, 'Port')[0]659                            svr_discovery_id = inst_id660                            svr_comment = gettext(661                                "Auto-detected {0} installation with the data "662                                "directory at {1}").format(663                                    winreg.QueryValueEx(664                                        inst_key, 'Display Name'665                                    )[0],666                                    winreg.QueryValueEx(667                                        inst_key, 'Data Directory'668                                    )[0])669 670                            add_server(671                                user_id, servergroup_id, svr_name,672                                svr_superuser, svr_port,673                                svr_discovery_id, svr_comment674                            )675 676                            inst_key.Close()677                    except Exception:678                        pass679        else:680            # We use the postgres-winreg.ini file on non-Windows681            from configparser import ConfigParser682 683            registry = ConfigParser()684 685        try:686            registry.read('/etc/postgres-reg.ini')687            sections = registry.sections()688 689            # Loop the sections, and get the data from any that are PG or PPAS690            for section in sections:691                if (692                    section.startswith('PostgreSQL/') or693                    section.startswith('EnterpriseDB/')694                ):695                    svr_name = registry.get(section, 'Description')696                    svr_superuser = registry.get(section, 'Superuser')697 698                    # getint function throws exception if value is blank.699                    # Ex: Port=700                    # In such case we should handle the exception and continue701                    # to read the next section of the config file.702                    try:703                        svr_port = registry.getint(section, 'Port')704                    except ValueError:705                        continue706 707                    svr_discovery_id = section708                    description = registry.get(section, 'Description')709                    data_directory = registry.get(section, 'DataDirectory')710                    svr_comment = gettext("Auto-detected {0} installation "711                                          "with the data directory at {1}"712                                          ).format(description, data_directory)713                    add_server(user_id, servergroup_id, svr_name,714                               svr_superuser, svr_port, svr_discovery_id,715                               svr_comment)716 717        except Exception as e:718            print(str(e))719            db.session.rollback()720 721    @user_logged_in.connect_via(app)722    @user_logged_out.connect_via(app)723    def force_session_write(app, user):724        session.force_write = True725 726    @user_logged_in.connect_via(app)727    def store_crypt_key(app, user):728        # in desktop mode, master password is used to encrypt/decrypt729        # and is stored in the keyManager memory730        if config.SERVER_MODE and 'password' in request.form:731            current_app.keyManager.set(request.form['password'])732 733    @user_logged_out.connect_via(app)734    def current_user_cleanup(app, user):735        from config import PG_DEFAULT_DRIVER736        from pgadmin.utils.driver import get_driver737        from flask import current_app738 739        for mdl in current_app.logout_hooks:740            try:741                mdl.on_logout()742            except Exception as e:743                current_app.logger.exception(e)744 745        _driver = get_driver(PG_DEFAULT_DRIVER)746        _driver.gc_own()747 748        # remove key749        current_app.keyManager.reset()750 751    ##########################################################################752    # Load plugin modules753    ##########################################################################754    from .submodules import get_submodules755    for module in get_submodules():756        app.logger.info('Registering blueprint module: %s' % module)757        if app.blueprints.get(module.name) is None:758            app.register_blueprint(module)759            app.register_logout_hook(module)760 761    @app.before_request762    def limit_host_addr():763        """764        This function validate the hosts from ALLOWED_HOSTS before allowing765        HTTP request to avoid Host Header Injection attack766        :return: None/JSON response with 403 HTTP status code767        """768        client_host = str(request.host).split(':', maxsplit=1)[0]769        valid = True770        allowed_hosts = config.ALLOWED_HOSTS771 772        if len(allowed_hosts) != 0:773            regex = re.compile(774                r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:/\d{1,2}|)')775            # Create separate list for ip addresses and host names776            ip_set = list(filter(lambda ip: regex.match(ip), allowed_hosts))777            host_set = list(filter(lambda ip: not regex.match(ip),778                                   allowed_hosts))779            is_ip = regex.match(client_host)780            if is_ip:781                ip_address = []782                for ip in ip_set:783                    ip_address.extend(list(ipaddress.ip_network(ip)))784                valid = ip_address.__contains__(785                    ipaddress.ip_address(client_host)786                )787            else:788                valid = host_set.__contains__(client_host)789 790        if not valid:791            return make_json_response(792                status=403, success=0,793                errormsg=_("403 FORBIDDEN")794            )795 796    ##########################################################################797    # Handle the desktop login798    ##########################################################################799 800    @app.before_request801    def before_request():802        """Login the default user if running in desktop mode"""803 804        # Check the auth key is valid, if it's set, and we're not in server805        # mode, and it's not a help file request.806 807        if not config.SERVER_MODE and app.PGADMIN_INT_KEY != '' and ((808            'key' not in request.args or809            request.args['key'] != app.PGADMIN_INT_KEY) and810            request.cookies.get('PGADMIN_INT_KEY') != app.PGADMIN_INT_KEY and811            request.endpoint != 'help.static'812        ):813            abort(401)814 815        if not config.SERVER_MODE and not current_user.is_authenticated:816            user = user_datastore.find_user(email=config.DESKTOP_USER)817            # Throw an error if we failed to find the desktop user, to give818            # the sysadmin a hint. We'll continue to try to login anyway as819            # that'll through a nice 500 error for us.820            if user is None:821                app.logger.error(822                    'The desktop user %s was not found in the configuration '823                    'database.'824                    % config.DESKTOP_USER825                )826                abort(401)827            login_user(user)828        elif config.SERVER_MODE and not current_user.is_authenticated and \829                request.endpoint in ('redirects.index', 'security.login') and \830                app.PGADMIN_EXTERNAL_AUTH_SOURCE in [KERBEROS, WEBSERVER]:831            return authenticate.login()832        # if the server is restarted the in memory key will be lost833        # but the user session may still be active. Logout the user834        # to get the key again when login835        if config.SERVER_MODE and current_user.is_authenticated and \836            app.PGADMIN_EXTERNAL_AUTH_SOURCE not in [837                KERBEROS, OAUTH2, WEBSERVER] and \838                current_app.keyManager.get() is None and \839                request.endpoint not in ('security.login', 'security.logout'):840            logout_user()841 842    @app.after_request843    def after_request(response):844        if 'key' in request.args:845            domain = dict()846            if config.COOKIE_DEFAULT_DOMAIN and \847                    config.COOKIE_DEFAULT_DOMAIN != 'localhost':848                domain['domain'] = config.COOKIE_DEFAULT_DOMAIN849            response.set_cookie('PGADMIN_INT_KEY', value=request.args['key'],850                                path=config.SESSION_COOKIE_PATH,851                                secure=config.SESSION_COOKIE_SECURE,852                                httponly=config.SESSION_COOKIE_HTTPONLY,853                                samesite=config.SESSION_COOKIE_SAMESITE,854                                **domain)855 856        SecurityHeaders.set_response_headers(response)857        return response858 859    ##########################################################################860    # Cache busting861    ##########################################################################862 863    # Version number to be added to all static file url requests864    # This is used by url_for function when generating urls865    # This will solve caching issues when application is upgrading866    # This is called - Cache Busting867    @app.url_defaults868    def add_internal_version(endpoint, values):869        extensions = config.APP_VERSION_EXTN870 871        # Add the internal version only if it is set872        if config.APP_VERSION_PARAM is not None and \873           config.APP_VERSION_PARAM != '':874            # If there is a filename, add the version875            if 'filename' in values \876               and values['filename'].endswith(extensions):877                values[config.APP_VERSION_PARAM] = config.APP_VERSION_INT878            else:879                # Sometimes there may be direct endpoint for some files880                # There will be only one rule for such endpoints881                urls = [url for url in app.url_map.iter_rules(endpoint)]882                if len(urls) == 1 and urls[0].rule.endswith(extensions):883                    values[config.APP_VERSION_PARAM] = \884                        config.APP_VERSION_INT885 886    # Strip away internal version param before sending further to app as it was887    # required for cache busting only888    @app.url_value_preprocessor889    def strip_version_number(endpoint, values):890        if values and config.APP_VERSION_PARAM in values:891            values.pop(config.APP_VERSION_PARAM)892 893    ##########################################################################894    # Minify output. Not required in desktop mode895    ##########################################################################896    if not config.DEBUG and config.SERVER_MODE:897        from flask_compress import Compress898        Compress(app)899 900    @app.context_processor901    def inject_blueprint():902        """903        Inject a reference to the current blueprint, if any.904        """905 906        return {907            'current_app': current_app,908            'current_blueprint': current_blueprint,909        }910 911    @app.errorhandler(Exception)912    def all_exception_handler(e):913        current_app.logger.error(e, exc_info=True)914        return internal_server_error(errormsg=str(e))915 916    # Exclude HTTPexception from above handler (all_exception_handler)917    # HTTPException are user defined exceptions and those should be returned918    # as is919    @app.errorhandler(HTTPException)920    def http_exception_handler(e):921        current_app.logger.error(e, exc_info=True)922        return e923 924    # Intialize the key manager925    app.keyManager = KeyManager()926 927    ##########################################################################928    # Protection against CSRF attacks929    ##########################################################################930    with app.app_context():931        pgCSRFProtect.init_app(app)932 933    ##########################################################################934    # All done!935    ##########################################################################936    socketio.init_app(app, cors_allowed_origins="*")937    return app938 
codekingpro/portable-devtools · Team Ai