diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a31960078..194b92d49 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,19 @@ Changelog This document describes changes between each past release. +3.2.1 (unreleased) +================== + +**Bug fixes** + +- Fix HTTP API version number exposed (1.7) +- Fix crash in authorization policy when requesting ``GET /buckets/collections`` (fixes #695) +- Fix crash with PostgreSQL storage backend when provided id in POST is an integer (#688). + Regression introduced in 3.2.0 with #655. +- Fix crash with PostgreSQL storage backend is configured as read-only and reaching + the records endpoint of an unknown collection (fixes #693, related #558) + + 3.2.0 (2016-06-14) ================== @@ -10,7 +23,7 @@ This document describes changes between each past release. - Allow record IDs to be any string instead of just UUIDs (fixes #655). -Protocol is now at version **1.7**. See `API changelog `_. +Protocol is now at version **1.7**. See `API changelog `_. **New features** @@ -52,7 +65,7 @@ Protocol is now at version **1.7**. See `API changelog `_. +Protocol is now at version **1.6**. See `API changelog `_. **Bug fixes** @@ -161,7 +174,7 @@ Protocol is now at version **1.6**. See `API changelog `_. +Protocol is now in version **1.5**. See `API changelog `_. 2.0.0 (2016-03-08) @@ -184,7 +197,7 @@ Protocol is now in version **1.5**. See `API changelog `_. +Protocol is now in version **1.4**. See `API changelog `_. **Breaking changes** @@ -214,7 +227,7 @@ Protocol is now in version **1.4**. See `API changelog `_. + `See more details `_. - Track execution time on StatsD for each authentication sub-policy (mozilla-services/cliquet#639) - Default console log renderer now has colours (mozilla-service/cliquet#671) - Output Kinto version with ``kinto --version`` (thanks @ayusharma) @@ -302,7 +315,7 @@ Protocol is now in version **1.4**. See `API changelog `_. +Protocol is now version 1.3. See `API changelog `_. **New features** @@ -757,7 +770,7 @@ Settings to use PostgreSQL instead of *Redis* (see default ``config/kinto.ini``) - ``cliquet.basic_auth_enabled`` is now deprecated (`see *Cliquet* docs to enable authentication backends - `_) + `_) **Internal changes** diff --git a/docs/conf.py b/docs/conf.py index 923c88d1c..aa5e9d04e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,7 +74,7 @@ # The short X.Y version. version = '3.2' # The full version, including alpha/beta/rc tags. -release = '3.2.0' +release = '3.2.1' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/kinto/__init__.py b/kinto/__init__.py index 67c5f0661..75202fa24 100644 --- a/kinto/__init__.py +++ b/kinto/__init__.py @@ -12,7 +12,7 @@ __version__ = pkg_resources.get_distribution(__package__).version # Implemented HTTP API Version -HTTP_API_VERSION = '1.6' +HTTP_API_VERSION = '1.7' # Main kinto logger logger = logging.getLogger(__name__) diff --git a/kinto/authorization.py b/kinto/authorization.py index 55d5a4289..904c975b2 100644 --- a/kinto/authorization.py +++ b/kinto/authorization.py @@ -1,7 +1,9 @@ -from kinto.core import authorization as core_authorization +import re + from pyramid.security import IAuthorizationPolicy, Authenticated from zope.interface import implementer +from kinto.core import authorization as core_authorization # Vocab really matters when you deal with permissions. Let's do a quick recap # of the terms used here: @@ -69,23 +71,15 @@ def get_object_type(object_uri): """Return the type of an object from its id.""" - - obj_parts = object_uri.split('/') - if len(obj_parts) % 2 == 0: - object_uri = '/'.join(obj_parts[:-1]) - - # Order matters here. More precise is tested first. - if 'records' in object_uri: - obj_type = 'record' - elif 'collections' in object_uri: - obj_type = 'collection' - elif 'groups' in object_uri: - obj_type = 'group' - elif 'buckets' in object_uri: - obj_type = 'bucket' - else: - obj_type = None - return obj_type + if re.match(r'/buckets/(.+)/collections/(.+)/records/(.+)?', object_uri): + return 'record' + if re.match(r'/buckets/(.+)/collections/(.+)?', object_uri): + return 'collection' + if re.match(r'/buckets/(.+)/groups/(.+)?', object_uri): + return 'group' + if re.match(r'/buckets/(.+)?', object_uri): + return 'bucket' + return None def build_permission_tuple(obj_type, unbound_permission, obj_parts): diff --git a/kinto/core/resource/__init__.py b/kinto/core/resource/__init__.py index 2572c92c6..40f600f94 100644 --- a/kinto/core/resource/__init__.py +++ b/kinto/core/resource/__init__.py @@ -717,7 +717,8 @@ def _raise_400_if_invalid_id(self, record_id): :raises: :class:`pyramid.httpexceptions.HTTPBadRequest` """ - if not self.model.id_generator.match(six.text_type(record_id)): + is_string = isinstance(record_id, six.string_types) + if not is_string or not self.model.id_generator.match(record_id): error_details = { 'location': 'path', 'description': "Invalid record id" diff --git a/kinto/tests/core/resource/test_views.py b/kinto/tests/core/resource/test_views.py index 56df58a4e..4fb1c6619 100644 --- a/kinto/tests/core/resource/test_views.py +++ b/kinto/tests/core/resource/test_views.py @@ -405,6 +405,13 @@ def test_id_is_validated_on_post(self): headers=self.headers, status=400) + with mock.patch.object(self.app.app.registry.id_generator, 'match', + return_value=True): + self.app.post_json(self.collection_url, + {'data': record}, + headers=self.headers, + status=400) + def test_id_is_preserved_on_post(self): record = MINIMALIST_RECORD.copy() record_id = record['id'] = '472be9ec-26fe-461b-8282-9c4e4b207ab3' diff --git a/kinto/tests/test_authorization.py b/kinto/tests/test_authorization.py index 5753e53fc..4c8b4dac3 100644 --- a/kinto/tests/test_authorization.py +++ b/kinto/tests/test_authorization.py @@ -55,6 +55,11 @@ def test_build_perm_set_uri_can_construct_parents_set_uris(self): 'bucket', 'write', obj_parts), (self.bucket_uri, 'write')) + def test_build_perm_set_supports_buckets_named_collections(self): + uri = '/buckets/collections' + self.assertEquals(build_permissions_set(uri, 'write'), + set([(uri, 'write')])) + def test_build_permission_tuple_fail_construct_children_set_uris(self): obj_parts = self.bucket_uri.split('/') # Cannot build record_uri from bucket obj_parts diff --git a/kinto/tests/test_views_records.py b/kinto/tests/test_views_records.py index 4994d2843..fe8cf9f62 100644 --- a/kinto/tests/test_views_records.py +++ b/kinto/tests/test_views_records.py @@ -37,6 +37,15 @@ def test_unknown_collection_raises_404(self): other_collection = self.collection_url.replace('barley', 'pills') self.app.get(other_collection, headers=self.headers, status=404) + def test_unknown_collection_does_not_query_timestamp(self): + other_collection = self.collection_url.replace('barley', 'pills') + patch = mock.patch.object(self.app.app.registry.storage, + 'collection_timestamp') + self.addCleanup(patch.stop) + mocked = patch.start() + self.app.get(other_collection, headers=self.headers, status=404) + self.assertFalse(mocked.called) + def test_parent_collection_is_fetched_only_once_in_batch(self): batch = {'requests': []} nb_create = 25 diff --git a/kinto/views/records.py b/kinto/views/records.py index b0dca73de..0ff1bf493 100644 --- a/kinto/views/records.py +++ b/kinto/views/records.py @@ -26,23 +26,22 @@ class Record(resource.ShareableResource): mapping = RecordSchema() schema_field = 'schema' - def __init__(self, *args, **kwargs): - super(Record, self).__init__(*args, **kwargs) - - self.model.id_generator = RelaxedUUID() - + def __init__(self, request, **kwargs): + # Before all, first check that the parent collection exists. # Check if already fetched before (in batch). - collections = self.request.bound_data.setdefault('collections', {}) - collection_uri = self.get_parent_id(self.request) + collections = request.bound_data.setdefault('collections', {}) + collection_uri = self.get_parent_id(request) if collection_uri not in collections: # Unknown yet, fetch from storage. collection_parent_id = '/buckets/%s' % self.bucket_id - collection = object_exists_or_404(self.request, + collection = object_exists_or_404(request, collection_id='collection', parent_id=collection_parent_id, object_id=self.collection_id) collections[collection_uri] = collection + super(Record, self).__init__(request, **kwargs) + self.model.id_generator = RelaxedUUID() self._collection = collections[collection_uri] def get_parent_id(self, request): diff --git a/setup.py b/setup.py index 6504d4761..9a08ca77b 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ def read_file(filename): setup(name='kinto', - version='3.2.0', + version='3.2.1', description='Kinto Web Service - Store, Sync, Share, and Self-Host.', long_description=README + "\n\n" + CHANGELOG + "\n\n" + CONTRIBUTORS, license='Apache License (2.0)',