Skip to content

Commit

Permalink
Merge pull request #172 from avantifellows/feature/user-role-readonly
Browse files Browse the repository at this point in the history
Superuser fix and organization-user role check
  • Loading branch information
dalmia authored May 28, 2021
2 parents e9b39a1 + bc2f5dd commit 6bb7036
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ Visit the [REST API guide](docs/REST-API.md) to connect your Plio's frontend set
- [Zappa Settings](docs/ZAPPA-SETTINGS.md)
- [Soft Delete](docs/SOFT-DELETE.md)
- [Google OAuth2](docs/oauth/GOOGLE-OAUTH2.md)

## Product Guide
To know more about the features and updates on Plio, visit the [product guide on our YouTube channel](https://www.youtube.com/playlist?list=PL3U0Jqw-piJgw2hSpuAZym4K1_Tb0RTRV).
10 changes: 10 additions & 0 deletions users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ def name(self):
def __str__(self):
return "%d: %s" % (self.id, self.name)

def get_role_for_organization(self, organization_id):
"""Returns the user's role within the organization provided (None if the user is not a part)"""
organization_user = OrganizationUser.objects.filter(
organization_id=organization_id, user_id=self.id
).first()
if not organization_user:
return None

return organization_user.role


class UserMeta(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE
Expand Down
67 changes: 67 additions & 0 deletions users/permissions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from rest_framework import permissions
from users.models import Role


class UserPermission(permissions.BasePermission):
Expand All @@ -17,3 +18,69 @@ def has_permission(self, request, view):
def has_object_permission(self, request, view, obj):
"""Object-level permissions for user. This determines whether the request can access a user instance or not."""
return request.user.is_superuser or request.user == obj


class OrganizationUserPermission(permissions.BasePermission):
"""
Permission check for organization-user mapping.
"""

def has_permission(self, request, view):
"""View-level permissions for organization-user. This determines whether the request can access organization-user instances or not."""

if request.user.is_superuser:
return True

# when listing, a user should only see organization-user mapping for the organizations they have access to.
# this is handled by get_queryset
if view.action in ["list", "retrieve", "destroy"]:
return True

user_organization_role = request.user.get_role_for_organization(
request.data["organization"]
)
if not user_organization_role or user_organization_role.name not in [
"org-admin",
"super-admin",
]:
# user doesn't belong to the queried organization
# or doesn't have sufficient role within organization
return False

if "role" not in request.data:
return False

requested_role = Role.objects.filter(id=request.data["role"]).first()

# super-admins can add users with org-admin and org-view roles to their organization
if user_organization_role.name == "super-admin":
return requested_role.name in ["org-admin", "org-view"]

# or-admins can add users with org-view role to their organization
if user_organization_role.name == "org-admin":
return requested_role.name == "org-view"

return False

def has_object_permission(self, request, view, obj):
"""Object-level permissions for organization-user. This determines whether the request can access an organization-user instance or not."""
if request.user.is_superuser:
return True

if view.action == "retrieve":
return True

if view.action == "destroy":
organization_id = obj.organization_id
else:
organization_id = request.data["organization"]

user_organization_role = request.user.get_role_for_organization(organization_id)

# super-admins can add users with org-admin and org-view roles to their organization
if user_organization_role.name == "super-admin":
return obj.role.name in ["org-admin", "org-view"]

# org-admins can add users with org-view role to their organization
if user_organization_role.name == "org-admin":
return obj.role.name == "org-view"
1 change: 1 addition & 0 deletions users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Meta:
"status",
]
extra_kwargs = {"password": {"write_only": True}}
read_only_fields = ["is_superuser", "is_staff"]

def to_representation(self, instance):
response = super().to_representation(instance)
Expand Down
22 changes: 21 additions & 1 deletion users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

from users.models import User, OneTimePassword, OrganizationUser
from users.serializers import UserSerializer, OtpSerializer, OrganizationUserSerializer
from users.permissions import UserPermission
from users.permissions import UserPermission, OrganizationUserPermission

from .services import SnsService
import requests
Expand All @@ -43,6 +43,7 @@ class UserViewSet(viewsets.ModelViewSet):
create: Create a user
partial_update: Patch a user
destroy: Soft delete a user
config: Retrieve or update user config
"""

permission_classes = [IsAuthenticated, UserPermission]
Expand Down Expand Up @@ -92,9 +93,28 @@ class OrganizationUserViewSet(viewsets.ModelViewSet):
destroy: Soft delete an organization user
"""

permission_classes = [IsAuthenticated, OrganizationUserPermission]
queryset = OrganizationUser.objects.all()
serializer_class = OrganizationUserSerializer

def get_queryset(self):
# get all organizations where the current user is a super-admin or org-admin
if self.request.user.is_superuser:
return OrganizationUser.objects.all()

user_organizations = OrganizationUser.objects.filter(
user=self.request.user, role__name__in=["super-admin", "org-admin"]
).all()

# get the array of organization ids
organization_ids = [
user_organization.organization_id
for user_organization in user_organizations
]

# return instances that falls under the organization ids
return OrganizationUser.objects.filter(organization__in=organization_ids)


@api_view(["POST"])
@permission_classes([AllowAny])
Expand Down

0 comments on commit 6bb7036

Please sign in to comment.