Raw assets API, path traversal checks

pull/78/head
Piero Toffanin 2017-01-18 14:49:53 -05:00
rodzic 5370254722
commit 7530515f25
5 zmienionych plików z 66 dodań i 20 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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')),
]

Wyświetl plik

@ -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">

Wyświetl plik

@ -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):