Skip to content

Commit 960b4fe

Browse files
committed
Merge pull request #4256 from Joshua-Anderson/permissions-sec-fix
fix(controller): don't check permissions with @permissions_classes
2 parents dd4d777 + de636f0 commit 960b4fe

4 files changed

Lines changed: 74 additions & 13 deletions

File tree

controller/api/fixtures/test_sharing.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@
5353
"date_joined": "2013-11-25T21:59:30.760Z"
5454
}
5555
},
56+
{
57+
"pk": 4,
58+
"model": "auth.user",
59+
"fields": {
60+
"username": "autotest-3",
61+
"first_name": "",
62+
"last_name": "",
63+
"is_active": true,
64+
"is_superuser": false,
65+
"is_staff": false,
66+
"last_login": "2013-11-25T21:59:31.404Z",
67+
"groups": [],
68+
"user_permissions": [],
69+
"password": "pbkdf2_sha256$10000$FrfwTVAtWPMD$HUfDokMeY37YshdyS3uhDZ+d/r8galU7kNuBfZxJl2s=",
70+
"email": "autotest@deis.io",
71+
"date_joined": "2013-11-25T21:59:30.760Z"
72+
}
73+
},
5674
{
5775
"pk": "5a09a1e0-a27e-4839-928b-449310ed90f0",
5876
"model": "api.app",

controller/api/tests/test_perm.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ def setUp(self):
147147
self.token = Token.objects.get(user=self.user).key
148148
self.user2 = User.objects.get(username='autotest-2')
149149
self.token2 = Token.objects.get(user=self.user2).key
150+
self.user3 = User.objects.get(username='autotest-3')
151+
self.token3 = Token.objects.get(user=self.user3).key
150152

151153
def test_create(self):
152154
# check that user 1 sees her lone app and user 2's app
@@ -210,12 +212,8 @@ def test_delete(self):
210212
response = self.client.get('/v1/apps', HTTP_AUTHORIZATION='token {}'.format(self.token2))
211213
self.assertEqual(response.status_code, 200)
212214
self.assertEqual(len(response.data['results']), 2)
213-
# try to delete the permission as user 2
214-
url = "/v1/apps/{}/perms/{}".format(app_id, 'autotest-2')
215-
response = self.client.delete(url, content_type='application/json',
216-
HTTP_AUTHORIZATION='token {}'.format(self.token2))
217-
self.assertEqual(response.status_code, 403)
218215
# delete permission to user 1's app
216+
url = "/v1/apps/{}/perms/{}".format(app_id, 'autotest-2')
219217
response = self.client.delete(url, content_type='application/json',
220218
HTTP_AUTHORIZATION='token {}'.format(self.token))
221219
self.assertEqual(response.status_code, 204)
@@ -280,3 +278,42 @@ def test_unauthorized_user_cannot_modify_perms(self):
280278
response = self.client.post(url, json.dumps(body), content_type='application/json',
281279
HTTP_AUTHORIZATION='token {}'.format(unauthorized_token))
282280
self.assertEqual(response.status_code, 403)
281+
282+
def test_collaborator_cannot_share(self):
283+
"""
284+
An collaborator should not be able to modify the app's permissions.
285+
"""
286+
app_id = "autotest-1-app"
287+
owner_token = self.token
288+
collab = self.user2
289+
collab_token = self.token2
290+
url = '/v1/apps/{}/perms'.format(app_id)
291+
# Share app with collaborator
292+
body = {'username': collab.username}
293+
response = self.client.post(url, json.dumps(body), content_type='application/json',
294+
HTTP_AUTHORIZATION='token {}'.format(owner_token))
295+
self.assertEqual(response.status_code, 201)
296+
# Collaborator should fail to share app
297+
body = {'username': self.user3.username}
298+
response = self.client.post(url, json.dumps(body), content_type='application/json',
299+
HTTP_AUTHORIZATION='token {}'.format(collab_token))
300+
self.assertEqual(response.status_code, 403)
301+
# Collaborator can list
302+
response = self.client.get(url, content_type='application/json',
303+
HTTP_AUTHORIZATION='token {}'.format(collab_token))
304+
self.assertEqual(response.status_code, 200)
305+
# Share app with user 3 for rest of tests
306+
response = self.client.post(url, json.dumps(body), content_type='application/json',
307+
HTTP_AUTHORIZATION='token {}'.format(owner_token))
308+
self.assertEqual(response.status_code, 201)
309+
response = self.client.get(url, content_type='application/json',
310+
HTTP_AUTHORIZATION='token {}'.format(collab_token))
311+
self.assertEqual(response.status_code, 200)
312+
# Collaborator cannot delete other collaborator
313+
url += "/{}".format(self.user3.username)
314+
response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(collab_token))
315+
self.assertEqual(response.status_code, 403)
316+
# Collaborator can delete themselves
317+
url = '/v1/apps/{}/perms/{}'.format(app_id, collab.username)
318+
response = self.client.delete(url, HTTP_AUTHORIZATION='token {}'.format(collab_token))
319+
self.assertEqual(response.status_code, 204)

controller/api/views.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from guardian.shortcuts import assign_perm, get_objects_for_user, \
99
get_users_with_perms, remove_perm
1010
from rest_framework import mixins, renderers, status
11-
from rest_framework.decorators import permission_classes
1211
from rest_framework.exceptions import PermissionDenied
1312
from rest_framework.permissions import IsAuthenticated
1413
from rest_framework.response import Response
@@ -404,27 +403,35 @@ class AppPermsViewSet(BaseDeisViewSet):
404403
def get_queryset(self):
405404
return self.model.objects.all()
406405

407-
@permission_classes([permissions.IsAppUser])
408406
def list(self, request, **kwargs):
409407
app = self.get_object()
410408
perm_name = "api.{}".format(self.perm)
411409
usernames = [u.username for u in get_users_with_perms(app)
412410
if u.has_perm(perm_name, app)]
413411
return Response({'users': usernames})
414412

415-
@permission_classes([permissions.IsOwnerOrAdmin])
416413
def create(self, request, **kwargs):
417414
app = self.get_object()
415+
if not permissions.IsOwnerOrAdmin.has_object_permission(permissions.IsOwnerOrAdmin(),
416+
request, self, app):
417+
raise PermissionDenied()
418+
418419
user = get_object_or_404(User, username=request.data['username'])
419420
assign_perm(self.perm, user, app)
420421
models.log_event(app, "User {} was granted access to {}".format(user, app))
421422
return Response(status=status.HTTP_201_CREATED)
422423

423-
@permission_classes([permissions.IsOwnerOrAdmin])
424424
def destroy(self, request, **kwargs):
425-
app = self.get_object()
425+
app = get_object_or_404(models.App, id=self.kwargs['id'])
426426
user = get_object_or_404(User, username=kwargs['username'])
427-
if not user.has_perm(self.perm, app):
427+
428+
perm_name = "api.{}".format(self.perm)
429+
if not user.has_perm(perm_name, app):
430+
raise PermissionDenied()
431+
432+
if (user != request.user and
433+
not permissions.IsOwnerOrAdmin.has_object_permission(permissions.IsOwnerOrAdmin(),
434+
request, self, app)):
428435
raise PermissionDenied()
429436
remove_perm(self.perm, user, app)
430437
models.log_event(app, "User {} was revoked access to {}".format(user, app))

tests/perms_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ func permsDeleteAdminTest(t *testing.T, params *utils.DeisTestConfig) {
6666

6767
func permsDeleteAppTest(t *testing.T, params, user *utils.DeisTestConfig) {
6868
utils.Execute(t, authLoginCmd, user, false, "")
69-
utils.Execute(t, permsDeleteAppCmd, user, true, "403 FORBIDDEN")
69+
utils.Execute(t, permsDeleteAppCmd, user, false, "")
7070
utils.Execute(t, authLoginCmd, params, false, "")
71-
utils.Execute(t, permsDeleteAppCmd, params, false, "")
7271
utils.CheckList(t, permsListAppCmd, params, "test1", true)
7372
}

0 commit comments

Comments
 (0)