Skip to content

Commit

Permalink
Merge pull request #503 from geoadmin/develop
Browse files Browse the repository at this point in the history
New Release v1.31.0 - #minor
  • Loading branch information
adk-swisstopo authored Jan 28, 2025
2 parents 80e727c + b900b2f commit fca3992
Show file tree
Hide file tree
Showing 95 changed files with 2,197 additions and 1,498 deletions.
10 changes: 0 additions & 10 deletions app/config/settings_dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,6 @@
AWS_SETTINGS['managed']['ACCESS_KEY_ID'] = env("LEGACY_AWS_ACCESS_KEY_ID")
AWS_SETTINGS['managed']['SECRET_ACCESS_KEY'] = env("LEGACY_AWS_SECRET_ACCESS_KEY")

# API Gateway integration PB-1009
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.RemoteUserBackend",
# We keep ModelBackend as fallback until we have moved all users to Cognito.
"django.contrib.auth.backends.ModelBackend",
]
MIDDLEWARE += [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"middleware.apigw.ApiGatewayMiddleware",
]
# By default sessions expire after two weeks.
# Sessions are only useful for user tracking in the admin UI. For security
# reason we should expire these sessions as soon as possible. Given the use
Expand Down
11 changes: 11 additions & 0 deletions app/config/settings_prod.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
'stac_api.apps.StacApiConfig',
]

# API Authentication options
FEATURE_AUTH_ENABLE_APIGW = env('FEATURE_AUTH_ENABLE_APIGW', bool, default=False)

# Middlewares are executed in order, once for the incoming
# request top-down, once for the outgoing response bottom up
# Note: The prometheus middlewares should always be first and
Expand All @@ -92,13 +95,20 @@
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'middleware.api_gateway_middleware.ApiGatewayMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'middleware.cache_headers.CacheHeadersMiddleware',
'middleware.exception.ExceptionLoggingMiddleware',
'django_prometheus.middleware.PrometheusAfterMiddleware',
]

AUTHENTICATION_BACKENDS = [
"middleware.api_gateway_middleware.ApiGatewayUserBackend",
# We keep ModelBackend as fallback until we have moved all users to Cognito.
"django.contrib.auth.backends.ModelBackend",
]

ROOT_URLCONF = 'config.urls'
API_BASE = 'api'
STAC_BASE = f'{API_BASE}/stac'
Expand Down Expand Up @@ -295,6 +305,7 @@ def get_logging_config():
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'],
'DEFAULT_AUTHENTICATION_CLASSES': [
'middleware.api_gateway_authentication.ApiGatewayAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
Expand Down
30 changes: 30 additions & 0 deletions app/middleware/api_gateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
REMOTE_USER_HEADER = "HTTP_GEOADMIN_USERNAME"


def validate_username_header(request):
"""Drop the Geoadmin-Username header if it's invalid.
This should be called before making any decision based on the value of the
Geoadmin-Username header.
API Gateway always sends the Geoadmin-Username header regardless of
whether it was able to authenticate the user. If it could not
authenticate the user, the value of the header as seen on the wire is a
single whitespace. An hexdump looks like this:
47 65 6f 61 64 6d 69 6e 5f 75 73 65 72 6e 61 6d 65 3a 20 0d 0a
Geoadmin-Username:...
This doesn't seem possible to reproduce with curl. It is possible to
reproduce with wget. It is unclear whether that technically counts as an
empty value or a whitespace. It is also possible that AWS change their
implementation later to send something slightly different. Regardless,
we already have a separate signal to tell us whether that value is
valid: Geoadmin-Authenticated. So we only consider Geoadmin-Username if
Geoadmin-Authenticated is set to "true".
Based on discussion in https://code.djangoproject.com/ticket/35971
"""
apigw_auth = request.META.get("HTTP_GEOADMIN_AUTHENTICATED", "false").lower() == "true"
if not apigw_auth and REMOTE_USER_HEADER in request.META:
del request.META[REMOTE_USER_HEADER]
26 changes: 26 additions & 0 deletions app/middleware/api_gateway_authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from middleware import api_gateway

from django.conf import settings

from rest_framework.authentication import RemoteUserAuthentication


class ApiGatewayAuthentication(RemoteUserAuthentication):
header = api_gateway.REMOTE_USER_HEADER

def authenticate(self, request):
if not settings.FEATURE_AUTH_ENABLE_APIGW:
return None

api_gateway.validate_username_header(request)
return super().authenticate(request)

def authenticate_header(self, request):
# For this authentication method, users send a "Bearer" token via the
# Authorization header. API Gateway looks up that token in Cognito and
# sets the Geoadmin-Username and Geoadmin-Authenticated headers. In this
# module we only care about the Geoadmin-* headers. But when
# authentication fails with a 401 error we need to hint at the correct
# authentication method from the point of view of the user, which is the
# Authorization/Bearer scheme.
return 'Bearer'
32 changes: 32 additions & 0 deletions app/middleware/api_gateway_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from middleware import api_gateway

from django.conf import settings
from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.middleware import PersistentRemoteUserMiddleware


class ApiGatewayMiddleware(PersistentRemoteUserMiddleware):
"""Persist user authentication based on the API Gateway headers."""
header = api_gateway.REMOTE_USER_HEADER

def process_request(self, request):
if not settings.FEATURE_AUTH_ENABLE_APIGW:
return None

api_gateway.validate_username_header(request)
return super().process_request(request)


class ApiGatewayUserBackend(RemoteUserBackend):
""" This backend is to be used in conjunction with the ``ApiGatewayMiddleware`.
It is probably not needed to provide a custom remote user backend as our custom remote user
middleware will never call authenticate if the feature is not enabled. But better be safe than
sorry.
"""

def authenticate(self, request, remote_user):
if not settings.FEATURE_AUTH_ENABLE_APIGW:
return None

return super().authenticate(request, remote_user)
32 changes: 0 additions & 32 deletions app/middleware/apigw.py

This file was deleted.

22 changes: 11 additions & 11 deletions app/stac_api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from stac_api.models import BBOX_CH
from stac_api.models import Asset
from stac_api.models import AssetUpload
from stac_api.models import Collection
from stac_api.models import CollectionAsset
from stac_api.models import CollectionLink
from stac_api.models import Item
from stac_api.models import ItemLink
from stac_api.models import LandingPage
from stac_api.models import LandingPageLink
from stac_api.models import Provider
from stac_api.models.collection import Collection
from stac_api.models.collection import CollectionAsset
from stac_api.models.collection import CollectionLink
from stac_api.models.general import BBOX_CH
from stac_api.models.general import LandingPage
from stac_api.models.general import LandingPageLink
from stac_api.models.general import Provider
from stac_api.models.item import Asset
from stac_api.models.item import AssetUpload
from stac_api.models.item import Item
from stac_api.models.item import ItemLink
from stac_api.utils import build_asset_href
from stac_api.utils import get_query_params
from stac_api.validators import validate_href_url
Expand Down
2 changes: 1 addition & 1 deletion app/stac_api/management/commands/calculate_extent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.core.management.base import CommandParser
from django.db import connection

from stac_api.models import Collection
from stac_api.models.collection import Collection
from stac_api.utils import CommandHandler
from stac_api.utils import CustomBaseCommand

Expand Down
6 changes: 3 additions & 3 deletions app/stac_api/management/commands/dummy_asset_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from django.conf import settings
from django.core.management.base import BaseCommand

from stac_api.models import Asset
from stac_api.models import AssetUpload
from stac_api.models import BaseAssetUpload
from stac_api.models.general import BaseAssetUpload
from stac_api.models.item import Asset
from stac_api.models.item import AssetUpload
from stac_api.s3_multipart_upload import MultipartUpload
from stac_api.utils import AVAILABLE_S3_BUCKETS
from stac_api.utils import CommandHandler
Expand Down
6 changes: 3 additions & 3 deletions app/stac_api/management/commands/dummy_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.management.base import BaseCommand

from stac_api.models import Asset
from stac_api.models import Collection
from stac_api.models import Item
from stac_api.models.collection import Collection
from stac_api.models.item import Asset
from stac_api.models.item import Item
from stac_api.utils import CommandHandler
from stac_api.validators import MEDIA_TYPES

Expand Down
2 changes: 1 addition & 1 deletion app/stac_api/management/commands/list_asset_uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.core.management.base import BaseCommand
from django.core.serializers.json import DjangoJSONEncoder

from stac_api.models import AssetUpload
from stac_api.models.item import AssetUpload
from stac_api.s3_multipart_upload import MultipartUpload
from stac_api.serializers.upload import AssetUploadSerializer
from stac_api.utils import CommandHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from rest_framework.request import Request
from rest_framework.test import APIRequestFactory

from stac_api.models import Item
from stac_api.models.item import Item
from stac_api.utils import CommandHandler

logger = logging.getLogger(__name__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from rest_framework.test import APIRequestFactory

from stac_api.models import Item
from stac_api.models.item import Item
from stac_api.utils import CommandHandler

logger = logging.getLogger(__name__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from rest_framework.test import APIRequestFactory

from stac_api.models import Item
from stac_api.models.item import Item
from stac_api.utils import CommandHandler

logger = logging.getLogger(__name__)
Expand Down
6 changes: 3 additions & 3 deletions app/stac_api/management/commands/remove_expired_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from django.core.management.base import CommandParser
from django.utils import timezone

from stac_api.models import AssetUpload
from stac_api.models import BaseAssetUpload
from stac_api.models import Item
from stac_api.models.general import BaseAssetUpload
from stac_api.models.item import AssetUpload
from stac_api.models.item import Item
from stac_api.utils import CommandHandler
from stac_api.utils import CustomBaseCommand

Expand Down
4 changes: 2 additions & 2 deletions app/stac_api/management/commands/update_asset_file_size.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from django.core.management.base import BaseCommand
from django.core.management.base import CommandParser

from stac_api.models import Asset
from stac_api.models import CollectionAsset
from stac_api.models.collection import CollectionAsset
from stac_api.models.item import Asset
from stac_api.utils import CommandHandler
from stac_api.utils import get_s3_client
from stac_api.utils import select_s3_bucket
Expand Down
Loading

0 comments on commit fca3992

Please sign in to comment.