kopia lustrzana https://github.com/OpenDroneMap/WebODM
Raw assets API, path traversal checks
rodzic
5370254722
commit
7530515f25
|
@ -1,5 +1,6 @@
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.exceptions import ObjectDoesNotExist, SuspiciousFileOperation
|
||||
from rest_framework import exceptions
|
||||
import os
|
||||
|
||||
from app import models
|
||||
|
||||
|
@ -41,4 +42,14 @@ def get_tile_json(name, tiles, bounds):
|
|||
'minzoom': 0,
|
||||
'maxzoom': 22,
|
||||
'bounds': bounds
|
||||
}
|
||||
}
|
||||
|
||||
def path_traversal_check(unsafe_path, known_safe_path):
|
||||
known_safe_path = os.path.abspath(known_safe_path)
|
||||
unsafe_path = os.path.abspath(unsafe_path)
|
||||
|
||||
if (os.path.commonprefix([known_safe_path, unsafe_path]) != known_safe_path):
|
||||
raise SuspiciousFileOperation("{} is not safe".format(unsafe_path))
|
||||
|
||||
# Passes the check
|
||||
return unsafe_path
|
|
@ -3,7 +3,7 @@ import os
|
|||
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
from django.contrib.gis.db.models.functions import Envelope
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.exceptions import ObjectDoesNotExist, SuspiciousFileOperation
|
||||
from django.db.models.functions import Cast
|
||||
from django.http import HttpResponse
|
||||
from wsgiref.util import FileWrapper
|
||||
|
@ -11,7 +11,7 @@ from rest_framework import status, serializers, viewsets, filters, exceptions, p
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.views import APIView
|
||||
from .common import get_and_check_project, get_tile_json
|
||||
from .common import get_and_check_project, get_tile_json, path_traversal_check
|
||||
|
||||
from app import models, scheduler, pending_actions
|
||||
from nodeodm.models import ProcessingNode
|
||||
|
@ -188,7 +188,11 @@ class TaskTilesJson(TaskNestedView):
|
|||
return Response(json)
|
||||
|
||||
|
||||
class TaskAssets(TaskNestedView):
|
||||
"""
|
||||
Task downloads are simply aliases to download the task's assets
|
||||
(but require a shorter path and look nicer the API user)
|
||||
"""
|
||||
class TaskDownloads(TaskNestedView):
|
||||
def get(self, request, pk=None, project_pk=None, asset=""):
|
||||
"""
|
||||
Downloads a task asset (if available)
|
||||
|
@ -217,4 +221,32 @@ class TaskAssets(TaskNestedView):
|
|||
response['Content-Disposition'] = "attachment; filename={}".format(asset_filename)
|
||||
return response
|
||||
else:
|
||||
raise exceptions.NotFound()
|
||||
raise exceptions.NotFound()
|
||||
|
||||
"""
|
||||
Raw access to the task's asset folder resources
|
||||
Useful when accessing a textured 3d model, or the Potree point cloud data
|
||||
"""
|
||||
class TaskAssets(TaskNestedView):
|
||||
def get(self, request, pk=None, project_pk=None, unsafe_asset_path=""):
|
||||
"""
|
||||
Downloads a task asset (if available)
|
||||
"""
|
||||
task = self.get_and_check_task(request, pk, project_pk)
|
||||
|
||||
# Check for directory traversal attacks
|
||||
try:
|
||||
asset_path = path_traversal_check(task.assets_path(unsafe_asset_path), task.assets_path(""))
|
||||
except SuspiciousFileOperation:
|
||||
raise exceptions.NotFound("Asset does not exist")
|
||||
|
||||
if (not os.path.exists(asset_path)) or os.path.isdir(asset_path):
|
||||
raise exceptions.NotFound("Asset does not exist")
|
||||
|
||||
asset_filename = os.path.basename(asset_path)
|
||||
|
||||
file = open(asset_path, "rb")
|
||||
response = HttpResponse(FileWrapper(file),
|
||||
content_type=(mimetypes.guess_type(asset_filename)[0] or "application/zip"))
|
||||
response['Content-Disposition'] = "inline; filename={}".format(asset_filename)
|
||||
return response
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.conf.urls import url, include
|
||||
from .projects import ProjectViewSet
|
||||
from .tasks import TaskViewSet, TaskTiles, TaskTilesJson, TaskAssets
|
||||
from .tasks import TaskViewSet, TaskTiles, TaskTilesJson, TaskDownloads, TaskAssets
|
||||
from .processingnodes import ProcessingNodeViewSet
|
||||
from rest_framework_nested import routers
|
||||
|
||||
|
@ -17,7 +17,8 @@ urlpatterns = [
|
|||
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/tiles/(?P<z>[\d]+)/(?P<x>[\d]+)/(?P<y>[\d]+)\.png$', TaskTiles.as_view()),
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/tiles\.json$', TaskTilesJson.as_view()),
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/download/(?P<asset>[^/.]+)/$', TaskAssets.as_view()),
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/download/(?P<asset>[^/.]+)/$', TaskDownloads.as_view()),
|
||||
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/assets/(?P<unsafe_asset_path>.+)$', TaskAssets.as_view()),
|
||||
|
||||
url(r'^auth/', include('rest_framework.urls')),
|
||||
]
|
|
@ -32,8 +32,6 @@ class ModelView extends React.Component {
|
|||
var container = this.container;
|
||||
|
||||
var sceneProperties = {
|
||||
// path: "/static/app/test/lion_takanawa/cloud.js",
|
||||
// path: "/static/app/test/conv/cloud.js",
|
||||
path: "/static/app/test/brighton/cloud.js",
|
||||
cameraPosition: null,
|
||||
cameraTarget: null,
|
||||
|
@ -446,9 +444,9 @@ class ModelView extends React.Component {
|
|||
);
|
||||
bg.material.depthTest = false;
|
||||
bg.material.depthWrite = false;
|
||||
sceneBG.add(bg);
|
||||
sceneBG.add(bg);
|
||||
|
||||
window.addEventListener( 'keydown', onKeyDown, false );
|
||||
window.addEventListener( 'keydown', this.onKeyDown, false );
|
||||
|
||||
directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
|
||||
directionalLight.position.set( 10, 10, 10 );
|
||||
|
@ -566,9 +564,7 @@ class ModelView extends React.Component {
|
|||
referenceFrame.updateMatrixWorld(true);
|
||||
}
|
||||
|
||||
function onKeyDown(event){
|
||||
//console.log(event.keyCode);
|
||||
|
||||
this.onKeyDown = function(event){
|
||||
if(event.keyCode === 69){
|
||||
// e pressed
|
||||
|
||||
|
@ -776,10 +772,12 @@ class ModelView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
window.addEventListener("resize", function(){
|
||||
this.onResize = function(){
|
||||
width = container.clientWidth;
|
||||
height = container.clientHeight;
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("resize", this.onResize);
|
||||
|
||||
var PotreeRenderer = function(){
|
||||
this.render = function(){
|
||||
|
@ -1010,12 +1008,15 @@ class ModelView extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
initThree();
|
||||
loop();
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("resize", this.onResize);
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
}
|
||||
|
||||
// React render
|
||||
render(){
|
||||
return (<div className="model-view">
|
||||
|
|
|
@ -197,7 +197,8 @@ class TestApi(BootTestCase):
|
|||
# - task creation via file upload
|
||||
# - scheduler processing steps
|
||||
# - tiles API urls (permissions, 404s)
|
||||
# - assets download
|
||||
# - assets download (aliases)
|
||||
# - assets raw downloads
|
||||
# - project deletion
|
||||
|
||||
def test_processingnodes(self):
|
||||
|
|
Ładowanie…
Reference in New Issue