diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/adapters/keycloak.py b/SAS/TMSS/backend/src/tmss/tmssapp/adapters/keycloak.py index 9dc736e8d0396f1d93f24d0d49ae4484fee3ca6b..9d1e8ee55756dec1cdfc40e3e9c51c4a9b2f7e6e 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/adapters/keycloak.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/adapters/keycloak.py @@ -45,7 +45,10 @@ def get_users_by_role_in_project(role, project): returns the list of users that have the specified role in the specified project """ project_persons = get_project_persons() - return project_persons[project][role] + if project in project_persons: + return project_persons[project][role] + else: + return [] @cachetools.func.ttl_cache(ttl=600) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py index effe24b408c55dbd4b1151971f61b079c35d1281..914f7dd9e15d534f567382535576a09064e06323 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/permissions.py @@ -28,7 +28,8 @@ def get_project_roles_for_user(user): try: if user == models.User.objects.get(username='paulus'): return ({'project': 'test_user_is_shared_support', 'role': 'shared_support'}, - {'project': 'test_user_is_contact', 'role': 'contact'}) + {'project': 'test_user_is_contact', 'role': 'contact'}, + {'project': 'test_user_is_friend', 'role': 'friend_of_project'}) except: pass diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py index 752ea356aaa6ec0ba81abe864314030ccbefb7ea..dc897e8d234ba0a9b0acf2c3fd85a310bf224f1a 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py @@ -25,6 +25,7 @@ from lofar.sas.tmss.tmss.tmssapp.viewsets.lofar_viewset import LOFARViewSet, LOF from lofar.sas.tmss.tmss.tmssapp import models from lofar.sas.tmss.tmss.tmssapp import serializers from lofar.sas.tmss.tmss.tmssapp.adapters.reports import create_cycle_report, create_project_report +from lofar.sas.tmss.tmss.tmssapp.adapters.keycloak import get_users_by_role_in_project from django.http import JsonResponse from datetime import datetime @@ -32,7 +33,7 @@ from lofar.common.json_utils import get_default_json_object_for_schema from lofar.common.datetimeutils import formatDatetime from lofar.sas.tmss.tmss.tmssapp.tasks import * from lofar.sas.tmss.tmss.tmssapp.subtasks import * -from lofar.sas.tmss.tmss.tmssapp.viewsets.permissions import TMSSDjangoModelPermissions +from lofar.sas.tmss.tmss.tmssapp.viewsets.permissions import TMSSDjangoModelPermissions, get_project_roles_for_user from django.urls import resolve, get_script_prefix,Resolver404 from rest_framework.filters import OrderingFilter @@ -394,6 +395,28 @@ class ProjectViewSet(LOFARViewSet): result = create_project_report(request, project) return Response(result, status=status.HTTP_200_OK) + @swagger_auto_schema(responses={200: 'List of users that have the "friend of project" role for this project', + 403: 'forbidden'}, + operation_description="Get the list of users that have the 'friend of project' role for this project") + @action(methods=['get'], detail=True, url_name="friend", name="Friend(s) of this project") + def friend(self, request, pk=None): + project = get_object_or_404(models.Project, pk=pk) + result = get_users_by_role_in_project(models.ProjectRole.Choices.FRIEND_OF_PROJECT.value, project.name) + return Response(result, status=status.HTTP_200_OK) + + @swagger_auto_schema(responses={200: 'List of roles that the requesting user has in this project', + 403: 'forbidden'}, + operation_description="Get the list of roles that the requesting user has in this project") + @action(methods=['get'], detail=True, url_name="my_roles", name="My roles in this project") + def my_roles(self, request, pk=None): + project = get_object_or_404(models.Project, pk=pk) + project_roles = get_project_roles_for_user(request.user) + result = [] + for project_role in project_roles: + if project.name == project_role['project']: + result.append(project_role['role']) + return Response(result, status=status.HTTP_200_OK) + class ProjectNestedViewSet(LOFARNestedViewSet): queryset = models.Project.objects.all() diff --git a/SAS/TMSS/backend/test/t_permissions_project_roles.py b/SAS/TMSS/backend/test/t_permissions_project_roles.py index 40ec90a4850a149636986c3eccbedb49c606b6bb..0c42f025fdbfe5f87da61aad84752754d442c820 100755 --- a/SAS/TMSS/backend/test/t_permissions_project_roles.py +++ b/SAS/TMSS/backend/test/t_permissions_project_roles.py @@ -51,6 +51,9 @@ from lofar.sas.tmss.test.tmss_test_data_rest import TMSSRESTTestDataCreator from django.test import TestCase +from django.contrib.auth import get_user_model +User = get_user_model() + class ProjectPermissionTestCase(TestCase): # This tests that the project permissions are enforced in light of the project roles that are externally provided # for the user through the user admin. This test does not rely on the project permissions as defined in the system, @@ -66,7 +69,8 @@ class ProjectPermissionTestCase(TestCase): cls.project_permission_patcher = mock.patch('lofar.sas.tmss.tmss.tmssapp.viewsets.get_project_roles_for_user') # todo: fix namespace so we get the get_project_roles that gets actually used cls.project_permission_mock = cls.project_permission_patcher.start() cls.project_permission_mock.return_value = ({'project': 'test_user_is_shared_support', 'role': 'shared_support'}, - {'project': 'test_user_is_contact', 'role': 'contact_author'}) + {'project': 'test_user_is_contact', 'role': 'contact_author'}, + {'project': 'test_user_is_friend', 'role': 'friend_of_project'}) # create some stuff as the standard super user, as setup for the tests below cls.test_data_creator = TMSSRESTTestDataCreator(BASE_URL, AUTH) @@ -75,8 +79,9 @@ class ProjectPermissionTestCase(TestCase): # create projects with magic names for which permission exists (or which have no whitelisted generic name) cls.project_shared_support_url = cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.Project(name='test_user_is_shared_support', cycle_urls=[cycle_url]), '/project/') cls.project_contact_url = cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.Project(name='test_user_is_contact', cycle_urls=[cycle_url]), '/project/') + cls.project_friend_url = cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.Project(name='test_user_is_friend', cycle_urls=[cycle_url]), '/project/') cls.project_forbidden_url = cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.Project(name='forbidden', cycle_urls=[cycle_url]), '/project/') - + cls.project_keycloak_url = cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.Project(name="2014LOFAROBS", cycle_urls=[cycle_url]), '/project/') # project is known to Keycloak # user is shared_support cls.scheduling_set_shared_support_url = cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.SchedulingSet(project_url=cls.project_shared_support_url), '/scheduling_set/') @@ -92,8 +97,10 @@ class ProjectPermissionTestCase(TestCase): # create the required permission entries to control what endpoint action requires which project role shared_support_role_url = BASE_URL + '/project_role/shared_support/' + friend_of_project_role_url = BASE_URL + '/project_role/friend_of_project/' cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.ProjectPermission(name='taskdraft', GET=[shared_support_role_url], POST=[shared_support_role_url]), '/project_permission/') cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.ProjectPermission(name='taskdraft-create_task_blueprint', POST=[shared_support_role_url]), '/project_permission/') + cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.ProjectPermission(name='project-my_roles', GET=[shared_support_role_url, friend_of_project_role_url]), '/project_permission/') cls.task_template_url = cls.test_data_creator.post_data_and_get_url(cls.test_data_creator.TaskTemplate(), '/task_template/') @@ -230,6 +237,37 @@ class ProjectPermissionTestCase(TestCase): # todo: add tests for other models with project permissions + def test_project_get_friend_returns_correct_user(self): + """ + Note: This test relies on real data from Keycloak. + """ + r = GET_and_assert_equal_expected_code(self, self.project_keycloak_url + '/friend/', 200) + self.assertEqual(len(r), 2) + for friend in r: + # Todo: find a way to mock the Keycloak response so we can assert more strictly. + self.assertTrue(friend.endswith('@astron.nl')) # redacted expected full email due to GDPR + + def test_project_get_friend_returns_403_if_no_permission_for_project(self): + + r = GET_and_assert_equal_expected_code(self, self.project_forbidden_url + '/my_roles/', 403, auth=self.auth) + self.assertIn('permission', str(r)) + + + def test_project_get_my_roles_returns_correct_roles(self): + + # r = GET_and_assert_equal_expected_code(self, self.project_shared_support_url + '/my_roles/', 200, auth=self.auth) + # expected_reply = ['shared_support'] + # self.assertEqual(expected_reply, r) + + r = GET_and_assert_equal_expected_code(self, self.project_friend_url + '/my_roles/', 200, auth=self.auth) + expected_reply = ['friend_of_project'] + self.assertEqual(expected_reply, r) + + def test_project_get_my_roles_returns_403_if_no_permission_for_project(self): + + r = GET_and_assert_equal_expected_code(self, self.project_forbidden_url + '/my_roles/', 403, auth=self.auth) + self.assertIn('permission', str(r)) + if __name__ == "__main__": logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',