Created
August 20, 2015 14:12
-
-
Save g-cassie/6b9b0f29371677f48659 to your computer and use it in GitHub Desktop.
This is how we are handling complex permissions right now (this is actually a grossly simplified version). The main problem is permissions logic is split between multiple places. It would be nice to consolidate all permissions logic in permissions.py. Additionally it would be nice to make this more composable so you can have AdminPermission and …
This file contains hidden or 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
class Organization(models.Model): | |
name = models.CharField() | |
class Project(models.Model): | |
name = models.CharField() | |
users = models.ManyToManyField('User', related_name='users', through='UserProjectPermission') | |
organization = models.ForeignKey(Organization) | |
class User(models.Model): | |
email = models.EmailField() | |
organization = models.ForeignKey(Organization) | |
projects = models.ManyToManyField('Project', related_name='projects', through='UserProjectPermission') | |
class UserProjectAccess(models.Model): | |
""" | |
Associate a User to a Project and specify their access level. | |
""" | |
VIEWER = 'VW' | |
EDITOR = 'ED' | |
ADMIN = 'AD' | |
ROLE_CHOICES = ((VIEWER, 'Viewing Only'), (EDITOR, 'Editor'), | |
(ADMIN, 'Administrator')) | |
project = models.ForeignKey(Project, related_name='accesses') | |
user = models.ForeignKey(User, related_name='accesses') | |
# a bunch of content types that nest under project | |
class ProjectEvent(models.Model): | |
project = models.ForeignKey(Project) | |
class ProjectListItem(models.Model): | |
project = models.ForeignKey(Project) | |
class ProjectDeadline(models.Model): | |
project = models.ForeignKey(Project) | |
This file contains hidden or 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
class ProjectObjectPermission(permissions.BasePermission): | |
def has_object_permission(self, request, view, obj): | |
""" | |
Allow access if user is trying to: | |
1. view an object for a `Project` to which they have viewer or greater | |
access. | |
2. edit or create an object for a `Project` to which | |
they have edit or greater access. | |
3. delete an object for a `Project` to which they have admin access. | |
4. Do anything to an object for a `Project` which is owned by an `Organization` | |
at which the user is an admin. | |
""" | |
try: | |
acccess = request.user.accesses.get(project=obj.project) | |
except UserProjectAccess.DoesNotExist: | |
if (obj.project.organization == request.user.organization) and user.is_admin: | |
return True # user can do anything becuase they are an org admin | |
return False # user has no affiliation to the project and can do nothing | |
if request.method in permissions.SAFE_METHODS: | |
return True | |
elif request.method == "DELETE": | |
return access.role == UserProjectAccess.ADMIN | |
else: # they are trying to edit or create | |
return access.role in [UserProjectAccess.EDITOR, UserProjectAccess.ADMIN] | |
This file contains hidden or 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
ProjectChild = namedtuple('ProjectChild', 'project') | |
class ProjectObjectViewSet(viewsets.ModelViewSet): | |
""" | |
My dream API would take everything in ProjectObjectViewSet and included it in `ProjectObjectPermission`. | |
""" | |
permissions = (ProjectObjectPermission,) | |
def perform_create(self, serializer, **kwargs): | |
""" | |
A user cannot create stuff in projects they do not have access to. | |
""" | |
obj = ProjectChild(serializer.validated_data['project']) | |
self.check_object_permissions(self.request, obj) | |
instance = serializer.save(**kwargs) | |
return instance | |
def perform_update(self, serializer, **kwargs): | |
""" | |
We use soft deletes (`.is_trashed`) property on every function. As a result we also have custom logic for updates | |
because only certain users can soft delete certain objects. We use some custom permission methods to try to keep it organized. | |
""" | |
pass | |
def get_queryset(self): | |
user = self.request.user | |
query = Q(project__users=user) | |
if user.is_admin: | |
query |= Q(deal__organizations=user.organization) | |
return self.queryset.filter(query).distinct() | |
class ProjectEventViewSet(ProjectObjectViewSet): | |
queryset = ProjectEvent.objects.all() | |
class ProjectListItemViewSet(ProjectObjectViewSet): | |
queryset = ProjectListItem.objects.all() | |
class ProjectDeadlineViewSet(ProjectObjectViewSet): | |
queryset = ProjectDeadline.objects.all() | |
def perform_delete(self): | |
# sometimes we will have custom logic for a specific ProjectObject on a specific aciton | |
# like delete might be possible if a related object is not already deleted. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment