From 231df005bc3a4f62636bf24ea9954792043d0c84 Mon Sep 17 00:00:00 2001 From: Andy Doan Date: Mon, 3 Aug 2015 11:42:40 -0500 Subject: s3: publish api support The apiv3 takes advantage of the fact that S3 can generate temporary URLs that our API client (linaro-cp) can upload files through. It should help reduce the load on our l-l-p server. Change-Id: Ibd3c11b7151392c5db7f107c8a6dcd4acb10a6c1 --- license_protected_downloads/api/v3.py | 42 +++++++++++++++++ license_protected_downloads/tests/test_api_v3.py | 59 ++++++++++++++++++++++++ urls.py | 2 + 3 files changed, 103 insertions(+) diff --git a/license_protected_downloads/api/v3.py b/license_protected_downloads/api/v3.py index b84a446..24a0bc3 100644 --- a/license_protected_downloads/api/v3.py +++ b/license_protected_downloads/api/v3.py @@ -1,4 +1,46 @@ +from django.conf import settings +from django.http import HttpResponse +from django.views.decorators.csrf import csrf_exempt + +from license_protected_downloads.artifact.s3 import S3Artifact +from license_protected_downloads.api.v1 import ( + HttpResponseError, +) from license_protected_downloads.api import v2 +from license_protected_downloads.models import ( + APILog, +) + # no changes required for tokens in v3 token = v2.token + + +class PublishResource(v2.PublishResource): + def __init__(self, request, path): + super(PublishResource, self).__init__(request, path) + + def POST(self): + b = S3Artifact.get_bucket() + if not b: + raise HttpResponseError('S3 is not enabled', 403) + + k = b.new_key(settings.S3_PREFIX_PATH + self.path) + if k.exists(): + APILog.mark(self.request, 'FILE_OVERWRITE_DENIED') + raise HttpResponseError('File already exists', 403) + + headers = {} + mtype = self.request.POST.get('Content-Type', None) + if mtype: + headers['Content-Type'] = mtype + + resp = HttpResponse(status=201) + resp['Location'] = k.generate_url(60, method='PUT', headers=headers) + APILog.mark(self.request, 'FILE_UPLOAD', self.api_key) + return resp + + +@csrf_exempt +def publish(request, path): + return PublishResource(request, path).handle() diff --git a/license_protected_downloads/tests/test_api_v3.py b/license_protected_downloads/tests/test_api_v3.py index e3bb40c..9c7ca9c 100644 --- a/license_protected_downloads/tests/test_api_v3.py +++ b/license_protected_downloads/tests/test_api_v3.py @@ -1,13 +1,23 @@ +import unittest +import urllib2 import datetime import json +from django.conf import settings from django.test import Client, TestCase +from license_protected_downloads.artifact.s3 import S3Artifact from license_protected_downloads.models import ( APIKeyStore, APIToken, ) +import requests + + +_orig_s3_prefix = getattr(settings, 'S3_PREFIX_PATH', None) +_s3_enabled = _orig_s3_prefix is not None + class APIv3TokenTests(TestCase): '''A subset of token tests since this *should* just be v2 token code''' @@ -36,3 +46,52 @@ class APIv3TokenTests(TestCase): resp = self.client.post( '/api/v3/token/', data=data, HTTP_AUTHTOKEN=self.api_key.key) self.assertEqual(201, resp.status_code) + + +@unittest.skipIf(_s3_enabled is False, 's3 not configured') +class APIv3PublishTests(TestCase): + def setUp(self): + self.client = Client(enforce_csrf_checks=True) + self.api_key = APIKeyStore.objects.create(key='foo', public=True) + + settings.S3_PREFIX_PATH = settings.S3_PREFIX_PATH[:-1] + '-test-pub/' + self.bucket = S3Artifact.get_bucket() + + # make sure nothing was left from an old run + keys = self.bucket.list(settings.S3_PREFIX_PATH) + self.bucket.delete_keys(keys) + + def tearDown(self): + settings.S3_PREFIX_PATH = _orig_s3_prefix + + def _post_file(self, url, token, content, resp_code=201): + response = self.client.post(url, HTTP_AUTHTOKEN=token) + self.assertEqual(response.status_code, resp_code) + if resp_code == 201: + url = response['Location'] + r = requests.put(url, data=content) + self.assertEqual(200, r.status_code) + + def test_publish_simple(self): + content = 'foo bar' + token = APIToken.objects.create(key=self.api_key).token + self._post_file('/api/v3/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._post_file('/api/v3/publish/a/BUILD-INFO.txt', token, info) + + resp = self.client.get('/a/b') + self.assertEqual(302, resp.status_code) + resp = urllib2.urlopen(resp['Location']) + self.assertEqual(content, resp.read()) + + def test_publish_no_overwrite(self): + content = 'foo bar' + token = APIToken.objects.create(key=self.api_key).token + k = self.bucket.get_key(settings.S3_PREFIX_PATH + 'a', validate=False) + k.set_contents_from_string(content) + self._post_file('/api/v3/publish/a', token, 'wont overwrite', 403) diff --git a/urls.py b/urls.py index dfd14c9..14d818f 100644 --- a/urls.py +++ b/urls.py @@ -67,6 +67,8 @@ urlpatterns = patterns( url(r'^api/v3/token/(?P.*)$', 'license_protected_downloads.api.v3.token'), + url(r'^api/v3/publish/(?P.*)$', + 'license_protected_downloads.api.v3.publish'), # Catch-all. We always return a file (or try to) if it exists. # This handler does that. -- cgit v1.2.3