diff --git a/foca/database/register_mongodb.py b/foca/database/register_mongodb.py index 4433515..90c8997 100644 --- a/foca/database/register_mongodb.py +++ b/foca/database/register_mongodb.py @@ -123,18 +123,24 @@ def _create_mongo_client( Returns: MongoDB client for Flask application instance. """ + # Normalize environment variables: strip whitespace and treat empty strings as None + mongo_username = os.environ.get('MONGO_USERNAME', '').strip() or None + mongo_password = os.environ.get('MONGO_PASSWORD', '').strip() or None + mongo_host = os.environ.get('MONGO_HOST', '').strip() or host + mongo_port = os.environ.get('MONGO_PORT', '').strip() or port + mongo_dbname = os.environ.get('MONGO_DBNAME', '').strip() or db + auth = '' - user = os.environ.get('MONGO_USERNAME') - if user is not None and user != "": + if mongo_username is not None and mongo_password is not None: auth = '{username}:{password}@'.format( - username=os.environ.get('MONGO_USERNAME'), - password=os.environ.get('MONGO_PASSWORD'), + username=mongo_username, + password=mongo_password, ) app.config['MONGO_URI'] = 'mongodb://{auth}{host}:{port}/{db}'.format( - host=os.environ.get('MONGO_HOST', host), - port=os.environ.get('MONGO_PORT', port), - db=os.environ.get('MONGO_DBNAME', db), + host=mongo_host, + port=mongo_port, + db=mongo_dbname, auth=auth ) @@ -144,9 +150,9 @@ def _create_mongo_client( "Registered database '{db}' at URI '{host}':'{port}' with Flask " 'application.' ).format( - db=os.environ.get('MONGO_DBNAME', db), - host=os.environ.get('MONGO_HOST', host), - port=os.environ.get('MONGO_PORT', port) + db=mongo_dbname, + host=mongo_host, + port=mongo_port ) ) return mongo diff --git a/foca/foca.py b/foca/foca.py index a249c89..ac9e725 100644 --- a/foca/foca.py +++ b/foca/foca.py @@ -149,6 +149,7 @@ def create_app(self) -> App: mongo_config=self.conf.db, access_control_config=self.conf.security.access_control, ) + logger.info("Access control registered.") else: if ( self.conf.security.access_control.api_specs diff --git a/foca/models/config.py b/foca/models/config.py index 121e261..5e76b22 100644 --- a/foca/models/config.py +++ b/foca/models/config.py @@ -4,7 +4,7 @@ from enum import Enum from functools import reduce import importlib -from importlib.resources import path as resource_path +from importlib.resources import as_file, files import operator from pathlib import Path from typing import ( @@ -726,10 +726,10 @@ def validate_model_path(cls, v: Optional[str]) -> str: """ if v is None: - with resource_path( - ACCESS_CONTROL_BASE_PATH, + model_file = files(ACCESS_CONTROL_BASE_PATH).joinpath( DEFAULT_MODEL_FILE - ) as _path: + ) + with as_file(model_file) as _path: return str(_path) model_path = Path(v) diff --git a/foca/security/access_control/register_access_control.py b/foca/security/access_control/register_access_control.py index ffd1894..031a8c4 100644 --- a/foca/security/access_control/register_access_control.py +++ b/foca/security/access_control/register_access_control.py @@ -2,7 +2,7 @@ import logging from functools import wraps -from importlib.resources import path as resource_path +from importlib.resources import as_file, files from pathlib import Path from typing import (Callable, Optional, Tuple) @@ -82,11 +82,13 @@ def register_access_control( mongo_config=mongo_config, access_control_config=access_control_config ) + logger.info("Access control enforcer registered.") cnx_app = register_permission_specs( app=cnx_app, access_control_config=access_control_config ) + logger.info("Access control permission specifications registered.") return cnx_app @@ -108,9 +110,10 @@ def register_permission_specs( """ # Check if default, get package path variables for specs. if access_control_config.api_specs is None: - with resource_path( - ACCESS_CONTROL_BASE_PATH, DEFAULT_API_SPEC_PATH - ) as _path: + spec_file = files(ACCESS_CONTROL_BASE_PATH).joinpath( + DEFAULT_API_SPEC_PATH + ) + with as_file(spec_file) as _path: spec_path = str(_path) else: spec_path = access_control_config.api_specs diff --git a/setup.cfg b/setup.cfg index 4a3889c..8182f7e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,3 +18,10 @@ version_variable = foca/__init__.py:__version__ [mypy] ignore_missing_imports = True + +[tool:pytest] +filterwarnings = + ignore:Accessing jsonschema\.draft4_format_checker is deprecated:DeprecationWarning + ignore:jsonschema\.RefResolver is deprecated:DeprecationWarning + ignore:jsonschema\.exceptions\.RefResolutionError is deprecated:DeprecationWarning + ignore:Passing a schema to Validator\.iter_errors is deprecated:DeprecationWarning diff --git a/tests/security/access_control/test_register_access_control.py b/tests/security/access_control/test_register_access_control.py index 376819a..a9a42b6 100644 --- a/tests/security/access_control/test_register_access_control.py +++ b/tests/security/access_control/test_register_access_control.py @@ -1,5 +1,7 @@ """Tests for registering access control""" +import logging +from types import SimpleNamespace from flask import Flask import mongomock from pymongo import MongoClient @@ -7,7 +9,8 @@ import pytest from foca.security.access_control.register_access_control import ( - check_permissions + check_permissions, + register_access_control, ) from foca.security.access_control.foca_casbin_adapter.adapter import Adapter from foca.errors.exceptions import Forbidden @@ -110,3 +113,37 @@ def mock_func(): ): with pytest.raises(Forbidden): mock_func() + + +def test_register_access_control_logs_setup_steps(monkeypatch, caplog): + """Test setup logs for access control registration flow.""" + caplog.set_level(logging.INFO) + access_control = AccessControlConfig(**ACCESS_CONTROL_CONFIG) + mongo_config = MongoConfig(**MONGO_CONFIG) + + dummy_app = SimpleNamespace( + app=SimpleNamespace(config=SimpleNamespace(foca=SimpleNamespace(db=None))) + ) + + monkeypatch.setattr( + "foca.security.access_control.register_access_control.add_new_database", + lambda app, conf, db_conf, db_name: None, + ) + monkeypatch.setattr( + "foca.security.access_control.register_access_control.register_casbin_enforcer", + lambda app, mongo_config, access_control_config: app, + ) + monkeypatch.setattr( + "foca.security.access_control.register_access_control.register_permission_specs", + lambda app, access_control_config: app, + ) + + updated_app = register_access_control( + cnx_app=dummy_app, + mongo_config=mongo_config, + access_control_config=access_control, + ) + + assert updated_app is dummy_app + assert "Access control enforcer registered." in caplog.text + assert "Access control permission specifications registered." in caplog.text diff --git a/tests/test_foca.py b/tests/test_foca.py index 2ba7d18..05a332c 100644 --- a/tests/test_foca.py +++ b/tests/test_foca.py @@ -130,22 +130,29 @@ def test_foca_CORS_disabled(): assert app.app.config.foca.security.cors.enabled is False -def test_foca_invalid_access_control(): +def test_foca_invalid_access_control(capsys): """Ensures access control is not enabled if auth flag is disabled.""" foca = Foca(config_file=INVALID_ACCESS_CONTROL_CONF) app = foca.create_app() + logs = capsys.readouterr().err assert app.app.config.foca.db is None + assert "Please enable security config to register access control." in logs + assert "Access control registered." not in logs -def test_foca_valid_access_control(): +def test_foca_valid_access_control(capsys): """Ensures access control settings are set correctly.""" foca = Foca(config_file=VALID_ACCESS_CONTROL_CONF) app = foca.create_app() + logs = capsys.readouterr().err my_db = app.app.config.foca.db.dbs["test_db"] my_coll = my_db.collections["test_collection"] assert isinstance(my_db.client, Database) assert isinstance(my_coll.client, Collection) assert isinstance(app, App) + assert "Access control enforcer registered." in logs + assert "Access control permission specifications registered." in logs + assert "Access control registered." in logs def test_foca_create_celery_app():