Repository: Kinto/kinto

Issue:
Permissions endpoint does not expose perms specified in settings
Ex:
- Set `kinto.bucket_write_principals = system.Authenticated`
- `GET /permissions` should mention every buckets

Related #892 #350 
Permissions endpoint does not expose perms specified in settings
Ex:
- Set `kinto.bucket_write_principals = system.Authenticated`
- `GET /permissions` should mention every buckets

Related #892 #350 


Discussion:

Resolving patch:
diff --git a/kinto/views/permissions.py b/kinto/views/permissions.py
--- a/kinto/views/permissions.py
+++ b/kinto/views/permissions.py
@@ -1,11 +1,51 @@
 import colander
 from pyramid.security import NO_PERMISSION_REQUIRED, Authenticated
+from pyramid.settings import aslist
 
 from kinto.authorization import PERMISSIONS_INHERITANCE_TREE
 from kinto.core import utils as core_utils, resource
 from kinto.core.storage.memory import extract_record_set
 
 
+def allowed_from_settings(settings, principals):
+    """Returns every permissions allowed from settings for the current user.
+    :param settings dict: app settings
+    :param principals list: list of principals of current user
+    :rtype: dict
+
+    Result example::
+
+        {
+            "bucket": {"write", "collection:create"},
+            "collection": {"read"}
+        }
+
+    XXX: This helper will be useful for Kinto/kinto#894
+    """
+    perms_settings = {k: aslist(v) for k, v in settings.items()
+                      if k.endswith('_principals')}
+    from_settings = {}
+    for key, allowed_principals in perms_settings.items():
+        resource_name, permission, _ = key.split('_')
+        # Keep the known permissions only.
+        if resource_name not in PERMISSIONS_INHERITANCE_TREE.keys():
+            continue
+        # Keep the permissions of the current user only.
+        if not bool(set(principals) & set(allowed_principals)):
+            continue
+        # ``collection_create_principals`` means ``collection:create`` in bucket.
+        if permission == 'create':
+            permission = '%s:%s' % (resource_name, permission)
+            resource_name = {  # resource parents.
+                'bucket': '',
+                'collection': 'bucket',
+                'group': 'bucket',
+                'record': 'collection'}[resource_name]
+        # Store them in a convenient way.
+        from_settings.setdefault(resource_name, set()).add(permission)
+    return from_settings
+
+
 class PermissionsModel(object):
     id_field = 'id'
     modified_field = 'last_modified'
@@ -41,10 +81,35 @@ def get_records(self, filters=None, sorting=None, pagination_rules=None,
         backend = self.request.registry.permission
         perms_by_object_uri = backend.get_accessible_objects(principals)
 
+        # Check settings for every allowed resources.
+        from_settings = allowed_from_settings(self.request.registry.settings, principals)
+
+        # Expand permissions obtained from backend with the object URIs that
+        # correspond to permissions allowed from settings.
+        allowed_resources = {'bucket', 'collection', 'group'} & set(from_settings.keys())
+        if allowed_resources:
+            storage = self.request.registry.storage
+            every_bucket, _ = storage.get_all(parent_id='', collection_id='bucket')
+            for bucket in every_bucket:
+                bucket_uri = '/buckets/{id}'.format(**bucket)
+                for res in allowed_resources:
+                    resource_perms = from_settings[res]
+                    # Bucket is always fetched.
+                    if res == 'bucket':
+                        perms_by_object_uri.setdefault(bucket_uri, []).extend(resource_perms)
+                        continue
+                    # Fetch bucket collections and groups.
+                    # XXX: wrong approach: query in a loop!
+                    every_subobjects, _ = storage.get_all(parent_id=bucket_uri,
+                                                          collection_id=res)
+                    for subobject in every_subobjects:
+                        subobj_uri = bucket_uri + '/{0}s/{1}'.format(res, subobject['id'])
+                        perms_by_object_uri.setdefault(subobj_uri, []).extend(resource_perms)
+
         entries = []
         for object_uri, perms in perms_by_object_uri.items():
             try:
-                # Obtain associated resource from object URI
+                # Obtain associated res from object URI
                 resource_name, matchdict = core_utils.view_lookup(self.request,
                                                                   object_uri)
             except ValueError:


Test patch:
diff --git a/tests/test_views_permissions.py b/tests/test_views_permissions.py
--- a/tests/test_views_permissions.py
+++ b/tests/test_views_permissions.py
@@ -124,3 +124,54 @@ def test_permissions_inherited_are_not_listed(self):
         resp = self.app.get('/permissions', headers=self.admin_headers)
         collections = [e for e in resp.json['data'] if e['resource_name'] == 'collection']
         self.assertEqual(len(collections), 0)
+
+
+class SettingsPermissionsTest(PermissionsViewTest):
+
+    admin_headers = get_user_headers('admin')
+    admin_principal = 'basicauth:bb7fe7b98e759578ef0de85b546dd57d21fe1e399390ad8dafc9886043a00e5c'  # NOQA
+
+    def __init__(self, *args, **kwargs):
+        super(SettingsPermissionsTest, self).__init__(*args, **kwargs)
+
+    def get_app_settings(self, extras=None):
+        settings = super(SettingsPermissionsTest, self).get_app_settings(extras)
+        settings['bucket_write_principals'] = 'system.Authenticated'
+        settings['group_create_principals'] = self.admin_principal
+        settings['collection_write_principals'] = 'system.Authenticated'
+        settings['record_create_principals'] = '/buckets/beers/groups/admins'
+        return settings
+
+    def setUp(self):
+        super(SettingsPermissionsTest, self).setUp()
+        self.app.put_json('/buckets/beers', MINIMALIST_BUCKET, headers=self.headers)
+        self.app.put_json('/buckets/beers/groups/admins',
+                          {'data': {'members': [self.admin_principal]}},
+                          headers=self.headers)
+        self.app.put_json('/buckets/beers/collections/barley',
+                          MINIMALIST_COLLECTION,
+                          headers=self.headers)
+
+    def test_bucket_write_taken_into_account(self):
+        resp = self.app.get('/permissions', headers=get_user_headers("any"))
+        buckets = [e for e in resp.json['data'] if e['resource_name'] == 'bucket']
+        self.assertEqual(buckets[0]['id'], 'beers')
+        self.assertIn('write', buckets[0]['permissions'])
+
+    def test_collection_create_taken_into_account(self):
+        resp = self.app.get('/permissions', headers=self.admin_headers)
+        buckets = [e for e in resp.json['data'] if e['resource_name'] == 'bucket']
+        self.assertEqual(buckets[0]['id'], 'beers')
+        self.assertIn('group:create', buckets[0]['permissions'])
+
+    def test_collection_write_taken_into_account(self):
+        resp = self.app.get('/permissions', headers=get_user_headers("any"))
+        collections = [e for e in resp.json['data'] if e['resource_name'] == 'collection']
+        self.assertEqual(collections[0]['id'], 'barley')
+        self.assertIn('write', collections[0]['permissions'])
+
+    def test_record_create_taken_into_account(self):
+        resp = self.app.get('/permissions', headers=self.admin_headers)
+        collections = [e for e in resp.json['data'] if e['resource_name'] == 'collection']
+        self.assertEqual(collections[0]['id'], 'barley')
+        self.assertIn('record:create', collections[0]['permissions'])