Repository: deis/deis

Issue:
fix(controller): shared users should be able to use apps:open
After sharing an app with another user and logging in as that user, I cannot use `deis open` to open the app in the browser.

```
><> deis open
403 FORBIDDEN
Detail:
You do not have permission to perform this action.
```



Discussion:
This is due to the client retrieving the cluster's domain via https://github.com/deis/deis/blob/master/client/deis.py#L541, which is restricted to administrators who are logged in: https://github.com/deis/deis/blob/master/controller/api/views.py#L185


Resolving patch:
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -572,15 +572,10 @@ def apps_open(self, args):
         # TODO: replace with a single API call to apps endpoint
         response = self._dispatch('get', "/api/apps/{}".format(app))
         if response.status_code == requests.codes.ok:  # @UndefinedVariable
-            cluster = response.json()['cluster']
-        else:
-            raise ResponseError(response)
-        response = self._dispatch('get', "/api/clusters/{}".format(cluster))
-        if response.status_code == requests.codes.ok:  # @UndefinedVariable
-            domain = response.json()['domain']
+            url = response.json()['url']
             # use the OS's default handler to open this URL
-            webbrowser.open('http://{}.{}/'.format(app, domain))
-            return domain
+            webbrowser.open('http://{}.{}/'.format(app, url))
+            return url
         else:
             raise ResponseError(response)
 
diff --git a/controller/api/models.py b/controller/api/models.py
--- a/controller/api/models.py
+++ b/controller/api/models.py
@@ -131,6 +131,10 @@ class Meta:
     def __str__(self):
         return self.id
 
+    @property
+    def url(self):
+        return self.id + '.' + self.cluster.domain
+
     def create(self, *args, **kwargs):
         config = Config.objects.create(owner=self.owner, app=self, values={})
         build = Build.objects.create(owner=self.owner, app=self, image=settings.DEFAULT_BUILD)
diff --git a/controller/api/serializers.py b/controller/api/serializers.py
--- a/controller/api/serializers.py
+++ b/controller/api/serializers.py
@@ -172,6 +172,7 @@ class AppSerializer(serializers.ModelSerializer):
     owner = serializers.Field(source='owner.username')
     id = serializers.SlugField(default=utils.generate_app_name)
     cluster = serializers.SlugRelatedField(slug_field='id')
+    url = serializers.Field(source='url')
 
     class Meta:
         """Metadata options for a :class:`AppSerializer`."""


Test patch:
diff --git a/controller/api/tests/test_app.py b/controller/api/tests/test_app.py
--- a/controller/api/tests/test_app.py
+++ b/controller/api/tests/test_app.py
@@ -42,6 +42,8 @@ def test_app(self):
         app_id = response.data['id']  # noqa
         self.assertIn('cluster', response.data)
         self.assertIn('id', response.data)
+        self.assertIn('url', response.data)
+        self.assertEqual(response.data['url'], '{app_id}.autotest.local'.format(**locals()))
         response = self.client.get('/api/apps')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(len(response.data['results']), 1)