-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #94 from edx/jsa/ecom-3978
Fix model permissions for UserCredential.
- Loading branch information
Showing
6 changed files
with
264 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
""" | ||
Custom permissions classes for use with DRF. | ||
""" | ||
from django.http import Http404 | ||
from rest_framework import permissions | ||
|
||
|
||
class UserCredentialViewSetPermissions(permissions.DjangoModelPermissions): | ||
""" | ||
Custom extension to DjangoModelPermissions for use with the | ||
UserCredentialViewSet. | ||
This permissions class uses an explicit 'view' permission, enabling support | ||
for: | ||
- explicitly granting certain users read access to any UserCredential | ||
- implicitly granting any user read access to UserCredentials that were | ||
awarded specifically to them | ||
- denying read access (obscured by HTTP 404) in any other case | ||
NOTE: users are allowed to read their own UserCredential records regardless | ||
of their 'status' (i.e. even if revoked). | ||
WARNING: this permissions implementation does not cover the 'list' method | ||
of access. The access control required under DRF for that use case is | ||
presently implemented in the `list` method of the viewset itself. | ||
""" | ||
|
||
# refer to the super() for more context on what this override is doing. | ||
perms_map = permissions.DjangoModelPermissions.perms_map | ||
perms_map.update({method: ['%(app_label)s.view_%(model_name)s'] for method in permissions.SAFE_METHODS}) | ||
|
||
def has_permission(self, request, view): | ||
""" | ||
Relax the base's view-level permissions in the case of 'safe' | ||
(read-only) methods, requiring only that the user be authenticated. | ||
This lets us delay deciding whether or not read permission should be | ||
implicitly granted, until after DRF has fetched the requested object. | ||
""" | ||
return super(UserCredentialViewSetPermissions, self).has_permission(request, view) or ( | ||
request.user.is_authenticated() and request.method in permissions.SAFE_METHODS | ||
) | ||
|
||
def has_object_permission(self, request, view, obj): | ||
""" | ||
Allow access to specific objects when granted explicitly (via model | ||
permissions) or, if a read-only request, implicitly (via matching | ||
username). | ||
""" | ||
if super(UserCredentialViewSetPermissions, self).has_permission(request, view): | ||
return True | ||
elif request.user.username.lower() == obj.username.lower(): | ||
return True | ||
else: | ||
raise Http404 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
credentials/apps/core/migrations/0003_auto_20160331_0218.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# -*- coding: utf-8 -*- | ||
from __future__ import unicode_literals | ||
|
||
from django.contrib.auth.models import Group, Permission | ||
from django.contrib.contenttypes.models import ContentType | ||
from django.core.exceptions import ObjectDoesNotExist | ||
from django.db import migrations | ||
|
||
from credentials.apps.core.constants import Role | ||
|
||
|
||
def create_view_permission(apps, schema_editor): | ||
""" | ||
Add an explicit view permission for UserCredential, and associate it with | ||
the ADMIN role. | ||
""" | ||
content_type = ContentType.objects.get(app_label="credentials", model="usercredential") | ||
permission, created = Permission.objects.get_or_create( | ||
content_type=content_type, | ||
codename='view_usercredential', | ||
name='Can view any user credential', | ||
) | ||
if created: | ||
Group.objects.get(name=Role.ADMINS).permissions.add(permission) | ||
|
||
|
||
def destroy_view_permission(apps, schema_editor): | ||
""" | ||
Remove the view permission, if it exists. Note that the permission will | ||
automatically be removed from any roles to which it had been linked. | ||
""" | ||
try: | ||
content_type = ContentType.objects.get(app_label='credentials', model='usercredential') | ||
permission = Permission.objects.get(content_type=content_type, codename='view_usercredential') | ||
permission.delete() | ||
except ObjectDoesNotExist: | ||
pass | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('core', '0002_auto_20160111_1251'), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython(code=create_view_permission, reverse_code=destroy_view_permission), | ||
] |
Oops, something went wrong.