Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:

environment:
name: testpypi
url: https://test.pypi.org/p/dserver-retrieve-plugin-mongo
url: https://test.pypi.org/p/dservercore

permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/dserver-retrieve-plugin-mongo # Replace <package-name> with your PyPI project name
url: https://pypi.org/p/dservercore # Replace <package-name> with your PyPI project name
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing

Expand Down
11 changes: 6 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ jobs:

strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
mongodb-version: ['4.2', '4.4', '5.0', '6.0']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
mongodb-version: ['5.0', '6.0', '7.0', '8.0']
dserver-search-plugin-mongo-version: ['0.4.2']
dserver-retrieve-plugin-mongo-version: ['0.4.2']

steps:
- name: checkout
Expand All @@ -45,9 +47,8 @@ jobs:

- name: install search and retrieve plugins
run: |
# This should move into the strategy matrix once released
pip install git+https://github.com/jic-dtool/dserver-search-plugin-mongo.git@main
pip install git+https://github.com/jic-dtool/dserver-retrieve-plugin-mongo.git@main
pip install dserver-search-plugin-mongo==${{ matrix.dserver-search-plugin-mongo-version }}
pip install dserver-retrieve-plugin-mongo==${{ matrix.dserver-retrieve-plugin-mongo-version }}

- name: test with pytest
run: |
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ CHANGELOG
This project uses `semantic versioning <http://semver.org/>`_.
This change log uses principles from `keep a changelog <http://keepachangelog.com/>`_.

[unreleased]
------------

Changed
^^^^^^^

- ``pkg_resources`` has been deprecated with Python 3.12. Replaced use of ``pkg_resources.iter_entry_points`` with ``importlib.metadata.entry_points`` for >= Python 3.8
- Added configuration option ``DISABLE_JWT_AUTHORISATION`` to disable JWT authorisation for testing purposes or for running locally
- Added configuration option ``DEFAULT_USER`` to specify the default user identity used when JWT authorisation is disabled

[0.21.0]
--------

Expand Down
31 changes: 24 additions & 7 deletions dservercore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@
from dservercore.sort import SortParameters
from dservercore.sql_models import DatasetSchema


from pkg_resources import iter_entry_points


logger = logging.getLogger(__name__)

# workaround for diverging python versions:
Expand All @@ -46,6 +42,26 @@
__version__ = None


# Python version-dependent treatment of entry points
if sys.version_info >= (3, 8):
from importlib.metadata import entry_points
eps = entry_points()
if sys.version_info >= (3, 10):
search_entrypoints_iterator = eps.select(group="dservercore.search")
retrieve_entrypoints_iterator = eps.select(group="dservercore.retrieve")
extension_entrypoints_iterator = eps.select(group="dservercore.extension")
else:
search_entrypoints_iterator = eps.get("dservercore.search", [])
retrieve_entrypoints_iterator = eps.get("dservercore.retrieve", [])
extension_entrypoints_iterator = eps.get("dservercore.extension", [])
else: # Python version < 3.8
from pkg_resources import iter_entry_points

search_entrypoints_iterator = iter_entry_points("dservercore.search")
retrieve_entrypoints_iterator = iter_entry_points("dservercore.retrieve")
extension_entrypoints_iterator = iter_entry_points("dservercore.extension")


class ValidationError(ValueError):
pass

Expand Down Expand Up @@ -228,9 +244,10 @@ def init_app(self, app, *args, **kwargs):
def create_app(test_config=None):
app = Flask(__name__)


# Load the search plugin.
search_entrypoints = []
for entrypoint in iter_entry_points("dservercore.search"):
for entrypoint in search_entrypoints_iterator:
logger.info("Discovered search plugin entrypoint %s", entrypoint)
search_entrypoints.append(entrypoint.load())
if len(search_entrypoints) < 1:
Expand All @@ -241,7 +258,7 @@ def create_app(test_config=None):

# Load the retrieve plugin.
retrieve_entrypoints = []
for entrypoint in iter_entry_points("dservercore.retrieve"):
for entrypoint in retrieve_entrypoints_iterator:
logger.info("Discovered retrieve plugin entrypoint %s", entrypoint)
retrieve_entrypoints.append(entrypoint.load())
if len(retrieve_entrypoints) < 1:
Expand All @@ -252,7 +269,7 @@ def create_app(test_config=None):

# Load any extension plugins.
app.custom_extensions = []
for entrypoint in iter_entry_points("dservercore.extension"):
for entrypoint in extension_entrypoints_iterator:
logger.info("Discovered extension plugin entrypoint %s", entrypoint)
ep = entrypoint.load()
app.custom_extensions.append(ep())
Expand Down
2 changes: 1 addition & 1 deletion dservercore/annotations_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
jsonify,
current_app
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion dservercore/base_uri_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from flask import (
abort,
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
5 changes: 5 additions & 0 deletions dservercore/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ class Config(object):
JWT_PRIVATE_KEY = _get_file_content("JWT_PRIVATE_KEY_FILE")
JWT_PUBLIC_KEY = _get_file_content("JWT_PUBLIC_KEY_FILE")

# Allow to disable jwt authentication
DISABLE_JWT_AUTHORISATION = os.environ.get("DISABLE_JWT_AUTHORISATION", False)
# If JWT authorisation disabled, always identify as this user:
DEFAULT_USER = os.environ.get("DEFAULT_USER", "testuser")

JSONIFY_PRETTYPRINT_REGULAR = True

API_TITLE = "dserver API"
Expand Down
2 changes: 1 addition & 1 deletion dservercore/config_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
jsonify,
)

from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion dservercore/manifest_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
jsonify,
current_app
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion dservercore/me_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
abort,
jsonify,
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion dservercore/readme_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
jsonify,
current_app
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion dservercore/tags_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
jsonify,
current_app
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion dservercore/uri_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
abort,
jsonify
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion dservercore/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
abort,
jsonify,
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
34 changes: 19 additions & 15 deletions dservercore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import importlib
import json
import logging
from pkg_resources import iter_entry_points
import sys

from itertools import chain

from flask import current_app
from flask_smorest.pagination import PaginationParameters
Expand All @@ -19,6 +21,9 @@
ValidationError,
UnknownBaseURIError,
UnknownURIError,
search_entrypoints_iterator,
retrieve_entrypoints_iterator,
extension_entrypoints_iterator,
__version__
)
from dservercore.sql_models import (
Expand Down Expand Up @@ -61,11 +66,6 @@
]


# These entrypoints might point to plugin modules with
# config objects to be serialized as part of the global server config:
DSERVER_PLUGIN_ENTRYPOINTS = ['extension', 'retrieve', 'search']


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -136,18 +136,22 @@ def versions_to_dict():
"""

versions_dict = {'dservercore': __version__}
for ep_group in DSERVER_PLUGIN_ENTRYPOINTS:
for ep in iter_entry_points("dservercore.{}".format(ep_group)):
entrypoints_iterator = chain(
search_entrypoints_iterator, retrieve_entrypoints_iterator, extension_entrypoints_iterator)
for ep in entrypoints_iterator:
if sys.version_info < (3, 8):
module_name = ep.module_name.split(".")[0]
else:
module_name = ep.value.split(":")[0].split(".")[0]

# import module
try:
plugin_module = importlib.import_module(module_name)
except ImportError as exc:
# plugin import failed, this should not happen
continue
# import module
try:
plugin_module = importlib.import_module(module_name)
except ImportError as exc:
# plugin import failed, this should not happen
continue

versions_dict[module_name] = getattr(plugin_module, '__version__', None)
versions_dict[module_name] = getattr(plugin_module, '__version__', None)

return versions_dict

Expand Down
28 changes: 28 additions & 0 deletions dservercore/utils_auth.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
"""Auth utility functions."""

from functools import wraps

from dservercore.sql_models import (
User,
BaseURI,
)

from flask import current_app

from flask_jwt_extended import jwt_required as flask_jwt_required
from flask_jwt_extended import get_jwt_identity as flask_get_jwt_identity


def jwt_required(*jwt_required_args, **jwt_required_kwargs):
"""Mark route for requiring JWT authorisation, unless JWT authorisation disabled."""
def wrapper(fn):
@wraps(fn)
def decorator(*args, **kwargs):
if current_app.config.get("DISABLE_JWT_AUTHORISATION"):
return fn(*args, **kwargs)
else:
return flask_jwt_required(*jwt_required_args, **jwt_required_kwargs)(fn)(*args, **kwargs)
return decorator
return wrapper


def get_jwt_identity():
"""Return JWT identity or 'testuser' if JWT authorisation disabled."""
if current_app.config.get("DISABLE_JWT_AUTHORISATION"):
return current_app.config.get("DEFAULT_USER")
else:
return flask_get_jwt_identity()


def _get_user_obj(username):
return User.query.filter_by(username=username).first()
Expand Down
2 changes: 1 addition & 1 deletion dservercore/uuid_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
abort,
jsonify
)
from flask_jwt_extended import (
from dservercore.utils_auth import (
jwt_required,
get_jwt_identity,
)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies = [
"marshmallow-sqlalchemy",
"flask-cors",
"dtoolcore>=3.18.0",
"flask-jwt-extended[asymmetric_crypto]>=4.0",
"flask-jwt-extended[asymmetric_crypto]>=4.6.0",
"pyyaml"
]

Expand Down