Skip to content

Commit 586b08c

Browse files
feat(controller): Allow adminOnly registration
1 parent f9cd85e commit 586b08c

7 files changed

Lines changed: 144 additions & 7 deletions

File tree

api/authentication.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.contrib.auth.models import AnonymousUser
22
from rest_framework import authentication
3+
from rest_framework.authentication import TokenAuthentication
34

45

56
class AnonymousAuthentication(authentication.BaseAuthentication):
@@ -9,3 +10,15 @@ def authenticate(self, request):
910
Authenticate the request for anyone!
1011
"""
1112
return AnonymousUser(), None
13+
14+
15+
class AnonymousOrAuthenticatedAuthentication(authentication.BaseAuthentication):
16+
17+
def authenticate(self, request):
18+
"""
19+
Authenticate the request for anyone or if a valid token is provided, a user.
20+
"""
21+
try:
22+
return TokenAuthentication.authenticate(TokenAuthentication(), request)
23+
except:
24+
return AnonymousUser(), None

api/permissions.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,22 @@ class HasRegistrationAuth(permissions.BasePermission):
9797
Checks to see if registration is enabled
9898
"""
9999
def has_permission(self, request, view):
100-
return settings.REGISTRATION_ENABLED
100+
"""
101+
If settings.REGISTRATION_MODE does not exist, such as during a test, return True
102+
Return `True` if permission is granted, `False` otherwise.
103+
"""
104+
try:
105+
if settings.REGISTRATION_MODE == 'disabled':
106+
return False
107+
if settings.REGISTRATION_MODE == 'enabled':
108+
return True
109+
elif settings.REGISTRATION_MODE == 'admin_only':
110+
return request.user.is_superuser
111+
else:
112+
raise Exception("{} is not a valid registation mode"
113+
.format(settings.REGISTRATION_MODE))
114+
except AttributeError:
115+
return True
101116

102117

103118
class HasBuilderAuth(permissions.BasePermission):

api/tests/test_auth.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_auth(self):
6363
content_type='application/x-www-form-urlencoded')
6464
self.assertEqual(response.status_code, 200)
6565

66-
@override_settings(REGISTRATION_ENABLED=False)
66+
@override_settings(REGISTRATION_MODE="disabled")
6767
def test_auth_registration_disabled(self):
6868
"""test that a new user cannot register when registration is disabled."""
6969
url = '/v1/auth/register'
@@ -79,6 +79,89 @@ def test_auth_registration_disabled(self):
7979
response = self.client.post(url, json.dumps(submit), content_type='application/json')
8080
self.assertEqual(response.status_code, 403)
8181

82+
@override_settings(REGISTRATION_MODE="admin_only")
83+
def test_auth_registration_admin_only_fails_if_not_admin(self):
84+
"""test that a non superuser cannot register when registration is admin only."""
85+
url = '/v1/auth/register'
86+
submit = {
87+
'username': 'testuser',
88+
'password': 'password',
89+
'first_name': 'test',
90+
'last_name': 'user',
91+
'email': 'test@user.com',
92+
'is_superuser': False,
93+
'is_staff': False,
94+
}
95+
response = self.client.post(url, json.dumps(submit), content_type='application/json')
96+
self.assertEqual(response.status_code, 403)
97+
98+
@override_settings(REGISTRATION_MODE="admin_only")
99+
def test_auth_registration_admin_only_works(self):
100+
"""test that a superuser can register when registration is admin only."""
101+
102+
user = User.objects.get(username='autotest')
103+
token = Token.objects.get(user=user)
104+
105+
url = '/v1/auth/register'
106+
107+
username, password = 'newuser_by_admin', 'password'
108+
first_name, last_name = 'Otto', 'Test'
109+
email = 'autotest@deis.io'
110+
111+
submit = {
112+
'username': username,
113+
'password': password,
114+
'first_name': first_name,
115+
'last_name': last_name,
116+
'email': email,
117+
# try to abuse superuser/staff level perms (not the first signup!)
118+
'is_superuser': True,
119+
'is_staff': True,
120+
}
121+
response = self.client.post(url, json.dumps(submit), content_type='application/json',
122+
HTTP_AUTHORIZATION='token {}'.format(token))
123+
124+
self.assertEqual(response.status_code, 201)
125+
for key in response.data.keys():
126+
self.assertIn(key, ['id', 'last_login', 'is_superuser', 'username', 'first_name',
127+
'last_name', 'email', 'is_active', 'is_superuser', 'is_staff',
128+
'date_joined', 'groups', 'user_permissions'])
129+
expected = {
130+
'username': username,
131+
'email': email,
132+
'first_name': first_name,
133+
'last_name': last_name,
134+
'is_active': True,
135+
'is_superuser': False,
136+
'is_staff': False
137+
}
138+
self.assertDictContainsSubset(expected, response.data)
139+
# test login
140+
url = '/v1/auth/login/'
141+
payload = urllib.urlencode({'username': username, 'password': password})
142+
response = self.client.post(url, data=payload,
143+
content_type='application/x-www-form-urlencoded')
144+
self.assertEqual(response.status_code, 200)
145+
146+
@override_settings(REGISTRATION_MODE="not_a_mode")
147+
def test_auth_registration_fails_with_nonexistant_mode(self):
148+
"""test that a registration should fail with a nonexistant mode"""
149+
url = '/v1/auth/register'
150+
submit = {
151+
'username': 'testuser',
152+
'password': 'password',
153+
'first_name': 'test',
154+
'last_name': 'user',
155+
'email': 'test@user.com',
156+
'is_superuser': False,
157+
'is_staff': False,
158+
}
159+
160+
try:
161+
self.client.post(url, json.dumps(submit), content_type='application/json')
162+
except Exception, e:
163+
self.assertEqual(str(e), 'not_a_mode is not a valid registation mode')
164+
82165
def test_cancel(self):
83166
"""Test that a registered user can cancel her account."""
84167
# test registration workflow

api/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
class UserRegistrationViewSet(GenericViewSet,
2121
mixins.CreateModelMixin):
2222
"""ViewSet to handle registering new users. The logic is in the serializer."""
23-
authentication_classes = [authentication.AnonymousAuthentication]
24-
permission_classes = [permissions.IsAnonymous, permissions.HasRegistrationAuth]
23+
authentication_classes = [authentication.AnonymousOrAuthenticatedAuthentication]
24+
permission_classes = [permissions.HasRegistrationAuth]
2525
serializer_class = serializers.UserSerializer
2626

2727

bin/boot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function etcd_safe_mkdir {
4747
etcd_set_default protocol ${DEIS_PROTOCOL:-http}
4848
etcd_set_default secretKey ${DEIS_SECRET_KEY:-`openssl rand -base64 64 | tr -d '\n'`}
4949
etcd_set_default builderKey ${DEIS_BUILDER_KEY:-`openssl rand -base64 64 | tr -d '\n'`}
50-
etcd_set_default registrationEnabled 1
50+
etcd_set_default registrationMode "enabled"
5151
etcd_set_default webEnabled 0
5252
etcd_set_default unitHostname default
5353

migrations/data/0002.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
3+
ETCD_PORT=${ETCD_PORT:-4001}
4+
ETCD="$HOST:$ETCD_PORT"
5+
ETCDCTL="etcdctl -C $ETCD"
6+
7+
# April 8, 2015: If registrationEnabled key exists, migrate it to registrationMode and delete it.
8+
9+
if [[ "$($ETCDCTL get /deis/migrations/data/0002 2> /dev/null)" != "done" ]];
10+
then
11+
if $ETCDCTL ls /deis/controller | grep -q '/deis/controller/registrationEnabled'
12+
then
13+
if [[ "$($ETCDCTL get /deis/controller/registrationEnabled 2> /dev/null)" == "false" ]]
14+
then
15+
$ETCDCTL set /deis/controller/registrationMode "disabled"
16+
else
17+
$ETCDCTL set /deis/controller/registrationMode "enabled"
18+
fi
19+
20+
$ETCDCTL rm /deis/controller/registrationEnabled
21+
else
22+
echo "registrationEnabled key doesn't exist, skipping migration"
23+
fi
24+
25+
$ETCDCTL set /deis/migrations/data/0002 "done"
26+
fi

templates/confd_settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
# move log directory out of /app/deis
3838
DEIS_LOG_DIR = '/data/logs'
3939

40-
{{ if exists "/deis/controller/registrationEnabled" }}
41-
REGISTRATION_ENABLED = bool({{ getv "/deis/controller/registrationEnabled" }})
40+
{{ if exists "/deis/controller/registrationMode" }}
41+
REGISTRATION_MODE = '{{ getv "/deis/controller/registrationMode" }}'
4242
{{ end }}
4343

4444
{{ if exists "/deis/controller/webEnabled" }}

0 commit comments

Comments
 (0)