Repository: deis/deis

Issue:
Dynamically disable registration
I have a few solutions, as to prevent joe public from registering to a public controller without firewalling.
- It would be good if the admin had the ability to dynamically disable/enable registration this would allow for new start to register and then close.
- Update the CLI to be abel to run `deis user:add` with the above solution.
- another option would be for a secure token/secret required during registration which is generated and printed to screen when the initial admin account is created.



Discussion:
would there be a possibility to allow a directory (like AD or LDAP) associated to user's ssh-key for ACL.

The kind that gitolite uses?

> Update the CLI to be abel to run deis user:add with the above solution.

We already have `deis auth:register` to accomplish that for us.

Currently, we have written our own `UserRegistrationView` which has `authentication_classes` set to a [default AnonymousAuthentication class](https://github.com/opdemand/deis/blob/master/api/views.py#L133). Instead, we can [write our own Authentication class](http://www.django-rest-framework.org/api-guide/authentication#custom-authentication) that can check if we have enabled user registration in `deis/settings.py`. Checking a variable like `settings.USER_REGISTRATION_ENABLED = True` would make the most sense here.

To reiterate, we could probably just write a subclass of `AnonymousAuthentication` and check `settings.USER_REGISTRATION_ENABLED` before continuing to run `AnonymousAuthentication`'s `authenticate` method. That way, we are completely backwards compatible with older versions of Deis, and nobody should feel affected by the change unless you decide to opt in.

Actually, it's probably better to check if user registration is enabled in [the permissions class](https://github.com/opdemand/deis/blob/master/api/views.py#L134) instead. Same process listed above applies.


Resolving patch:
diff --git a/controller/api/views.py b/controller/api/views.py
--- a/controller/api/views.py
+++ b/controller/api/views.py
@@ -26,7 +26,7 @@
 from api import docker, models, serializers
 from .exceptions import BuildFormationError, UserRegistrationException
 
-from deis import settings
+from django.conf import settings
 
 
 class AnonymousAuthentication(BaseAuthentication):
@@ -111,6 +111,14 @@ def has_permission(self, request, view):
         return request.method in permissions.SAFE_METHODS or request.user.is_superuser
 
 
+class HasRegistrationAuth(permissions.BasePermission):
+    """
+    Checks to see if registration is enabled
+    """
+    def has_permission(self, request, view):
+        return settings.REGISTRATION_ENABLED
+
+
 class HasBuilderAuth(permissions.BasePermission):
     """
     View permission to allow builder to perform actions
@@ -132,7 +140,7 @@ class UserRegistrationView(viewsets.GenericViewSet,
     model = User
 
     authentication_classes = (AnonymousAuthentication,)
-    permission_classes = (IsAnonymous,)
+    permission_classes = (IsAnonymous, HasRegistrationAuth)
     serializer_class = serializers.UserSerializer
 
     def post_save(self, user, created=False):
diff --git a/controller/deis/settings.py b/controller/deis/settings.py
--- a/controller/deis/settings.py
+++ b/controller/deis/settings.py
@@ -296,6 +296,9 @@
 # default providers, typically overriden in local_settings to include ec2, etc.
 PROVIDER_MODULES = ('mock',)
 
+# check if we can register users with `deis register`
+REGISTRATION_ENABLED = True
+
 # default to sqlite3, but allow postgresql config through envvars
 DATABASES = {
     'default': {


Test patch:
diff --git a/controller/api/tests/test_auth.py b/controller/api/tests/test_auth.py
--- a/controller/api/tests/test_auth.py
+++ b/controller/api/tests/test_auth.py
@@ -11,6 +11,7 @@
 
 from django.conf import settings
 from django.test import TestCase
+from django.test.utils import override_settings
 
 
 class AuthTest(TestCase):
@@ -85,6 +86,22 @@ def test_auth(self):
         response = self.client.get(url)
         self.assertEqual(response.status_code, 200)
 
+    @override_settings(REGISTRATION_ENABLED=False)
+    def test_auth_registration_disabled(self):
+        """test that a new user cannot register when registration is disabled."""
+        url = '/api/auth/register'
+        submit = {
+            'username': 'testuser',
+            'password': 'password',
+            'first_name': 'test',
+            'last_name': 'user',
+            'email': 'test@user.com',
+            'is_superuser': False,
+            'is_staff': False,
+        }
+        response = self.client.post(url, json.dumps(submit), content_type='application/json')
+        self.assertEqual(response.status_code, 403)
+
     def test_cancel(self):
         """Test that a registered user can cancel her account."""
         # test registration workflow