Skip to content
Open
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
106 changes: 0 additions & 106 deletions .github/workflows/build-and-publish.yml

This file was deleted.

41 changes: 41 additions & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Docker Image

on:
push:
branches:
- main
tags:
- v*
workflow_dispatch:

permissions:
contents: read
id-token: write

concurrency:
group: docker-${{ github.ref }}

jobs:
image:
uses: docker/github-builder/.github/workflows/build.yml@v1
with:
output: image
# Only refs that produce tags may push: a workflow_dispatch on any other
# branch would otherwise push untagged digests and then fail.
push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
set-meta-labels: true
meta-images: opendronemap/webui
# :edge on main; :<version> and :latest on a v* tag (latest=auto
# skips prereleases such as v3.7.0-rc1).
meta-tags: |
type=edge,branch=main
type=semver,pattern={{version}}
secrets:
registry-auths: |
- registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \
libpng \
libwebp \
tiff \
exiftool \
py3-shapely

# Install libexecinfo from Alpine 3.16 (removed in 3.17+)
Expand Down
18 changes: 9 additions & 9 deletions app/api/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

class JSONWebTokenAuthenticationQS(JWTAuthentication):
"""
JWT authentication that accepts tokens from query string parameter 'jwt'
instead of Authorization header
JWT authentication that accepts the token from the 'jwt' query string
parameter. Header authentication is handled by JWTAuthentication.
"""
def get_raw_token(self, request):
# Check query parameters first
token = request.query_params.get('jwt')
if token:
return token.encode('utf-8') if isinstance(token, str) else token
# Fall back to standard header-based authentication
return super().get_raw_token(request)
def authenticate(self, request):
raw_token = request.query_params.get('jwt')
if not raw_token:
return None

validated_token = self.get_validated_token(raw_token)
return self.get_user(validated_token), validated_token
74 changes: 0 additions & 74 deletions app/api/jwt.py

This file was deleted.

2 changes: 1 addition & 1 deletion app/api/processingnodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Meta:
fields = ['has_available_options', 'id', 'hostname', 'port', 'api_version', 'queue_count', 'max_images', 'label', 'engine', 'engine_version', ]

class ProcessingNodeViewSet(viewsets.ModelViewSet):
filter_class = ProcessingNodeFilter
filterset_class = ProcessingNodeFilter

pagination_class = None
serializer_class = ProcessingNodeSerializer
Expand Down
6 changes: 4 additions & 2 deletions app/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from django.db import transaction
from django.contrib.auth.models import User, Group
from django.contrib.postgres.search import SearchQuery, SearchVector
from django.db.models import Prefetch
from django.contrib.postgres.aggregates import StringAgg
from django.db.models import Q

Expand Down Expand Up @@ -103,9 +104,10 @@ class Meta:


class ProjectViewSet(viewsets.ModelViewSet):
filter_fields = ('id', 'name', 'description', 'created_at')
serializer_class = ProjectSerializer
queryset = models.Project.objects.prefetch_related('task_set').filter(deleting=False).order_by('-created_at')
queryset = models.Project.objects.prefetch_related(
Prefetch('task_set', queryset=models.Task.objects.order_by('-created_at'))
).filter(deleting=False).order_by('-created_at')
filterset_class = ProjectFilter
ordering_fields = '__all__'

Expand Down
27 changes: 21 additions & 6 deletions app/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .processingnodes import ProcessingNodeViewSet, ProcessingNodeOptionsView
from .admin import AdminUserViewSet, AdminGroupViewSet, AdminProfileViewSet
from rest_framework_nested import routers
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.views import TokenObtainSlidingView, TokenRefreshSlidingView
from .tiler import TileJson, Bounds, Metadata, Tiles, Export
from .potree import Scene, CameraView
from .workers import CheckTask, GetTaskResult, CancelTask
Expand Down Expand Up @@ -51,8 +51,21 @@
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/textured_model/$', TaskSafeTexturedModel.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/assets/(?P<unsafe_asset_path>.+)$', TaskAssets.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/import$', TaskAssetsImport.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/import/external/init$', TaskExternalImportInit.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/import/external/upload$', TaskExternalImportUpload.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/import/external/commit$', TaskExternalImportCommit.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/thumbnail$', TaskThumbnail.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/backup$', TaskBackup.as_view()),

re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media\.geojson$', TaskMediaGeoJSON.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media/$', TaskMediaList.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media/upload$', TaskMediaUpload.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media/download/(?P<filename>.+)$', TaskMediaDownload.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media/thumbnail/(?P<filename>.+)$', TaskMediaThumbnail.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media/video/(?P<filename>[^/]+)/flightpath\.geojson$', TaskVideoFlightPath.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media/manage/(?P<filename>.+)$', TaskMediaManage.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/media/panorama/(?P<filename>[^/]+)/tiles/(?P<level>\d+)/(?P<face>\w)/(?P<row>\d+)/(?P<col>\d+)\.jpg$', TaskPanoramaTiles.as_view()),

re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/images/thumbnail/(?P<image_filename>.+)$', Thumbnail.as_view()),
re_path(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/images/download/(?P<image_filename>.+)$', ImageDownload.as_view()),

Expand All @@ -61,16 +74,18 @@

re_path(r'workers/check/(?P<celery_task_id>.+)', CheckTask.as_view()),
re_path(r'workers/get/(?P<celery_task_id>.+)', GetTaskResult.as_view()),
re_path(r'workers/cancel/(?P<celery_task_id>.+)', CancelTask.as_view()),

path('auth/', include('rest_framework.urls')),
path('token-auth/', TokenObtainPairView.as_view()),
re_path(r'^token-auth/refresh/?$', TokenRefreshSlidingView.as_view()),
re_path(r'^token-auth/?$', TokenObtainSlidingView.as_view()),

re_path(r'^plugins/(?P<plugin_name>[^/.]+)/(.*)$', api_view_handler),
]

if settings.ENABLE_USERS_API:
urlpatterns.append(re_path(r'users', UsersList.as_view()))
path('user/profile/', UsersProfile.as_view()),
re_path(r'^users/?$', UsersList.as_view()),
re_path(r'^groups/?$', GroupsList.as_view()),
]

if settings.EXTERNAL_AUTH_ENDPOINT != '':
urlpatterns.append(path('external-token-auth/', ExternalTokenAuth.as_view()))

2 changes: 1 addition & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ def read_backup_file(self):
self.name = backup.get('name', self.name)
self.processing_time = backup.get('processing_time', self.processing_time)
self.options = backup.get('options', self.options)
self.created_at = datetime.fromtimestamp(backup.get('created_at', self.created_at.astimezone(timezone.utc).timestamp()), tz=timezone.utc)
self.created_at = datetime.fromtimestamp(backup.get('created_at', self.created_at.astimezone(tz.utc).timestamp()), tz=tz.utc)
self.public = backup.get('public', self.public)
self.resize_to = backup.get('resize_to', self.resize_to)
self.potree_scene = backup.get('potree_scene', self.potree_scene)
Expand Down
8 changes: 6 additions & 2 deletions app/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ def test_token_auth(self):
'username': 'testuser',
'password': 'wrongpwd'
})
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)

# Can generate token with valid credentials
res = client.post('/api/token-auth/', {
Expand All @@ -523,7 +523,7 @@ def test_token_auth(self):
})
self.assertEqual(res.status_code, status.HTTP_200_OK)

token = res.data['access']
token = res.data['token']
self.assertTrue(len(token) > 0)

# Can access resources by passing token via querystring
Expand All @@ -534,3 +534,7 @@ def test_token_auth(self):
client = APIClient(HTTP_AUTHORIZATION="Bearer {0}".format(token))
res = client.get('/api/processingnodes/')
self.assertEqual(res.status_code, status.HTTP_200_OK)

client = APIClient(HTTP_AUTHORIZATION="JWT {0}".format(token))
res = client.get('/api/processingnodes/')
self.assertEqual(res.status_code, status.HTTP_200_OK)
Loading
Loading