Skip to content

Commit dba04a8

Browse files
author
Matthew Fisher
committed
Merge pull request #2945 from bacongobbler/2854-api-refactor
ref(controller): API view refactor
2 parents 0459ccc + ae10569 commit dba04a8

3 files changed

Lines changed: 77 additions & 82 deletions

File tree

controller/api/permissions.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55
from api import models
66

77

8+
def is_app_user(request, obj):
9+
if request.user.is_superuser or \
10+
isinstance(obj, models.App) and obj.owner == request.user or \
11+
hasattr(obj, 'app') and obj.app.owner == request.user:
12+
return True
13+
elif request.user.has_perm('use_app', obj) or \
14+
hasattr(obj, 'app') and request.user.has_perm('use_app', obj.app):
15+
return request.method != 'DELETE'
16+
else:
17+
return False
18+
19+
820
class IsAnonymous(permissions.BasePermission):
921
"""
1022
View permission to allow anonymous users.
@@ -30,26 +42,29 @@ def has_object_permission(self, request, view, obj):
3042
return False
3143

3244

33-
class IsAppUser(permissions.BasePermission):
45+
class IsOwnerOrAdmin(permissions.BasePermission):
3446
"""
35-
Object-level permission to allow owners or collaborators to access
36-
an app-related model.
47+
Object-level permission to allow only owners of an object or administrators to access it.
48+
Assumes the model instance has an `owner` attribute.
3749
"""
3850
def has_object_permission(self, request, view, obj):
3951
if request.user.is_superuser:
4052
return True
41-
if isinstance(obj, models.App) and obj.owner == request.user:
42-
return True
43-
elif hasattr(obj, 'app') and obj.app.owner == request.user:
44-
return True
45-
elif request.user.has_perm('use_app', obj):
46-
return request.method != 'DELETE'
47-
elif hasattr(obj, 'app') and request.user.has_perm('use_app', obj.app):
48-
return request.method != 'DELETE'
53+
if hasattr(obj, 'owner'):
54+
return obj.owner == request.user
4955
else:
5056
return False
5157

5258

59+
class IsAppUser(permissions.BasePermission):
60+
"""
61+
Object-level permission to allow owners or collaborators to access
62+
an app-related model.
63+
"""
64+
def has_object_permission(self, request, view, obj):
65+
return is_app_user(request, obj)
66+
67+
5368
class IsAdmin(permissions.BasePermission):
5469
"""
5570
View permission to allow only admins.

controller/api/tests/test_perm.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ def test_delete(self):
215215
response = self.client.delete(url, content_type='application/json',
216216
HTTP_AUTHORIZATION='token {}'.format(self.token2))
217217
self.assertEqual(response.status_code, 403)
218-
self.assertIsNone(response.data)
219218
# delete permission to user 1's app
220219
response = self.client.delete(url, content_type='application/json',
221220
HTTP_AUTHORIZATION='token {}'.format(self.token))

controller/api/views.py

Lines changed: 51 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from guardian.shortcuts import assign_perm, get_objects_for_user, \
1010
get_users_with_perms, remove_perm
1111
from rest_framework import mixins, renderers, status
12+
from rest_framework.decorators import permission_classes
1213
from rest_framework.exceptions import PermissionDenied
1314
from rest_framework.permissions import IsAuthenticated
1415
from rest_framework.response import Response
@@ -37,10 +38,10 @@ def get_object(self):
3738

3839
def passwd(self, request, **kwargs):
3940
obj = self.get_object()
40-
if not obj.check_password(request.DATA['password']):
41+
if not obj.check_password(request.data['password']):
4142
return Response({'detail': 'Current password does not match'},
4243
status=status.HTTP_400_BAD_REQUEST)
43-
obj.set_password(request.DATA['new_password'])
44+
obj.set_password(request.data['new_password'])
4445
obj.save()
4546
return Response({'status': 'password set'})
4647

@@ -80,7 +81,7 @@ def get_object(self, **kwargs):
8081
return self.get_queryset(**kwargs).latest('created')
8182

8283
def create(self, request, **kwargs):
83-
request.DATA['app'] = self.get_app()
84+
request.data['app'] = self.get_app()
8485
return super(AppResourceViewSet, self).create(request, **kwargs)
8586

8687

@@ -134,14 +135,14 @@ def scale(self, request, **kwargs):
134135
new_structure = {}
135136
app = self.get_object()
136137
try:
137-
for target, count in request.DATA.items():
138+
for target, count in request.data.items():
138139
new_structure[target] = int(count)
139140
models.validate_app_structure(new_structure)
140141
app.scale(request.user, new_structure)
141142
except (TypeError, ValueError):
142143
return Response({'detail': 'Invalid scaling format'},
143144
status=status.HTTP_400_BAD_REQUEST)
144-
except (ValidationError, EnvironmentError) as e:
145+
except (EnvironmentError, ValidationError) as e:
145146
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
146147
except RuntimeError as e:
147148
return Response({'detail': str(e)}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
@@ -150,19 +151,16 @@ def scale(self, request, **kwargs):
150151
def logs(self, request, **kwargs):
151152
app = self.get_object()
152153
try:
153-
logs = app.logs()
154+
return Response(app.logs(), status=status.HTTP_200_OK, content_type='text/plain')
154155
except EnvironmentError:
155156
return Response("No logs for {}".format(app.id),
156157
status=status.HTTP_204_NO_CONTENT,
157158
content_type='text/plain')
158-
return Response(logs, status=status.HTTP_200_OK,
159-
content_type='text/plain')
160159

161160
def run(self, request, **kwargs):
162161
app = self.get_object()
163-
command = request.DATA['command']
164162
try:
165-
output_and_rc = app.run(self.request.user, command)
163+
output_and_rc = app.run(self.request.user, request.data['command'])
166164
except EnvironmentError as e:
167165
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
168166
except RuntimeError as e:
@@ -241,12 +239,12 @@ def rollback(self, request, **kwargs):
241239
Create a new release as a copy of the state of the compiled slug and config vars of a
242240
previous release.
243241
"""
242+
app = self.get_app()
244243
try:
245-
app = self.get_app()
246244
release = app.release_set.latest()
247245
version_to_rollback_to = release.version - 1
248-
if request.DATA.get('version'):
249-
version_to_rollback_to = int(request.DATA['version'])
246+
if request.data.get('version'):
247+
version_to_rollback_to = int(request.data['version'])
250248
new_release = release.rollback(request.user, version_to_rollback_to)
251249
response = {'version': new_release.version}
252250
return Response(response, status=status.HTTP_201_CREATED)
@@ -268,18 +266,13 @@ class PushHookViewSet(BaseHookViewSet):
268266

269267
def create(self, request, *args, **kwargs):
270268
app = get_object_or_404(models.App, id=request.data['receive_repo'])
271-
self.user = get_object_or_404(User, username=request.data['receive_user'])
269+
request.user = get_object_or_404(User, username=request.data['receive_user'])
272270
# check the user is authorized for this app
273-
if self.user == app.owner or \
274-
self.user in get_users_with_perms(app) or \
275-
self.user.is_superuser:
276-
request.data['app'] = app
277-
request.data['owner'] = self.user
278-
return super(PushHookViewSet, self).create(request, *args, **kwargs)
279-
raise PermissionDenied()
280-
281-
def perform_create(self, serializer, **kwargs):
282-
serializer.save(owner=self.user)
271+
if not permissions.is_app_user(request, app):
272+
raise PermissionDenied()
273+
request.data['app'] = app
274+
request.data['owner'] = request.user
275+
return super(PushHookViewSet, self).create(request, *args, **kwargs)
283276

284277

285278
class BuildHookViewSet(BaseHookViewSet):
@@ -289,24 +282,17 @@ class BuildHookViewSet(BaseHookViewSet):
289282

290283
def create(self, request, *args, **kwargs):
291284
app = get_object_or_404(models.App, id=request.data['receive_repo'])
292-
self.user = get_object_or_404(User, username=request.data['receive_user'])
285+
self.user = request.user = get_object_or_404(User, username=request.data['receive_user'])
293286
# check the user is authorized for this app
294-
if self.user == app.owner or \
295-
self.user in get_users_with_perms(app) or \
296-
self.user.is_superuser:
297-
request._data = request.data.copy()
298-
request.data['app'] = app
299-
request.data['owner'] = self.user
300-
super(BuildHookViewSet, self).create(request, *args, **kwargs)
301-
# return the application databag
302-
response = {'release': {'version': app.release_set.latest().version},
303-
'domains': ['.'.join([app.id, settings.DEIS_DOMAIN])]}
304-
return Response(response, status=status.HTTP_200_OK)
305-
raise PermissionDenied()
306-
307-
def perform_create(self, serializer, **kwargs):
308-
build = serializer.save(owner=self.user)
309-
self.post_save(build)
287+
if not permissions.is_app_user(request, app):
288+
raise PermissionDenied()
289+
request.data['app'] = app
290+
request.data['owner'] = self.user
291+
super(BuildHookViewSet, self).create(request, *args, **kwargs)
292+
# return the application databag
293+
response = {'release': {'version': app.release_set.latest().version},
294+
'domains': ['.'.join([app.id, settings.DEIS_DOMAIN])]}
295+
return Response(response, status=status.HTTP_200_OK)
310296

311297
def post_save(self, build):
312298
build.create(self.user)
@@ -318,16 +304,14 @@ class ConfigHookViewSet(BaseHookViewSet):
318304
serializer_class = serializers.ConfigSerializer
319305

320306
def create(self, request, *args, **kwargs):
321-
app = get_object_or_404(models.App, id=request.DATA['receive_repo'])
322-
user = get_object_or_404(User, username=request.DATA['receive_user'])
307+
app = get_object_or_404(models.App, id=request.data['receive_repo'])
308+
request.user = get_object_or_404(User, username=request.data['receive_user'])
323309
# check the user is authorized for this app
324-
if user == app.owner or \
325-
user in get_users_with_perms(app) or \
326-
user.is_superuser:
327-
config = app.release_set.latest().config
328-
serializer = self.get_serializer(config)
329-
return Response(serializer.data, status=status.HTTP_200_OK)
330-
raise PermissionDenied()
310+
if not permissions.is_app_user(request, app):
311+
raise PermissionDenied()
312+
config = app.release_set.latest().config
313+
serializer = self.get_serializer(config)
314+
return Response(serializer.data, status=status.HTTP_200_OK)
331315

332316

333317
class AppPermsViewSet(BaseDeisViewSet):
@@ -336,52 +320,49 @@ class AppPermsViewSet(BaseDeisViewSet):
336320
model = models.App # models class
337321
perm = 'use_app' # short name for permission
338322

323+
def get_queryset(self):
324+
return self.model.objects.all()
325+
326+
@permission_classes([permissions.IsAppUser])
339327
def list(self, request, **kwargs):
340-
app = get_object_or_404(self.model, id=kwargs['id'])
328+
app = self.get_object()
341329
perm_name = "api.{}".format(self.perm)
342-
if request.user != app.owner and \
343-
not request.user.has_perm(perm_name, app) and \
344-
not request.user.is_superuser:
345-
return Response(status=status.HTTP_403_FORBIDDEN)
346330
usernames = [u.username for u in get_users_with_perms(app)
347331
if u.has_perm(perm_name, app)]
348332
return Response({'users': usernames})
349333

334+
@permission_classes([permissions.IsOwnerOrAdmin])
350335
def create(self, request, **kwargs):
351-
app = get_object_or_404(self.model, id=kwargs['id'])
352-
if request.user != app.owner and not request.user.is_superuser:
353-
return Response(status=status.HTTP_403_FORBIDDEN)
354-
user = get_object_or_404(User, username=request.DATA['username'])
336+
app = self.get_object()
337+
user = get_object_or_404(User, username=request.data['username'])
355338
assign_perm(self.perm, user, app)
356339
models.log_event(app, "User {} was granted access to {}".format(user, app))
357340
return Response(status=status.HTTP_201_CREATED)
358341

342+
@permission_classes([permissions.IsOwnerOrAdmin])
359343
def destroy(self, request, **kwargs):
360-
app = get_object_or_404(self.model, id=kwargs['id'])
361-
if request.user != app.owner and not request.user.is_superuser:
362-
return Response(status=status.HTTP_403_FORBIDDEN)
344+
app = self.get_object()
363345
user = get_object_or_404(User, username=kwargs['username'])
364-
if user.has_perm(self.perm, app):
365-
remove_perm(self.perm, user, app)
366-
models.log_event(app, "User {} was revoked access to {}".format(user, app))
367-
return Response(status=status.HTTP_204_NO_CONTENT)
368-
else:
369-
return Response(status=status.HTTP_403_FORBIDDEN)
346+
if not user.has_perm(self.perm, app):
347+
raise PermissionDenied()
348+
remove_perm(self.perm, user, app)
349+
models.log_event(app, "User {} was revoked access to {}".format(user, app))
350+
return Response(status=status.HTTP_204_NO_CONTENT)
370351

371352

372353
class AdminPermsViewSet(BaseDeisViewSet):
373354
"""RESTful views for sharing admin permissions with other users."""
374355

375356
model = User
376357
serializer_class = serializers.AdminUserSerializer
377-
permission_classes = (permissions.IsAdmin,)
358+
permission_classes = [permissions.IsAdmin]
378359

379360
def get_queryset(self, **kwargs):
380361
self.check_object_permissions(self.request, self.request.user)
381362
return self.model.objects.filter(is_active=True, is_superuser=True)
382363

383364
def create(self, request, **kwargs):
384-
user = get_object_or_404(User, username=request.DATA['username'])
365+
user = get_object_or_404(User, username=request.data['username'])
385366
user.is_superuser = user.is_staff = True
386367
user.save(update_fields=['is_superuser', 'is_staff'])
387368
return Response(status=status.HTTP_201_CREATED)

0 commit comments

Comments
 (0)