Repository: Kinto/kinto

Issue:
Crash in GET /permissions with 4.0
```
(.venv)➜ http PUT :8888/v1/buckets/magic --auth user:pass
HTTP/1.1 201 Created
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Content-Length: 156
Content-Type: application/json; charset=UTF-8
Date: Thu, 18 Aug 2016 16:04:59 GMT
Etag: "1471536299925"
Last-Modified: Thu, 18 Aug 2016 16:04:59 GMT
Server: waitress

{
    "data": {
        "id": "magic", 
        "last_modified": 1471536299925
    }, 
    "permissions": {
        "write": [
            "basicauth:a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a"
        ]
    }
}

```

```
(.venv)➜ http PUT :8888/v1/buckets/magic/collections/system --auth user:pass
HTTP/1.1 201 Created
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Content-Length: 157
Content-Type: application/json; charset=UTF-8
Date: Thu, 18 Aug 2016 16:05:07 GMT
Etag: "1471536307861"
Last-Modified: Thu, 18 Aug 2016 16:05:07 GMT
Server: waitress

{
    "data": {
        "id": "system", 
        "last_modified": 1471536307861
    }, 
    "permissions": {
        "write": [
            "basicauth:a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a"
        ]
    }
}
```

```
(.venv)➜  http  :8888/v1/permissions --auth user:pass
HTTP/1.1 500 Internal Server Error
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Content-Length: 183
Content-Type: application/json; charset=UTF-8
Date: Thu, 18 Aug 2016 16:05:11 GMT
Server: waitress

{
    "code": 500, 
    "errno": 999, 
    "error": "Internal Server Error", 
    "info": "https://github.com/Kinto/kinto/issues/", 
    "message": "A programmatic error occured, developers have been informed."
}

```

```
ERROR  "GET   /v1/permissions" ? (? ms) 'NoneType' object has no attribute 'name' agent=node-fetch/1.0 (+https://github.com/bitinn/node-fetch) authn_type=basicauth errno=None exception=Traceback (most recent call last):
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/tweens.py", line 22, in excview_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 101, in tm_tween
    reraise(*exc_info)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid_tm/__init__.py", line 83, in tm_tween
    response = handler(request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/router.py", line 158, in handle_request
    view_name
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/view.py", line 547, in _call_view
    response = view_callable(context, request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/config/views.py", line 182, in __call__
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 393, in attr_view
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 371, in predicate_wrapper
    return view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 442, in rendered_view
    result = view(context, request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/pyramid/viewderivers.py", line 147, in _requestonly_view
    response = view(request)
  File "/home/mathieu/Code/Mozilla/kinto-client/.venv/local/lib/python2.7/site-packages/cornice/service.py", line 573, in wrapper
    response = view_(request)
  File "/home/mathieu/Code/Mozilla/kinto/kinto/views/permissions.py", line 41, in permissions_get
    resource_name, matchdict = core_utils.view_lookup(request, object_uri)
  File "/home/mathieu/Code/Mozilla/kinto/kinto/core/utils.py", line 390, in view_lookup
    resource_name = route.name.replace('-record', '')\
AttributeError: 'NoneType' object has no attribute 'name' lang=None uid=a9d81e1b56f2b77103e87601621a99b3d82a15abf7c72ce57a032d0964317c7a
```



Discussion:
I was not able to reproduce :s

```
$ http  :8888/v1/permissions --auth user:pass
HTTP/1.1 200 OK
Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff
Content-Length: 338
Content-Type: application/json; charset=UTF-8
Date: Thu, 18 Aug 2016 16:19:34 GMT
Server: waitress

{
    "data": [
        {
            "bucket_id": "magic", 
            "id": "magic", 
            "permissions": [
                "write", 
                "read", 
                "collection:create", 
                "group:create"
            ], 
            "resource_name": "bucket", 
            "uri": "/buckets/magic"
        }, 
        {
            "bucket_id": "magic", 
            "collection_id": "system", 
            "id": "system", 
            "permissions": [
                "write", 
                "read", 
                "record:create"
            ], 
            "resource_name": "collection", 
            "uri": "/buckets/magic/collections/system"
        }
    ]
}
```

It's reproduced on TravisCI : https://travis-ci.org/Kinto/kinto-http.js/jobs/153325171

![capture d ecran de 2016-08-18 18-28-53](https://cloud.githubusercontent.com/assets/546692/17781949/a57514f8-6571-11e6-93f9-985d30daebf0.png)

Its probably related to the history plugin, I'll have a look


Resolving patch:
diff --git a/kinto/core/utils.py b/kinto/core/utils.py
--- a/kinto/core/utils.py
+++ b/kinto/core/utils.py
@@ -387,6 +387,9 @@ def view_lookup(request, uri):
     fakerequest = Request.blank(path=path)
     info = routes_mapper(fakerequest)
     matchdict, route = info['match'], info['route']
+    if route is None:
+        raise ValueError("URI has no route")
+
     resource_name = route.name.replace('-record', '')\
                               .replace('-collection', '')
     return resource_name, matchdict
diff --git a/kinto/views/permissions.py b/kinto/views/permissions.py
--- a/kinto/views/permissions.py
+++ b/kinto/views/permissions.py
@@ -37,8 +37,14 @@ def permissions_get(request):
 
     entries = []
     for object_uri, perms in perms_by_object_uri.items():
-        # Obtain associated resource from object URI
-        resource_name, matchdict = core_utils.view_lookup(request, object_uri)
+        try:
+            # Obtain associated resource from object URI
+            resource_name, matchdict = core_utils.view_lookup(request,
+                                                              object_uri)
+        except ValueError:
+            # Skip permissions entries that are not linked to an object URI.
+            continue
+
         # For consistency with events payloads, prefix id with resource name
         matchdict[resource_name + '_id'] = matchdict.get('id')
 


Test patch:
diff --git a/tests/plugins/test_history.py b/tests/plugins/test_history.py
--- a/tests/plugins/test_history.py
+++ b/tests/plugins/test_history.py
@@ -404,6 +404,11 @@ def test_implicit_creations_are_listed(self):
 
 class PermissionsTest(HistoryWebTest):
 
+    def get_app_settings(self, extras=None):
+        settings = super(PermissionsTest, self).get_app_settings(extras)
+        settings['experimental_permissions_endpoint'] = 'true'
+        return settings
+
     def setUp(self):
         self.alice_headers = get_user_headers('alice')
         self.bob_headers = get_user_headers('bob')
@@ -523,3 +528,9 @@ def test_new_entries_are_not_readable_if_permission_is_removed(self):
         resp = self.app.get('/buckets/test/history',
                             headers=self.alice_headers)
         assert resp.headers['ETag'] != before
+
+    def test_history_entries_are_not_listed_in_permissions_endpoint(self):
+        resp = self.app.get('/permissions',
+                            headers=self.headers)
+        entries = [e['resource_name'] == 'history' for e in resp.json["data"]]
+        assert not any(entries)