diff options
author | Andy Doan <andy.doan@linaro.org> | 2014-09-04 17:18:23 -0500 |
---|---|---|
committer | Andy Doan <andy.doan@linaro.org> | 2014-09-17 14:23:51 -0500 |
commit | 0bdb00d0551e7f0c2850097f54f9c633befb0ab8 (patch) | |
tree | e02e966044216264704282bf247176153c1ee3a4 | |
parent | fb2e01c1cf500c8f52add181ed807b8fa15cf456 (diff) |
apiv2: create api to publish files
Change-Id: Ieaf1ffff87734fcc4b8c585e631a5304ec686e9a
-rw-r--r-- | license_protected_downloads/api/v1.py | 29 | ||||
-rw-r--r-- | license_protected_downloads/api/v2.py | 37 | ||||
-rw-r--r-- | license_protected_downloads/tests/test_api_v2.py | 68 | ||||
-rw-r--r-- | urls.py | 3 |
4 files changed, 123 insertions, 14 deletions
diff --git a/license_protected_downloads/api/v1.py b/license_protected_downloads/api/v1.py index 139cc9e..f606249 100644 --- a/license_protected_downloads/api/v1.py +++ b/license_protected_downloads/api/v1.py @@ -36,6 +36,21 @@ def upload_target_path(path, key, public): return safe_path_join(base_path, path) +def do_upload(infd, path, api_key): + path = upload_target_path( + path, api_key.key, public=api_key.public) + + # Create directory if required + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + + with open(path, "wb") as destination: + for chunk in infd.chunks(): + destination.write(chunk) + + + @csrf_exempt def file_server_post(request, path): """ Handle post requests. @@ -58,19 +73,7 @@ def file_server_post(request, path): return HttpResponseServerError("Invalid call") APILog.mark(request, 'FILE_UPLOAD', api_key) - - path = upload_target_path( - path, request.POST["key"], public=api_key.public) - - # Create directory if required - dirname = os.path.dirname(path) - if not os.path.isdir(dirname): - os.makedirs(dirname) - - with open(path, "wb") as destination: - for chunk in request.FILES["file"].chunks(): - destination.write(chunk) - + do_upload(request.FILES['file'], path, api_key) return HttpResponse("OK") diff --git a/license_protected_downloads/api/v2.py b/license_protected_downloads/api/v2.py index 397b9b0..35fe19d 100644 --- a/license_protected_downloads/api/v2.py +++ b/license_protected_downloads/api/v2.py @@ -5,6 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt +from license_protected_downloads.api.v1 import do_upload from license_protected_downloads.models import ( APIKeyStore, APILog, @@ -97,3 +98,39 @@ class TokenResource(RestResource): @csrf_exempt def token(request, token): return TokenResource(request, token).handle() + + +class PublishResource(RestResource): + def __init__(self, request, path): + super(PublishResource, self).__init__(request) + self.path = path + + def authenticate(self): + if 'HTTP_AUTHTOKEN' not in self.request.META: + APILog.mark(self.request, 'INVALID_KEY_MISSING') + raise RestException('Missing api token', 401) + try: + token = APIToken.objects.get( + token=self.request.META['HTTP_AUTHTOKEN']) + if not token.valid_request(self.request): + raise RestException('Token no longer valid', 401) + self.api_key = token.key + except APIToken.DoesNotExist: + APILog.mark(self.request, 'INVALID_KEY') + raise RestException('Invalid api token', 401) + + def POST(self): + if 'file' not in self.request.FILES or not self.path: + APILog.mark(self.request, 'INVALID_ARGUMENTS', self.api_key) + raise RestException('Invalid Arguments', 401) + + APILog.mark(self.request, 'FILE_UPLOAD', self.api_key) + do_upload(self.request.FILES['file'], self.path, self.api_key) + resp = HttpResponse(status=201) + resp['Location'] = self.path + return resp + + +@csrf_exempt +def publish(request, path): + return PublishResource(request, path).handle() diff --git a/license_protected_downloads/tests/test_api_v2.py b/license_protected_downloads/tests/test_api_v2.py index 942b9a7..12acf2f 100644 --- a/license_protected_downloads/tests/test_api_v2.py +++ b/license_protected_downloads/tests/test_api_v2.py @@ -1,5 +1,10 @@ import datetime import json +import shutil +import tempfile +import StringIO + +import mock from django.test import Client, TestCase @@ -12,7 +17,14 @@ from license_protected_downloads.models import ( class APIv2Tests(TestCase): def setUp(self): self.client = Client(enforce_csrf_checks=True) - self.api_key = APIKeyStore.objects.create(key='foo') + self.api_key = APIKeyStore.objects.create(key='foo', public=True) + + path = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, path) + m = mock.patch('django.conf.settings.SERVED_PATHS', + new_callable=lambda: [path]) + self.addCleanup(m.stop) + m.start() def test_token_no_auth(self): resp = self.client.get('/api/v2/token/') @@ -64,3 +76,57 @@ class APIv2Tests(TestCase): resp = self.client.get( resp['Location'], HTTP_AUTHTOKEN=self.api_key.key) self.assertEqual(200, resp.status_code) + + def test_publish_no_token(self): + resp = self.client.post('/api/v2/publish/a') + self.assertEqual(401, resp.status_code) + + def test_publish_bad_token(self): + resp = self.client.post('/api/v2/publish/a', HTTP_AUTHTOKEN='bad') + self.assertEqual(401, resp.status_code) + + def test_publish_expired_token(self): + expires = datetime.datetime.now() - datetime.timedelta(minutes=1) + token = APIToken.objects.create(key=self.api_key, expires=expires) + resp = self.client.post( + '/api/v2/publish/a', HTTP_AUTHTOKEN=token.token) + self.assertEqual(401, resp.status_code) + + def _send_file(self, url, token, content, resp_code=201): + f = StringIO.StringIO(content) + f.name = 'name' # to fool django's client.post + response = self.client.post( + url, HTTP_AUTHTOKEN=token, data={"file": f}) + self.assertEqual(response.status_code, resp_code) + + def test_publish_simple(self): + content = 'foo bar' + token = APIToken.objects.create(key=self.api_key).token + self._send_file('/api/v2/publish/a', token, content) + + # we'll be missing a build-info so: + self.assertEqual(403, self.client.get('/a').status_code) + + # add a build-info and see if the file works + info = 'Format-Version: 0.5\n\nFiles-Pattern: *\nLicense-Type: open\n' + self._send_file('/api/v2/publish/BUILD-INFO.txt', token, info) + + resp = self.client.get('/a') + self.assertEqual(200, resp.status_code) + self.assertEqual(content, open(resp['X-Sendfile']).read()) + + def test_publish_with_dirs(self): + content = 'foo bar' + token = APIToken.objects.create(key=self.api_key).token + self._send_file('/api/v2/publish/a/b', token, content) + + # we'll be missing a build-info so: + self.assertEqual(403, self.client.get('/a/b').status_code) + + # add a build-info and see if the file works + info = 'Format-Version: 0.5\n\nFiles-Pattern: *\nLicense-Type: open\n' + self._send_file('/api/v2/publish/a/BUILD-INFO.txt', token, info) + + resp = self.client.get('/a/b') + self.assertEqual(200, resp.status_code) + self.assertEqual(content, open(resp['X-Sendfile']).read()) @@ -56,6 +56,9 @@ urlpatterns = patterns( url(r'^api/v2/token/(?P<token>.*)$', 'license_protected_downloads.api.v2.token'), + url(r'^api/v2/publish/(?P<path>.*)$', + 'license_protected_downloads.api.v2.publish'), + # Catch-all. We always return a file (or try to) if it exists. # This handler does that. url(r'(?P<path>.*)', 'license_protected_downloads.views.file_server'), |