Repository: deis/deis

Issue:
(api) deis ps:list returns processes list for unauthorised apps
Right now deis client can retrieve process list from api for ANY app ignoring permissions:

``` bash
$ deis apps
=== Apps
app-one

$ deis ps:list --app=app-two
=== app-two Processes

--- web:
web.1 up (v3)
```



Discussion:
@joiggama can you check `deis perms:list` on those apps to see who has permission to see those apps?

@bacongobbler yes, I don't have permissions for `app-two`: 

``` bash
$ deis perms:list --app=app-one
{
  "users": []
}

$ deis perms:list --app=app-two
403 FORBIDDEN
```

You're quite right. Only the owner of the app should be able to see that app according to https://github.com/deis/deis/blob/master/controller/api/views.py#L484. I have yet to confirm this on my box, but nice catch!

This is actually a pervasive bug that applies to releases and builds as well. :facepunch: I have a fix for it in a branch but need to write tests this morning before I make a pull request.


Resolving patch:
diff --git a/controller/api/views.py b/controller/api/views.py
--- a/controller/api/views.py
+++ b/controller/api/views.py
@@ -8,6 +8,7 @@
 
 from django.contrib.auth.models import AnonymousUser
 from django.contrib.auth.models import User
+from django.http import Http404
 from django.utils import timezone
 from guardian.shortcuts import assign_perm
 from guardian.shortcuts import get_objects_for_user
@@ -324,14 +325,17 @@ def pre_save(self, obj):
 
     def get_queryset(self, **kwargs):
         app = get_object_or_404(models.App, id=self.kwargs['id'])
+        try:
+            self.check_object_permissions(self.request, app)
+        except PermissionDenied:
+            raise Http404("No {} matches the given query.".format(
+                self.model._meta.object_name))
         return self.model.objects.filter(app=app)
 
     def get_object(self, *args, **kwargs):
         obj = self.get_queryset().latest('created')
-        user = self.request.user
-        if user == obj.app.owner or user in get_users_with_perms(obj.app):
-            return obj
-        raise PermissionDenied()
+        self.check_object_permissions(self.request, obj)
+        return obj
 
 
 class AppBuildViewSet(BaseAppViewSet):
@@ -368,10 +372,12 @@ class AppConfigViewSet(BaseAppViewSet):
     def get_object(self, *args, **kwargs):
         """Return the Config associated with the App's latest Release."""
         app = get_object_or_404(models.App, id=self.kwargs['id'])
-        user = self.request.user
-        if user == app.owner or user in get_users_with_perms(app):
+        try:
+            self.check_object_permissions(self.request, app)
             return app.release_set.latest().config
-        raise PermissionDenied()
+        except (PermissionDenied, models.Release.DoesNotExist):
+            raise Http404("No {} matches the given query.".format(
+                self.model._meta.object_name))
 
     def post_save(self, config, created=False):
         if created:
@@ -409,7 +415,12 @@ class AppLimitViewSet(BaseAppViewSet):
     def get_object(self, *args, **kwargs):
         """Return the Limit associated with the App's latest Release."""
         app = get_object_or_404(models.App, id=self.kwargs['id'])
-        return app.release_set.latest().config.limit
+        try:
+            self.check_object_permissions(self.request, app)
+            return app.release_set.latest().config.limit
+        except (PermissionDenied, models.Release.DoesNotExist):
+            raise Http404("No {} matches the given query.".format(
+                self.model._meta.object_name))
 
     def post_save(self, limit, created=False):
         if created:
@@ -481,15 +492,14 @@ def rollback(self, request, *args, **kwargs):
         return Response(response, status=status.HTTP_201_CREATED)
 
 
-class AppContainerViewSet(OwnerViewSet):
+class AppContainerViewSet(BaseAppViewSet):
     """RESTful views for :class:`~api.models.Container`."""
 
     model = models.Container
     serializer_class = serializers.ContainerSerializer
 
     def get_queryset(self, **kwargs):
-        app = get_object_or_404(models.App, id=self.kwargs['id'])
-        qs = self.model.objects.filter(app=app)
+        qs = super(AppContainerViewSet, self).get_queryset(**kwargs)
         container_type = self.kwargs.get('type')
         if container_type:
             qs = qs.filter(type=container_type)


Test patch:
diff --git a/controller/api/tests/test_perm.py b/controller/api/tests/test_perm.py
--- a/controller/api/tests/test_perm.py
+++ b/controller/api/tests/test_perm.py
@@ -154,6 +154,11 @@ def test_create(self):
             self.client.login(username='autotest-2', password='password'))
         response = self.client.get('/api/apps')
         self.assertEqual(len(response.data['results']), 0)
+        # check that user 2 can't see any of the app's builds, configs,
+        # containers, limits, or releases
+        for model in ['builds', 'config', 'containers', 'limits', 'releases']:
+            response = self.client.get("/api/apps/{}/{}/".format(app_id, model))
+            self.assertEqual(response.data['detail'], 'Not found')
         # TODO: test that git pushing to the app fails
         # give user 2 permission to user 1's app
         self.assertTrue(
@@ -168,6 +173,12 @@ def test_create(self):
         response = self.client.get('/api/apps')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(len(response.data['results']), 1)
+        # check that user 2 sees (empty) results now for builds, containers,
+        # and releases. (config and limit will still give 404s since we didn't
+        # push a build here.)
+        for model in ['builds', 'containers', 'releases']:
+            response = self.client.get("/api/apps/{}/{}/".format(app_id, model))
+            self.assertEqual(len(response.data['results']), 0)
         # TODO:  check that user 2 can git push the app
 
     def test_create_errors(self):