Merge lp:~dooferlad/linaro-license-protection/public_push_support into lp:~linaro-automation/linaro-license-protection/trunk

Proposed by James Tunnicliffe
Status: Merged
Approved by: Milo Casagrande
Approved revision: 204
Merged at revision: 209
Proposed branch: lp:~dooferlad/linaro-license-protection/public_push_support
Merge into: lp:~linaro-automation/linaro-license-protection/trunk
Diff against target: 210 lines (+90/-17)
4 files modified
license_protected_downloads/models.py (+1/-0)
license_protected_downloads/tests/test_views.py (+53/-0)
license_protected_downloads/uploads.py (+18/-4)
license_protected_downloads/views.py (+18/-13)
To merge this branch: bzr merge lp:~dooferlad/linaro-license-protection/public_push_support
Reviewer Review Type Date Requested Status
Milo Casagrande (community) Approve
Review via email: mp+175319@code.launchpad.net

Description of the change

* Tidy up a few bits from the old publishing code (so the old API should function against upload paths - untested)
* Should work for public publishing - when getting a key, have &public=true on the end of the URL.
* Haven't updated the docs, just found that explaining a proposed change took more effort than writing it!

To post a comment you must log in.
Revision history for this message
Milo Casagrande (milo) wrote :
Download full text (3.2 KiB)

Hi James,

overall it looks good, a couple of suggestion follows.

On Wed, Jul 17, 2013 at 5:29 PM, James Tunnicliffe
<email address hidden> wrote:
>
> + def test_get_public_key_post_and_get_file(self):
> + response = self.client.get("http://testserver/api/request_key",
> + data={"key": settings.MASTER_API_KEY,
> + "public": ""})
> +
> + self.assertEqual(response.status_code, 200)
> + # Don't care what the key is, as long as it isn't blank
> + self.assertRegexpMatches(response.content, "\S+")
> + key = response.content
> +
> + # Now write a file so we can upload it
> + file_content = "test_get_key_post_and_get_file"
> + file_root = "/tmp"
> +
> + tmp_file_name = os.path.join(
> + file_root,
> + self.make_temporary_file(file_content))
> +
> + buildinfo_content = "\n".join([
> + "Format-Version: 0.1",
> + "Files-Pattern: *",
> + "Build-Name: test",
> + "License-Type: open"])
> + tmp_build_info = os.path.join(
> + file_root,
> + self.make_temporary_file(buildinfo_content))
> +
> + # Send the files
> + with open(tmp_file_name) as f:
> + response = self.client.post("http://testserver/pub/file_name",
> + data={"key": key, "file": f})
> + self.assertEqual(response.status_code, 200)
> +
> + with open(tmp_build_info) as f:
> + response = self.client.post("http://testserver/pub/BUILD-INFO.txt",
> + data={"key": key, "file": f})
> + self.assertEqual(response.status_code, 200)
> +
> + # Check the upload worked by reading the file back from its uploaded
> + # location
> + uploaded_file_path = os.path.join(settings.SERVED_PATHS[0],
> + "file_name")
> +
> + with open(uploaded_file_path) as f:
> + self.assertEqual(f.read(), file_content)
> +
> + # Test we can fetch the newly uploaded file
> + response = self.client.get("http://testserver/pub/file_name")
> + self.assertEqual(response.status_code, 200)
> +
> + # Delete the files generated by the test
> + shutil.rmtree(os.path.join(settings.SERVED_PATHS[0], "pub"))

Wouldn't it be better to have all the logic of this test method inside
a try-finally?
I'm concerned with an error that can happen, for whatever reason,
somewhere before the call to shutil.rmtree, leaving cruft on the file
system.

> === modified file 'license_protected_downloads/uploads.py'
>
>
> @@ -41,11 +44,14 @@
> APIKeyStore.objects.filter(key=request.POST["key"])):
> return HttpResponseServerError("Invalid key")
>
> + api_key = APIKeyStore.objects.filter(key=request.POST["key"])
> +

Does the call to APIKeyStore.objects.filter get cached in some way?
The problem is that the other solution I have in mind means to always
call APIKeyStore.objects.filter even if "key in request.POST" is
False.

--
Milo Casagrande | Automation Engineer
Linaro.org <www.li...

Read more...

Revision history for this message
Milo Casagrande (milo) wrote :

Needs fixing, at least for the try-finally as said above.

review: Needs Fixing
Revision history for this message
James Tunnicliffe (dooferlad) wrote :
Download full text (3.5 KiB)

On 22 July 2013 16:04, Milo Casagrande <email address hidden> wrote:
> Hi James,
>
> overall it looks good, a couple of suggestion follows.
>
> On Wed, Jul 17, 2013 at 5:29 PM, James Tunnicliffe
> <email address hidden> wrote:
>>
>> + def test_get_public_key_post_and_get_file(self):
>> + response = self.client.get("http://testserver/api/request_key",
>> + data={"key": settings.MASTER_API_KEY,
>> + "public": ""})
>> +
>> + self.assertEqual(response.status_code, 200)
>> + # Don't care what the key is, as long as it isn't blank
>> + self.assertRegexpMatches(response.content, "\S+")
>> + key = response.content
>> +
>> + # Now write a file so we can upload it
>> + file_content = "test_get_key_post_and_get_file"
>> + file_root = "/tmp"
>> +
>> + tmp_file_name = os.path.join(
>> + file_root,
>> + self.make_temporary_file(file_content))
>> +
>> + buildinfo_content = "\n".join([
>> + "Format-Version: 0.1",
>> + "Files-Pattern: *",
>> + "Build-Name: test",
>> + "License-Type: open"])
>> + tmp_build_info = os.path.join(
>> + file_root,
>> + self.make_temporary_file(buildinfo_content))
>> +
>> + # Send the files
>> + with open(tmp_file_name) as f:
>> + response = self.client.post("http://testserver/pub/file_name",
>> + data={"key": key, "file": f})
>> + self.assertEqual(response.status_code, 200)
>> +
>> + with open(tmp_build_info) as f:
>> + response = self.client.post("http://testserver/pub/BUILD-INFO.txt",
>> + data={"key": key, "file": f})
>> + self.assertEqual(response.status_code, 200)
>> +
>> + # Check the upload worked by reading the file back from its uploaded
>> + # location
>> + uploaded_file_path = os.path.join(settings.SERVED_PATHS[0],
>> + "file_name")
>> +
>> + with open(uploaded_file_path) as f:
>> + self.assertEqual(f.read(), file_content)
>> +
>> + # Test we can fetch the newly uploaded file
>> + response = self.client.get("http://testserver/pub/file_name")
>> + self.assertEqual(response.status_code, 200)
>> +
>> + # Delete the files generated by the test
>> + shutil.rmtree(os.path.join(settings.SERVED_PATHS[0], "pub"))
>
>
> Wouldn't it be better to have all the logic of this test method inside
> a try-finally?
> I'm concerned with an error that can happen, for whatever reason,
> somewhere before the call to shutil.rmtree, leaving cruft on the file
> system.

Good point. Will clean that up.

>> === modified file 'license_protected_downloads/uploads.py'
>>
>>
>> @@ -41,11 +44,14 @@
>> APIKeyStore.objects.filter(key=request.POST["key"])):
>> return HttpResponseServerError("Invalid key")
>>
>> + api_key = APIKeyStore.objects.filter(key=request.POST["key"])
>> +
>
> Does the call to APIKeyStore.objects.filter get cached in some way?
> The pro...

Read more...

Revision history for this message
James Tunnicliffe (dooferlad) wrote :
Download full text (3.8 KiB)

Actually I should put the tests in a new class with a tearDown that
removes extra directories - it is called even if there is an
exception.

On 22 July 2013 16:31, James Tunnicliffe <email address hidden> wrote:
> On 22 July 2013 16:04, Milo Casagrande <email address hidden> wrote:
>> Hi James,
>>
>> overall it looks good, a couple of suggestion follows.
>>
>> On Wed, Jul 17, 2013 at 5:29 PM, James Tunnicliffe
>> <email address hidden> wrote:
>>>
>>> + def test_get_public_key_post_and_get_file(self):
>>> + response = self.client.get("http://testserver/api/request_key",
>>> + data={"key": settings.MASTER_API_KEY,
>>> + "public": ""})
>>> +
>>> + self.assertEqual(response.status_code, 200)
>>> + # Don't care what the key is, as long as it isn't blank
>>> + self.assertRegexpMatches(response.content, "\S+")
>>> + key = response.content
>>> +
>>> + # Now write a file so we can upload it
>>> + file_content = "test_get_key_post_and_get_file"
>>> + file_root = "/tmp"
>>> +
>>> + tmp_file_name = os.path.join(
>>> + file_root,
>>> + self.make_temporary_file(file_content))
>>> +
>>> + buildinfo_content = "\n".join([
>>> + "Format-Version: 0.1",
>>> + "Files-Pattern: *",
>>> + "Build-Name: test",
>>> + "License-Type: open"])
>>> + tmp_build_info = os.path.join(
>>> + file_root,
>>> + self.make_temporary_file(buildinfo_content))
>>> +
>>> + # Send the files
>>> + with open(tmp_file_name) as f:
>>> + response = self.client.post("http://testserver/pub/file_name",
>>> + data={"key": key, "file": f})
>>> + self.assertEqual(response.status_code, 200)
>>> +
>>> + with open(tmp_build_info) as f:
>>> + response = self.client.post("http://testserver/pub/BUILD-INFO.txt",
>>> + data={"key": key, "file": f})
>>> + self.assertEqual(response.status_code, 200)
>>> +
>>> + # Check the upload worked by reading the file back from its uploaded
>>> + # location
>>> + uploaded_file_path = os.path.join(settings.SERVED_PATHS[0],
>>> + "file_name")
>>> +
>>> + with open(uploaded_file_path) as f:
>>> + self.assertEqual(f.read(), file_content)
>>> +
>>> + # Test we can fetch the newly uploaded file
>>> + response = self.client.get("http://testserver/pub/file_name")
>>> + self.assertEqual(response.status_code, 200)
>>> +
>>> + # Delete the files generated by the test
>>> + shutil.rmtree(os.path.join(settings.SERVED_PATHS[0], "pub"))
>>
>>
>> Wouldn't it be better to have all the logic of this test method inside
>> a try-finally?
>> I'm concerned with an error that can happen, for whatever reason,
>> somewhere before the call to shutil.rmtree, leaving cruft on the file
>> system.
>
> Good point. Will clean that up.
>
>>> === modified file 'license_protected_downloads/uploads.py'
>>>
>>>
>>> @@ -41,...

Read more...

Revision history for this message
Milo Casagrande (milo) wrote :

On Mon, Jul 22, 2013 at 5:34 PM, James Tunnicliffe
<email address hidden> wrote:
>> Does the call to APIKeyStore.objects.filter get cached in some way?
>> The problem is that the other solution I have in mind means to always
>> call APIKeyStore.objects.filter even if "key in request.POST" is
>> False.
>
> You couldn't make the call - if request.POST["key"] didn't exist, it
> would raise an exception. If you had no filter for the key value, the
> filter would match everything, which would be a bad thing.

Righto, it is the same key.

On Mon, Jul 22, 2013 at 5:37 PM, James Tunnicliffe
<email address hidden> wrote:
> Actually I should put the tests in a new class with a tearDown that
> removes extra directories - it is called even if there is an
> exception.

Even better!

--
Milo Casagrande | Automation Engineer
Linaro.org <www.linaro.org> │ Open source software for ARM SoCs

Revision history for this message
Milo Casagrande (milo) wrote :

Spoke with James about this merge proposal: I will merge it and fix test issue discovered.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'license_protected_downloads/models.py'
--- license_protected_downloads/models.py 2013-06-05 13:16:41 +0000
+++ license_protected_downloads/models.py 2013-07-17 15:28:26 +0000
@@ -30,3 +30,4 @@
3030
31class APIKeyStore(models.Model):31class APIKeyStore(models.Model):
32 key = models.CharField(max_length=80)32 key = models.CharField(max_length=80)
33 public = models.BooleanField()
3334
=== modified file 'license_protected_downloads/tests/test_views.py'
--- license_protected_downloads/tests/test_views.py 2013-06-14 12:39:29 +0000
+++ license_protected_downloads/tests/test_views.py 2013-07-17 15:28:26 +0000
@@ -907,6 +907,59 @@
907 # Delete the files generated by the test907 # Delete the files generated by the test
908 shutil.rmtree(os.path.join(settings.UPLOAD_PATH, key))908 shutil.rmtree(os.path.join(settings.UPLOAD_PATH, key))
909909
910 def test_get_public_key_post_and_get_file(self):
911 response = self.client.get("http://testserver/api/request_key",
912 data={"key": settings.MASTER_API_KEY,
913 "public": ""})
914
915 self.assertEqual(response.status_code, 200)
916 # Don't care what the key is, as long as it isn't blank
917 self.assertRegexpMatches(response.content, "\S+")
918 key = response.content
919
920 # Now write a file so we can upload it
921 file_content = "test_get_key_post_and_get_file"
922 file_root = "/tmp"
923
924 tmp_file_name = os.path.join(
925 file_root,
926 self.make_temporary_file(file_content))
927
928 buildinfo_content = "\n".join([
929 "Format-Version: 0.1",
930 "Files-Pattern: *",
931 "Build-Name: test",
932 "License-Type: open"])
933 tmp_build_info = os.path.join(
934 file_root,
935 self.make_temporary_file(buildinfo_content))
936
937 # Send the files
938 with open(tmp_file_name) as f:
939 response = self.client.post("http://testserver/pub/file_name",
940 data={"key": key, "file": f})
941 self.assertEqual(response.status_code, 200)
942
943 with open(tmp_build_info) as f:
944 response = self.client.post("http://testserver/pub/BUILD-INFO.txt",
945 data={"key": key, "file": f})
946 self.assertEqual(response.status_code, 200)
947
948 # Check the upload worked by reading the file back from its uploaded
949 # location
950 uploaded_file_path = os.path.join(settings.SERVED_PATHS[0],
951 "file_name")
952
953 with open(uploaded_file_path) as f:
954 self.assertEqual(f.read(), file_content)
955
956 # Test we can fetch the newly uploaded file
957 response = self.client.get("http://testserver/pub/file_name")
958 self.assertEqual(response.status_code, 200)
959
960 # Delete the files generated by the test
961 shutil.rmtree(os.path.join(settings.SERVED_PATHS[0], "pub"))
962
910 def test_post_file_no_key(self):963 def test_post_file_no_key(self):
911 file_content = "test_post_file_no_key"964 file_content = "test_post_file_no_key"
912 file_root = "/tmp"965 file_root = "/tmp"
913966
=== modified file 'license_protected_downloads/uploads.py'
--- license_protected_downloads/uploads.py 2013-06-11 15:03:01 +0000
+++ license_protected_downloads/uploads.py 2013-07-17 15:28:26 +0000
@@ -17,13 +17,16 @@
17 file = forms.FileField()17 file = forms.FileField()
1818
1919
20def upload_target_path(path, key):20def upload_target_path(path, key, public):
21 """Quick path handling function.21 """Quick path handling function.
2222
23 Checks that the generated path doesn't end up outside the target directory,23 Checks that the generated path doesn't end up outside the target directory,
24 so you can't set path to start with "/" and upload to anywhere.24 so you can't set path to start with "/" and upload to anywhere.
25 """25 """
26 base_path = os.path.join(settings.UPLOAD_PATH, key)26 if public:
27 base_path = os.path.join(settings.SERVED_PATHS[0])
28 else:
29 base_path = os.path.join(settings.UPLOAD_PATH, key)
27 return safe_path_join(base_path, path)30 return safe_path_join(base_path, path)
2831
2932
@@ -41,11 +44,14 @@
41 APIKeyStore.objects.filter(key=request.POST["key"])):44 APIKeyStore.objects.filter(key=request.POST["key"])):
42 return HttpResponseServerError("Invalid key")45 return HttpResponseServerError("Invalid key")
4346
47 api_key = APIKeyStore.objects.filter(key=request.POST["key"])
48
44 form = UploadFileForm(request.POST, request.FILES)49 form = UploadFileForm(request.POST, request.FILES)
45 if not form.is_valid() or not path:50 if not form.is_valid() or not path:
46 return HttpResponseServerError("Invalid call")51 return HttpResponseServerError("Invalid call")
4752
48 path = upload_target_path(path, request.POST["key"])53 path = upload_target_path(
54 path, request.POST["key"], public=api_key[0].public)
4955
50 # Create directory if required56 # Create directory if required
51 dirname = os.path.dirname(path)57 dirname = os.path.dirname(path)
@@ -69,7 +75,15 @@
69 while APIKeyStore.objects.filter(key=key):75 while APIKeyStore.objects.filter(key=key):
70 key = "%030x" % random.randrange(256**15)76 key = "%030x" % random.randrange(256**15)
7177
72 api_key = APIKeyStore(key=key)78 # Look for a hint of sanity in the value given to public, but don't
79 # care about it too much.
80 yes = ["", "y", "yes", "true", "1"]
81 if "public" in request.GET:
82 public = request.GET["public"].lower() in yes
83 else:
84 public = False
85
86 api_key = APIKeyStore(key=key, public=public)
73 api_key.save()87 api_key.save()
74 return HttpResponse(key)88 return HttpResponse(key)
7589
7690
=== modified file 'license_protected_downloads/views.py'
--- license_protected_downloads/views.py 2013-06-19 09:51:06 +0000
+++ license_protected_downloads/views.py 2013-07-17 15:28:26 +0000
@@ -24,7 +24,7 @@
24import bzr_version24import bzr_version
25from buildinfo import BuildInfo, IncorrectDataFormatException25from buildinfo import BuildInfo, IncorrectDataFormatException
26from render_text_files import RenderTextFiles26from render_text_files import RenderTextFiles
27from models import License27from models import License, APIKeyStore
28# Load group auth "plugin" dynamically28# Load group auth "plugin" dynamically
29import importlib29import importlib
30group_auth_modules = [importlib.import_module(m) for m in settings.GROUP_AUTH_MODULES]30group_auth_modules = [importlib.import_module(m) for m in settings.GROUP_AUTH_MODULES]
@@ -136,13 +136,25 @@
136 return listing136 return listing
137137
138138
139def test_path(path, served_paths=None):139def test_path(path, request, served_paths=None):
140 """Check that path points to something we can serve up.140 """Check that path points to something we can serve up.
141141
142 served_paths can be provided to overwrite settings.SERVED_PATHS. This is142 served_paths can be provided to overwrite settings.SERVED_PATHS. This is
143 used for uploaded files, which may not be shared in the server root.143 used for uploaded files, which may not be shared in the server root.
144 """144 """
145145
146 # if key is in request.GET["key"] then need to mod path and give
147 # access to a per-key directory.
148 if "key" in request.GET:
149 key_details = APIKeyStore.objects.filter(key=request.GET["key"])
150 if key_details:
151 path = os.path.join(request.GET["key"], path)
152
153 # Private uploads are in a separate path (or can be), so set
154 # served_paths as needed.
155 if key_details[0].public == False:
156 served_paths = settings.UPLOAD_PATH
157
146 if served_paths is None:158 if served_paths is None:
147 served_paths = settings.SERVED_PATHS159 served_paths = settings.SERVED_PATHS
148 else:160 else:
@@ -458,14 +470,7 @@
458def file_server_get(request, path):470def file_server_get(request, path):
459471
460 url = path472 url = path
461473 result = test_path(path, request)
462 # if key is in request.GET["key"] then need to mod path and give
463 # access to a per-key directory.
464 if "key" in request.GET:
465 path = os.path.join(request.GET["key"], path)
466 result = test_path(path, settings.UPLOAD_PATH)
467 else:
468 result = test_path(path)
469474
470 if not result:475 if not result:
471 raise Http404476 raise Http404
@@ -572,7 +577,7 @@
572577
573def get_textile_files(request):578def get_textile_files(request):
574579
575 result = test_path(request.GET.get("path"))580 result = test_path(request.GET.get("path"), request)
576 if not result:581 if not result:
577 raise Http404582 raise Http404
578583
@@ -600,7 +605,7 @@
600def list_files_api(request, path):605def list_files_api(request, path):
601 path = iri_to_uri(path)606 path = iri_to_uri(path)
602 url = path607 url = path
603 result = test_path(path)608 result = test_path(path, request)
604 if not result:609 if not result:
605 raise Http404610 raise Http404
606611
@@ -643,7 +648,7 @@
643648
644def get_license_api(request, path):649def get_license_api(request, path):
645 path = iri_to_uri(path)650 path = iri_to_uri(path)
646 result = test_path(path)651 result = test_path(path, request)
647 if not result:652 if not result:
648 raise Http404653 raise Http404
649654

Subscribers

People subscribed via source and target branches