From 18097a1bd6655bd9eff753403cf0e2b8dce19974 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 17 Oct 2016 13:57:05 -0400 Subject: [PATCH 1/2] Fix Link headers for pagination to match RFC Fixes part of #2002 --- endpoints/v2/__init__.py | 5 +++-- test/registry_tests.py | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/endpoints/v2/__init__.py b/endpoints/v2/__init__.py index 8d611de0a..da20b9077 100644 --- a/endpoints/v2/__init__.py +++ b/endpoints/v2/__init__.py @@ -59,8 +59,9 @@ def paginate(limit_kwarg_name='limit', offset_kwarg_name='offset', return next_page_token = encrypt_page_token({'offset': limit + offset}) - link = get_app_url() + url_for(request.endpoint, **request.view_args) - link += '?%s; rel="next"' % urlencode({'n': limit, 'next_page': next_page_token}) + link_url = get_app_url() + url_for(request.endpoint, **request.view_args) + link_param = urlencode({'n': limit, 'next_page': next_page_token}) + link = '<%s?%s>; rel="next"' % (link_url, link_param) response.headers['Link'] = link kwargs[limit_kwarg_name] = limit diff --git a/test/registry_tests.py b/test/registry_tests.py index 075b761fc..120d5b8be 100644 --- a/test/registry_tests.py +++ b/test/registry_tests.py @@ -1377,11 +1377,15 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix break # Check the next page of results. - link = result.headers['Link'] + link_header = result.headers['Link'] + self.assertTrue(link_header.startswith('<')) + self.assertTrue(link_header.endswith('>; rel="next"')) + + link = link_header[1:] self.assertTrue(link.endswith('; rel="next"')) url, _ = link.split(';') - relative_url = url[len(self.get_server_url()):] + relative_url = url[len(self.get_server_url()):-1] encountered.update(set(result_json['tags'])) From 3439f814b6e6b39d7565dc31b9b8a37255900592 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 17 Oct 2016 14:32:43 -0400 Subject: [PATCH 2/2] Fix quoting of scopes in WWW-Authenticate header Fixes part of #2002 --- auth/registry_jwt_auth.py | 6 ++++-- test/registry_tests.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/auth/registry_jwt_auth.py b/auth/registry_jwt_auth.py index fc06c3c85..d49a7cbcf 100644 --- a/auth/registry_jwt_auth.py +++ b/auth/registry_jwt_auth.py @@ -127,9 +127,11 @@ def get_auth_headers(repository=None, scopes=None): realm_auth_path, app.config['SERVER_HOSTNAME']) if repository: - authenticate += ',scope=repository:{0}'.format(repository) + scopes_string = "repository:{0}".format(repository) if scopes: - authenticate += ':' + ','.join(scopes) + scopes_string += ':' + ','.join(scopes) + + authenticate += ',scope="{0}"'.format(scopes_string) headers['WWW-Authenticate'] = authenticate headers['Docker-Distribution-API-Version'] = 'registry/2.0' diff --git a/test/registry_tests.py b/test/registry_tests.py index 120d5b8be..b9d4d8212 100644 --- a/test/registry_tests.py +++ b/test/registry_tests.py @@ -1327,6 +1327,13 @@ class V1RegistryTests(V1RegistryPullMixin, V1RegistryPushMixin, RegistryTestsMix class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMixin, RegistryTestCaseMixin, LiveServerTestCase): """ Tests for V2 registry. """ + def test_proper_auth_response(self): + response = self.conduct('GET', '/v2/devtable/doesnotexist/tags/list', auth='jwt', + expected_code=401) + self.assertIn('WWW-Authenticate', response.headers) + self.assertIn('scope="repository:devtable/doesnotexist:pull"', + response.headers['WWW-Authenticate']) + def test_parent_misordered(self): images = [ { @@ -1711,6 +1718,8 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix # Assert 401s to non-auth endpoints also get the WWW-Authenticate header. self.assertIn('WWW-Authenticate', response.headers) + self.assertIn('scope="repository:devtable/doesnotexist:pull"', + response.headers['WWW-Authenticate']) def test_one_five_blacklist(self): self.conduct('GET', '/v2/', expected_code=404, user_agent='Go 1.1 package http') @@ -1735,7 +1744,7 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix self.assertIsNotNone(response.headers.get('Link')) # Request with the next link. - link_url = response.headers.get('Link').split(';')[0] + link_url = response.headers.get('Link')[1:].split(';')[0][:-1] v2_index = link_url.find('/v2/') relative_url = link_url[v2_index:]