Skip to content

Commit

Permalink
Merge pull request #67 from multinet-app/permissions-current-user
Browse files Browse the repository at this point in the history
Create endpoint for retrieving permissions of current user
  • Loading branch information
naglepuff authored Aug 20, 2021
2 parents 686a330 + aa6f4e5 commit 570b863
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 3 deletions.
20 changes: 20 additions & 0 deletions multinet/api/migrations/0008_alter_workspacerole_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 3.2.5 on 2021-08-20 14:22

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0007_auto_20210812_1630'),
]

operations = [
migrations.AlterField(
model_name='workspacerole',
name='role',
field=models.PositiveSmallIntegerField(
choices=[(1, 'reader'), (2, 'writer'), (3, 'maintainer')]
),
),
]
32 changes: 29 additions & 3 deletions multinet/api/models/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def create_default_arango_db_name():


class WorkspaceRoleChoice(models.IntegerChoices):
READER = 1
WRITER = 2
MAINTAINER = 3
READER = 1, 'reader'
WRITER = 2, 'writer'
MAINTAINER = 3, 'maintainer'


class WorkspaceRole(TimeStampedModel):
Expand Down Expand Up @@ -81,6 +81,32 @@ def get_user_permission(self, user: User) -> Optional[WorkspaceRole]:
"""Get the WorkspaceRole for a given user on this workspace."""
return WorkspaceRole.objects.filter(workspace=self.pk, user=user.pk).first()

def get_user_permission_label(self, user: User) -> Optional[str]:
"""
Get a the string label of a user's workspace role, or None.
This function differs from Workspace::get_user_permisssion. Whereas that
function returns a WorkspaceRole object describing a user's permission for
a particular workspace, this function returns a human-readable representation
of that permission level. Since ownership of a workspace is modeled by a property
on that workspace, this function takes that into account, and returns 'owner' if
the given user is the owner of this workspace. Moreover, even if a user does not
have explicit permission via the ownersip relationship or a WorkspaceRole object,
they are still considered a reader if this workspace is public. Therefore, if this
workspace is public and the user passed in has no explicit permission, 'reader' will
be returned.
"""
if self.owner == user:
return 'owner'

workspace_role = self.get_user_permission(user)
if workspace_role is None:
if self.public:
return WorkspaceRoleChoice.READER.label
return None
else:
return WorkspaceRoleChoice(workspace_role.role).label

def set_user_permission(self, user: User, permission: WorkspaceRoleChoice) -> bool:
"""
Set a user role for this workspace.
Expand Down
50 changes: 50 additions & 0 deletions multinet/api/tests/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,53 @@ def test_workspace_rest_put_permissions(
else:
assert workspace.public is False
assert workspace.owner == old_owner


@pytest.mark.django_db
@pytest.mark.parametrize(
'permission,is_owner,status_code,success',
[
(None, False, 404, False),
(WorkspaceRoleChoice.READER, False, 200, True),
(WorkspaceRoleChoice.WRITER, False, 200, True),
(WorkspaceRoleChoice.MAINTAINER, False, 200, True),
(None, True, 200, True),
],
)
def test_workspace_rest_get_user_permission(
workspace: Workspace,
user: User,
authenticated_api_client: APIClient,
permission: WorkspaceRoleChoice,
is_owner: bool,
status_code: int,
success: bool,
):
if permission is not None:
workspace.set_user_permission(user, permission)
elif is_owner:
workspace.set_owner(user)

r = authenticated_api_client.get(f'/api/workspaces/{workspace.name}/permissions/me/')
assert r.status_code == status_code

if success:
assert r.data == {
'username': user.username,
'workspace': workspace.name,
'permission': workspace.get_user_permission_label(user),
}


@pytest.mark.django_db
def test_workspace_rest_get_user_permission_public(
public_workspace_factory: PublicWorkspaceFactory, api_client: APIClient
):
workspace = public_workspace_factory()
r = api_client.get(f'/api/workspaces/{workspace.name}/permissions/me/')
assert r.status_code == 200
assert r.data == {
'username': '', # anonymous user
'workspace': workspace.name,
'permission': WorkspaceRoleChoice.READER.label,
}
7 changes: 7 additions & 0 deletions multinet/api/views/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ class Meta:
]


class SingleUserWorkspacePermissionSerializer(serializers.Serializer):
# Allow empty username since anonymous user is a reader for public workspaces
username = serializers.CharField(validators=[UnicodeUsernameValidator()], allow_blank=True)
workspace = serializers.CharField()
permission = serializers.CharField(allow_blank=True, allow_null=True)


# The required fields for table creation
class TableCreateSerializer(serializers.ModelSerializer):
class Meta:
Expand Down
15 changes: 15 additions & 0 deletions multinet/api/views/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from multinet.api.views.serializers import (
PermissionsCreateSerializer,
PermissionsReturnSerializer,
SingleUserWorkspacePermissionSerializer,
WorkspaceCreateSerializer,
WorkspaceRenameSerializer,
WorkspaceSerializer,
Expand Down Expand Up @@ -109,6 +110,20 @@ def get_workspace_permissions(self, request, name: str):
serializer = PermissionsReturnSerializer(workspace)
return Response(serializer.data, status=status.HTTP_200_OK)

@swagger_auto_schema(responses={200: SingleUserWorkspacePermissionSerializer()})
@action(detail=True, url_path='permissions/me')
@require_workspace_permission(WorkspaceRoleChoice.READER)
def get_current_user_workspace_permissions(self, request, name: str):
"""Get the workspace permission for the user of the request."""
workspace: Workspace = get_object_or_404(Workspace, name=name)
user = request.user
permission = workspace.get_user_permission_label(user)

data = {'username': user.username, 'workspace': name, 'permission': permission}
serializer = SingleUserWorkspacePermissionSerializer(data=data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)

def build_user_list(self, validated_data: OrderedDict) -> list:
"""
Build a list of user objects from an ordered dictionary of user data.
Expand Down

0 comments on commit 570b863

Please sign in to comment.