Repository: deis/deis

Issue:
Deis pull does not respect permissions.
Any user can `deis pull` into any app without having the needed permissions. Steps to reproduce:

```
 - deis login --username user1
 - deis create
 - deis pull postgres
   # creates release v2
 - deis login --username user2
 - deis pull postgres
   # creates release v3
```

The last step should fail. Other commands like `deis info` or `deis config` fail as excepted because `user2` does not have permissions on this app.



Discussion:
This is odd. `deis pull` has the exact same permissions as `deis config`. They both are children of [BaseAppViewSet](https://github.com/deis/deis/blob/583f9f343dafa9df2fdfd1274f126e67b2fdf6cc/controller/api/views.py#L224-L226) which has the permissions from [IsAppUser](https://github.com/deis/deis/blob/583f9f343dafa9df2fdfd1274f126e67b2fdf6cc/controller/api/permissions.py#L33).

Could you please dump your user and application information from the database, please? I'd be interested to know how user2 has permission to user1's app.

```
fleetctl ssh deis-controller
nse deis-controller
./manage.py dumpdata auth.User
./manage.py dumpdata api.App
```

Thanks!

Just created a unit test to confirm this, and it seems that users can create builds for other users. Very strange behaviour. Thanks for this; I'll look into it.


Resolving patch:
diff --git a/controller/api/views.py b/controller/api/views.py
--- a/controller/api/views.py
+++ b/controller/api/views.py
@@ -260,6 +260,7 @@ def get_success_headers(self, data):
 
     def create(self, request, *args, **kwargs):
         app = get_object_or_404(models.App, id=self.kwargs['id'])
+        self.check_object_permissions(self.request, app)
         request._data = request.DATA.copy()
         request.DATA['app'] = app
         try:


Test patch:
diff --git a/controller/api/tests/test_build.py b/controller/api/tests/test_build.py
--- a/controller/api/tests/test_build.py
+++ b/controller/api/tests/test_build.py
@@ -172,7 +172,6 @@ def test_build_default_containers(self):
     def test_build_str(self):
         """Test the text representation of a build."""
         url = '/v1/apps'
-        response = self.client.post(url)
         response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
         self.assertEqual(response.status_code, 201)
         app_id = response.data['id']
@@ -207,3 +206,18 @@ def test_admin_can_create_builds_on_other_apps(self):
         build = Build.objects.get(uuid=response.data['uuid'])
         self.assertEqual(str(build), "{}-{}".format(
                          response.data['app'], response.data['uuid'][:7]))
+
+    @mock.patch('requests.post', mock_import_repository_task)
+    def test_unauthorized_user_cannot_create_build(self):
+        """An unauthorized user should not be able to create builds for other apps."""
+        url = '/v1/apps'
+        response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
+        app_id = response.data['id']
+        # attempt to create a build as a malicious user
+        evil_user = User.objects.get(username='autotest2')
+        evil_token = Token.objects.get(user=evil_user).key
+        url = "/v1/apps/{app_id}/builds".format(**locals())
+        body = {'image': 'eeeeeevillllll'}
+        response = self.client.post(url, json.dumps(body), content_type='application/json',
+                                    HTTP_AUTHORIZATION='token {}'.format(evil_token))
+        self.assertEqual(response.status_code, 403)