diff --git a/requirements.txt b/requirements.txt index 712d9fc5e..316d0b8be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,20 +8,26 @@ # ############################################################################### -Flask==2.2.* +Flask==2.3.*; python_version > '3.7' +Flask==2.2.*; python_version <= '3.7' Flask-Gravatar==0.* Flask-Login==0.* Flask-Mail==0.* Flask-Migrate==4.* greenlet==1.1.2; python_version <= '3.10' -Flask-SQLAlchemy==3.0.* -Flask-WTF==1.1.1 +Flask-SQLAlchemy==3.1.*; python_version > '3.7' +Flask-SQLAlchemy==3.0.*; python_version <= '3.7' +Flask-WTF==1.2.*; python_version > '3.7' +Flask-WTF==1.1.1; python_version <= '3.7' Flask-Compress==1.* Flask-Paranoid==0.* -Flask-Babel==3.1.* -Flask-Security-Too==5.1.* +Flask-Babel==4.0.*; python_version > '3.7' +Flask-Babel==3.1.*; python_version <= '3.7' +Flask-Security-Too==5.2.*; python_version > '3.7' +Flask-Security-Too==5.1.*; python_version <= '3.7' Flask-SocketIO==5.3.* -WTForms==3.0.* +WTForms==3.1.*; python_version > '3.7' +WTForms==3.0.*; python_version <= '3.7' passlib==1.* pytz==2023.* speaklater3==1.* @@ -51,5 +57,7 @@ azure-mgmt-subscription==3.1.1 azure-identity==1.15.0 google-api-python-client==2.* google-auth-oauthlib==1.1.0 -Werkzeug==2.2.3 -keyring==24.* +keyring==24.*; python_version > '3.7' +keyring==23.*; python_version <= '3.7' +Werkzeug==2.3.*; python_version > '3.7' +Werkzeug==2.2.3; python_version <= '3.7' diff --git a/web/config.py b/web/config.py index 5fe98f0bf..3b7971c0d 100644 --- a/web/config.py +++ b/web/config.py @@ -414,13 +414,7 @@ SECURITY_EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE = \ ########################################################################## # Email address validation ########################################################################## - -# flask-security-too will validate email addresses and check deliverability -# by default. Disable the deliverability check by default, which was the old -# behaviour in <= v5.3 CHECK_EMAIL_DELIVERABILITY = False -SECURITY_EMAIL_VALIDATOR_ARGS = \ - {"check_deliverability": CHECK_EMAIL_DELIVERABILITY} ########################################################################## # Upgrade checks diff --git a/web/pgAdmin4.py b/web/pgAdmin4.py index ce52cf613..3d4b2d134 100644 --- a/web/pgAdmin4.py +++ b/web/pgAdmin4.py @@ -152,6 +152,8 @@ else: if not app.PGADMIN_RUNTIME: app.wsgi_app = ReverseProxied(app.wsgi_app) +app.run_before_app_start() + ########################################################################## # The entry point diff --git a/web/pgacloud/providers/_abstract.py b/web/pgacloud/providers/_abstract.py index d9a20a297..ae110e2a6 100644 --- a/web/pgacloud/providers/_abstract.py +++ b/web/pgacloud/providers/_abstract.py @@ -7,6 +7,7 @@ # ########################################################################## + class AbsProvider: """ Abstract provider """ parser = None diff --git a/web/pgadmin/__init__.py b/web/pgadmin/__init__.py index fc402b8d9..785103035 100644 --- a/web/pgadmin/__init__.py +++ b/web/pgadmin/__init__.py @@ -31,6 +31,7 @@ from flask_mail import Mail from flask_paranoid import Paranoid from flask_security import Security, SQLAlchemyUserDatastore, current_user from flask_security.utils import login_user, logout_user +from flask_migrate import Migrate from werkzeug.datastructures import ImmutableDict from werkzeug.local import LocalProxy from werkzeug.utils import find_modules @@ -79,6 +80,7 @@ class PgAdmin(Flask): loader=VersionedTemplateLoader(self) ) self.logout_hooks = [] + self.before_app_start = [] super().__init__(*args, **kwargs) @@ -105,13 +107,6 @@ class PgAdmin(Flask): if isinstance(blueprint, PgAdminModule): yield blueprint - @property - def stylesheets(self): - stylesheets = [] - for module in self.submodules: - stylesheets.extend(getattr(module, "stylesheets", [])) - return set(stylesheets) - @property def messages(self): messages = dict() @@ -150,21 +145,6 @@ class PgAdmin(Flask): yield 'pgadmin.root', wsgi_root_path - @property - def javascripts(self): - scripts = [] - scripts_names = [] - - # Remove duplicate javascripts from the list - for module in self.submodules: - module_scripts = getattr(module, "javascripts", []) - for s in module_scripts: - if s['name'] not in scripts_names: - scripts.append(s) - scripts_names.append(s['name']) - - return scripts - @property def menu_items(self): from operator import attrgetter @@ -182,6 +162,15 @@ class PgAdmin(Flask): isinstance(getattr(module, 'on_logout'), MethodType): self.logout_hooks.append(module) + def register_before_app_start(self, callback): + self.before_app_start.append(callback) + + def run_before_app_start(self): + # call before app starts or is exported + with self.app_context(), self.test_request_context(): + for callback in self.before_app_start: + callback() + def _find_blueprint(): if request.blueprint: @@ -348,6 +337,7 @@ def create_app(app_name=None): # Create database connection object and mailer db.init_app(app) + Migrate(app, db) ########################################################################## # Upgrade the schema (if required) @@ -501,8 +491,6 @@ def create_app(app_name=None): # CSRF Token expiration till session expires 'WTF_CSRF_TIME_LIMIT': getattr(config, 'CSRF_TIME_LIMIT', None), 'WTF_CSRF_METHODS': ['GET', 'POST', 'PUT', 'DELETE'], - # Disable deliverable check for email addresss - 'SECURITY_EMAIL_VALIDATOR_ARGS': config.SECURITY_EMAIL_VALIDATOR_ARGS })) if 'SCRIPT_NAME' in os.environ and os.environ["SCRIPT_NAME"]: diff --git a/web/pgadmin/browser/collection.py b/web/pgadmin/browser/collection.py index 692d2261e..0faeb95d3 100644 --- a/web/pgadmin/browser/collection.py +++ b/web/pgadmin/browser/collection.py @@ -218,10 +218,6 @@ class CollectionNodeModule(PgAdminModule, PGChildModule, metaclass=ABCMeta): def node_path(self): return self.browser_url_prefix + self.node_type - @property - def javascripts(self): - return [] - @property def show_node(self): """ diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py index c3b9f0fd9..c83640ab9 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/type.py @@ -35,7 +35,4 @@ class ConstraintRegistry(): class ConstraintTypeModule(CollectionNodeModule): - register = Blueprint.register - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + pass diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py index a2d479cac..26054eedc 100644 --- a/web/pgadmin/dashboard/__init__.py +++ b/web/pgadmin/dashboard/__init__.py @@ -36,14 +36,6 @@ class DashboardModule(PgAdminModule): def get_own_menuitems(self): return {} - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets - def register_preferences(self): """ register_preferences diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py index 7a48ef217..180b34646 100644 --- a/web/pgadmin/misc/__init__.py +++ b/web/pgadmin/misc/__init__.py @@ -10,7 +10,8 @@ """A blueprint module providing utility functions for the application.""" from pgadmin.utils import driver -from flask import url_for, render_template, Response, request, current_app +from flask import render_template, Response, request, current_app +from flask.helpers import url_for from flask_babel import gettext from flask_security import login_required from pgadmin.utils import PgAdminModule, replace_binary_path, \ @@ -36,10 +37,6 @@ MODULE_NAME = 'misc' class MiscModule(PgAdminModule): LABEL = gettext('Miscellaneous') - def get_own_stylesheets(self): - stylesheets = [] - return stylesheets - def register_preferences(self): """ Register preferences for this module. diff --git a/web/pgadmin/misc/bgprocess/__init__.py b/web/pgadmin/misc/bgprocess/__init__.py index c9799b82a..dcc9afd9d 100644 --- a/web/pgadmin/misc/bgprocess/__init__.py +++ b/web/pgadmin/misc/bgprocess/__init__.py @@ -23,13 +23,6 @@ MODULE_NAME = 'bgprocess' class BGProcessModule(PgAdminModule): - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets def get_exposed_url_endpoints(self): """ diff --git a/web/pgadmin/misc/cloud/__init__.py b/web/pgadmin/misc/cloud/__init__.py index 6a31c3943..609ceeaa5 100644 --- a/web/pgadmin/misc/cloud/__init__.py +++ b/web/pgadmin/misc/cloud/__init__.py @@ -44,14 +44,6 @@ class CloudModule(PgAdminModule): """ - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets - def get_exposed_url_endpoints(self): """ Returns: diff --git a/web/pgadmin/misc/cloud/azure/__init__.py b/web/pgadmin/misc/cloud/azure/__init__.py index fb64b7fca..ab2f078a8 100644 --- a/web/pgadmin/misc/cloud/azure/__init__.py +++ b/web/pgadmin/misc/cloud/azure/__init__.py @@ -39,14 +39,6 @@ MODULE_NAME = 'azure' class AzurePostgresqlModule(PgAdminModule): """Cloud module to deploy on Azure Postgresql""" - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets - def get_exposed_url_endpoints(self): return ['azure.verify_credentials', 'azure.check_cluster_name_availability', diff --git a/web/pgadmin/misc/cloud/biganimal/__init__.py b/web/pgadmin/misc/cloud/biganimal/__init__.py index 4f0481b46..ccdee0ef3 100644 --- a/web/pgadmin/misc/cloud/biganimal/__init__.py +++ b/web/pgadmin/misc/cloud/biganimal/__init__.py @@ -32,13 +32,6 @@ EHA_CLUSTER_ARCH = 'eha' # Extreme High Availability class BigAnimalModule(PgAdminModule): """Cloud module to deploy on EDB BigAnimal""" - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets def get_exposed_url_endpoints(self): return ['biganimal.verification', diff --git a/web/pgadmin/misc/cloud/google/__init__.py b/web/pgadmin/misc/cloud/google/__init__.py index c07f8f3e6..35d0f1743 100644 --- a/web/pgadmin/misc/cloud/google/__init__.py +++ b/web/pgadmin/misc/cloud/google/__init__.py @@ -37,14 +37,6 @@ os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Required for Oauth2 class GooglePostgresqlModule(PgAdminModule): """Cloud module to deploy on Google Cloud""" - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets - def get_exposed_url_endpoints(self): return ['google.verify_credentials', 'google.projects', diff --git a/web/pgadmin/misc/cloud/rds/__init__.py b/web/pgadmin/misc/cloud/rds/__init__.py index 3f9de90bb..bae0e1446 100644 --- a/web/pgadmin/misc/cloud/rds/__init__.py +++ b/web/pgadmin/misc/cloud/rds/__init__.py @@ -34,13 +34,6 @@ MODULE_NAME = 'rds' class RDSModule(PgAdminModule): """Cloud module to deploy on AWS RDS""" - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets def get_exposed_url_endpoints(self): return ['rds.db_versions', diff --git a/web/pgadmin/preferences/__init__.py b/web/pgadmin/preferences/__init__.py index 1167e882c..d45bdb8d9 100644 --- a/web/pgadmin/preferences/__init__.py +++ b/web/pgadmin/preferences/__init__.py @@ -36,9 +36,6 @@ class PreferencesModule(PgAdminModule): And, allows the user to modify (not add/remove) as per their requirement. """ - def get_own_stylesheets(self): - return [] - def get_own_menuitems(self): return {} diff --git a/web/pgadmin/setup/db_upgrade.py b/web/pgadmin/setup/db_upgrade.py index 69d9df952..8ce10eb8c 100644 --- a/web/pgadmin/setup/db_upgrade.py +++ b/web/pgadmin/setup/db_upgrade.py @@ -10,13 +10,10 @@ import os import flask_migrate -from pgadmin import db - def db_upgrade(app): from pgadmin.utils import u_encode, fs_encoding with app.app_context(): - flask_migrate.Migrate(app, db) migration_folder = os.path.join( os.path.dirname(os.path.realpath(u_encode(__file__, fs_encoding))), os.pardir, os.pardir, diff --git a/web/pgadmin/tools/grant_wizard/__init__.py b/web/pgadmin/tools/grant_wizard/__init__.py index 700c8b75e..b1bc96284 100644 --- a/web/pgadmin/tools/grant_wizard/__init__.py +++ b/web/pgadmin/tools/grant_wizard/__init__.py @@ -42,14 +42,6 @@ class GrantWizardModule(PgAdminModule): javascript file. """ - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets - def show_system_objects(self): """ return system preference objects diff --git a/web/pgadmin/tools/maintenance/__init__.py b/web/pgadmin/tools/maintenance/__init__.py index 98ce92e44..3df34e643 100644 --- a/web/pgadmin/tools/maintenance/__init__.py +++ b/web/pgadmin/tools/maintenance/__init__.py @@ -35,14 +35,6 @@ class MaintenanceModule(PgAdminModule): """ LABEL = _('Maintenance') - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module. - """ - stylesheets = [] - return stylesheets - def get_exposed_url_endpoints(self): """ Returns: diff --git a/web/pgadmin/utils/__init__.py b/web/pgadmin/utils/__init__.py index 291525ad5..fd0d3dd80 100644 --- a/web/pgadmin/utils/__init__.py +++ b/web/pgadmin/utils/__init__.py @@ -49,19 +49,6 @@ class PgAdminModule(Blueprint): super().__init__(name, import_name, **kwargs) - def create_module_preference(): - # Create preference for each module by default - if hasattr(self, 'LABEL'): - self.preference = Preferences(self.name, self.LABEL) - else: - self.preference = Preferences(self.name, None) - - self.register_preferences() - - # Create and register the module preference object and preferences for - # it just before the first request - self.before_app_first_request(create_module_preference) - def register_preferences(self): # To be implemented by child classes pass @@ -74,20 +61,25 @@ class PgAdminModule(Blueprint): super().register(app, options) + def create_module_preference(): + # Create preference for each module by default + if hasattr(self, 'LABEL'): + self.preference = Preferences(self.name, self.LABEL) + else: + self.preference = Preferences(self.name, None) + + self.register_preferences() + + # Create and register the module preference object and preferences for + # it just before starting app + app.register_before_app_start(create_module_preference) + for module in self.submodules: module.parentmodules.append(self) if app.blueprints.get(module.name) is None: app.register_blueprint(module) app.register_logout_hook(module) - def get_own_stylesheets(self): - """ - Returns: - list: the stylesheets used by this module, not including any - stylesheet needed by the submodules. - """ - return [] - def get_own_messages(self): """ Returns: @@ -111,13 +103,6 @@ class PgAdminModule(Blueprint): """ return [] - @property - def stylesheets(self): - stylesheets = self.get_own_stylesheets() - for module in self.submodules: - stylesheets.extend(module.stylesheets) - return stylesheets - @property def messages(self): res = self.get_own_messages() diff --git a/web/pgadmin/utils/validation_utils.py b/web/pgadmin/utils/validation_utils.py index d569716bf..6d17aae97 100644 --- a/web/pgadmin/utils/validation_utils.py +++ b/web/pgadmin/utils/validation_utils.py @@ -19,7 +19,6 @@ def validate_email(email): email, check_deliverability=config.CHECK_EMAIL_DELIVERABILITY) # Update with the normalized form. - email = valid.email return True except EmailNotValidError as e: # email is not valid, exception message is human-readable diff --git a/web/regression/python_test_utils/csrf_test_client.py b/web/regression/python_test_utils/csrf_test_client.py index 61c7a37e8..69b9f3092 100644 --- a/web/regression/python_test_utils/csrf_test_client.py +++ b/web/regression/python_test_utils/csrf_test_client.py @@ -9,12 +9,13 @@ import re import flask -from flask import current_app, request, session, testing +from flask import current_app, testing from werkzeug.datastructures import Headers from werkzeug.test import EnvironBuilder from flask_wtf.csrf import generate_csrf import config +import sys class RequestShim(): @@ -26,9 +27,17 @@ class RequestShim(): def set_cookie(self, key, value='', *args, **kwargs): "Set the cookie on the Flask test client." - server_name = current_app.config["SERVER_NAME"] or "localhost" + if sys.version_info <= (3, 7, 9999): + server_name = current_app.config["SERVER_NAME"] or "localhost" + return self.client.set_cookie( + server_name, key=key, value=value, *args, **kwargs + ) + + if kwargs['domain'] is None: + kwargs['domain'] = current_app.config["SERVER_NAME"] or "localhost" + return self.client.set_cookie( - server_name, key=key, value=value, *args, **kwargs + key=key, value=value, *args, **kwargs ) def delete_cookie(self, key, *args, **kwargs): @@ -90,8 +99,14 @@ class TestClient(testing.FlaskClient): # this test client, such as the secure cookie that # powers `flask.session`, # and make a test request context that has those cookies in it. - environ_overrides = {} - self.cookie_jar.inject_wsgi(environ_overrides) + environ_overrides = { + 'wsgi.url_scheme': '' + } + if sys.version_info <= (3, 7, 9999): + self.cookie_jar.inject_wsgi(environ_overrides) + else: + self._add_cookies_to_wsgi(environ_overrides) + with self.app.test_request_context(): # Now, we call Flask-WTF's method of generating a CSRF token... csrf_token = generate_csrf() diff --git a/web/regression/runtests.py b/web/regression/runtests.py index 77ff56e82..7c3b94b07 100644 --- a/web/regression/runtests.py +++ b/web/regression/runtests.py @@ -109,7 +109,9 @@ from logging import WARNING config.CONSOLE_LOG_LEVEL = WARNING # Create the app -app = create_app() +from pgAdmin4 import app +# app = create_app() +app.app_context().push() app.PGADMIN_INT_KEY = '' app.config.update({'SESSION_COOKIE_DOMAIN': None}) @@ -511,8 +513,9 @@ def execute_test(test_module_list_passed, server_passed, driver_passed, test_utils.create_database(server_passed, test_db_name) # Configure preferences for the test cases - test_utils.configure_preferences( - default_binary_path=server_passed['default_binary_paths']) + with app.app_context(): + test_utils.configure_preferences( + default_binary_path=server_passed['default_binary_paths']) # Create user to run selenoid tests in parallel if parallel_ui_test: