From 0fb7c101c703d1bb52751dadc47139a723b9acd4 Mon Sep 17 00:00:00 2001 From: jansenk <jkantor@edx.org> Date: Tue, 11 Dec 2018 13:34:14 -0500 Subject: [PATCH] Add API call to list roles of the logged-on user EDUCATOR-3796 --- common/djangoapps/enrollment/api.py | 9 +++ common/djangoapps/enrollment/data.py | 14 ++++ .../djangoapps/enrollment/tests/test_data.py | 18 ++++- .../djangoapps/enrollment/tests/test_views.py | 70 +++++++++++++++++++ common/djangoapps/enrollment/urls.py | 3 +- common/djangoapps/enrollment/views.py | 49 +++++++++++++ 6 files changed, 161 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py index 57035d4bc2d..f0c86412b04 100644 --- a/common/djangoapps/enrollment/api.py +++ b/common/djangoapps/enrollment/api.py @@ -466,6 +466,15 @@ def unenroll_user_from_all_courses(user_id): return _data_api().unenroll_user_from_all_courses(user_id) +def get_user_roles(user_id): + """ + Returns a list of all roles that this user has. + :param user_id: The id of the selected user. + :return: All roles for all courses that this user has. + """ + return _data_api().get_user_roles(user_id) + + def _data_api(): """Returns a Data API. This relies on Django settings to find the appropriate data API. diff --git a/common/djangoapps/enrollment/data.py b/common/djangoapps/enrollment/data.py index 033a1380ce7..f6ec0d710e3 100644 --- a/common/djangoapps/enrollment/data.py +++ b/common/djangoapps/enrollment/data.py @@ -27,6 +27,7 @@ from student.models import ( EnrollmentClosedError, NonExistentCourseError ) +from student.roles import RoleCache log = logging.getLogger(__name__) @@ -337,3 +338,16 @@ def get_course_enrollment_info(course_id, include_expired=False): raise CourseNotFoundError(msg) else: return CourseSerializer(course, include_expired=include_expired).data + + +def get_user_roles(user_id): + """ + Returns a list of all roles that this user has. + :param user_id: The id of the selected user. + :return: All roles for all courses that this user has. + """ + user = _get_user(user_id) + if not hasattr(user, '_roles'): + user._roles = RoleCache(user) + role_cache = user._roles + return role_cache._roles diff --git a/common/djangoapps/enrollment/tests/test_data.py b/common/djangoapps/enrollment/tests/test_data.py index 9009fb121e1..475070f57a1 100644 --- a/common/djangoapps/enrollment/tests/test_data.py +++ b/common/djangoapps/enrollment/tests/test_data.py @@ -23,7 +23,7 @@ from enrollment.errors import ( from enrollment.serializers import CourseEnrollmentSerializer from openedx.core.lib.exceptions import CourseNotFoundError from student.models import AlreadyEnrolledError, CourseEnrollment, CourseFullError, EnrollmentClosedError -from student.tests.factories import UserFactory +from student.tests.factories import UserFactory, CourseAccessRoleFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -378,3 +378,19 @@ class EnrollmentDataTest(ModuleStoreTestCase): if not include_expired: self.assertNotIn('verified', result_slugs) + + def test_get_roles(self): + """Create a role for a user, then get it""" + expected_role = CourseAccessRoleFactory.create(course_id=self.course.id, user=self.user, role="SuperCoolTestRole") + roles = data.get_user_roles(self.user.username) + self.assertEqual(roles, {expected_role}) + + def test_get_roles_no_roles(self): + """Get roles for a user who has no roles""" + roles = data.get_user_roles(self.user.username) + self.assertEqual(roles, set()) + + def test_get_roles_invalid_user(self): + """Get roles for a user that doesn't exist""" + with pytest.raises(UserNotFoundError): + data.get_user_roles("i_dont_exist_and_should_raise_an_error") diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index bc70c209286..f1ea9cc95d1 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -45,6 +45,7 @@ from student.roles import CourseStaffRole from student.tests.factories import AdminFactory, UserFactory, SuperuserFactory from util.models import RateLimitConfiguration from util.testing import UrlResetMixin +from six import text_type class EnrollmentTestMixin(object): @@ -1413,3 +1414,72 @@ class UnenrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase): url = reverse('unenrollment') headers = self.build_jwt_headers(submitting_user) return self.client.post(url, json.dumps(data), content_type='application/json', **headers) + + +@ddt.ddt +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +class UserRoleTest(ModuleStoreTestCase): + """ + Tests the API call to list user roles. + """ + USERNAME = "Bob" + EMAIL = "bob@example.com" + PASSWORD = "edx" + + ENABLED_CACHES = ['default'] + + def setUp(self): + """ Create a course and user, then log in. """ + super(UserRoleTest, self).setUp() + self.course1 = CourseFactory.create(emit_signals=True, org="org1", course="course1", run="run1") + self.course2 = CourseFactory.create(emit_signals=True, org="org2", course="course2", run="run2") + self.user = UserFactory.create( + username=self.USERNAME, + email=self.EMAIL, + password=self.PASSWORD, + ) + self.client.login(username=self.USERNAME, password=self.PASSWORD) + + def _create_expected_role_dict(self, course, role): + return { + 'course_id': text_type(course.id), + 'org': course.org, + 'role': role.ROLE, + } + + def _assert_roles(self, expected_response): + response = self.client.get(reverse('roles')) + self.assertEqual(response.status_code, status.HTTP_200_OK) + response_data = json.loads(response.content) + self.assertEqual(response_data, expected_response) + + def test_not_logged_in(self): + self.client.logout() + response = self.client.get(reverse('roles')) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_roles_no_roles(self): + self._assert_roles([]) + + def test_roles(self): + role1 = CourseStaffRole(self.course1.id) + role1.add_users(self.user) + expected_role1 = self._create_expected_role_dict(self.course1, role1) + self._assert_roles([expected_role1]) + role2 = CourseStaffRole(self.course2.id) + role2.add_users(self.user) + expected_role2 = self._create_expected_role_dict(self.course2, role2) + self._assert_roles([expected_role2, expected_role1]) + + def test_roles_exception(self): + with patch('enrollment.api.get_user_roles') as mock_get_user_roles: + mock_get_user_roles.side_effect = Exception() + response = self.client.get(reverse('roles')) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + expected_response = { + "message": ( + u"An error occurred while retrieving roles for user '{username}" + ).format(username=self.user.username) + } + response_data = json.loads(response.content) + self.assertEqual(response_data, expected_response) diff --git a/common/djangoapps/enrollment/urls.py b/common/djangoapps/enrollment/urls.py index da67d9a3e4a..4d465ae16d8 100644 --- a/common/djangoapps/enrollment/urls.py +++ b/common/djangoapps/enrollment/urls.py @@ -5,7 +5,7 @@ URLs for the Enrollment API from django.conf import settings from django.conf.urls import url -from .views import EnrollmentCourseDetailView, EnrollmentListView, EnrollmentView, UnenrollmentView +from .views import EnrollmentCourseDetailView, EnrollmentListView, EnrollmentView, UnenrollmentView, EnrollmentUserRolesView urlpatterns = [ url(r'^enrollment/{username},{course_key}$'.format( @@ -18,4 +18,5 @@ urlpatterns = [ url(r'^course/{course_key}$'.format(course_key=settings.COURSE_ID_PATTERN), EnrollmentCourseDetailView.as_view(), name='courseenrollmentdetails'), url(r'^unenroll/$', UnenrollmentView.as_view(), name='unenrollment'), + url(r'^roles/$', EnrollmentUserRolesView.as_view(), name='roles'), ] diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index f502fde54fe..919cbc1893d 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -205,6 +205,55 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn): ) +class EnrollmentUserRolesView(APIView): + """ + **Use Case** + + Get the roles for the current logged-in user. + + **Example Requests** + + GET /api/enrollment/v1/roles/ + + **Response Values** + + If the request is successful, an HTTP 200 "OK" response is + returned along with a collection of user roles for the + logged-in user + + """ + authentication_classes = (JwtAuthentication, + OAuth2AuthenticationAllowInactiveUser, + EnrollmentCrossDomainSessionAuth) + permission_classes = ApiKeyHeaderPermissionIsAuthenticated, + throttle_classes = EnrollmentUserThrottle, + + @method_decorator(ensure_csrf_cookie_cross_domain) + def get(self, request): + """ + Gets a list of all roles for the currently logged-in user + """ + try: + roles_data = api.get_user_roles(request.user.username) + except Exception: + return Response( + status=status.HTTP_400_BAD_REQUEST, + data={ + "message": ( + u"An error occurred while retrieving roles for user '{username}" + ).format(username=request.user.username) + } + ) + return Response([ + { + "org": role.org, + "course_id": text_type(role.course_id), + "role": role.role + } + for role in roles_data + ]) + + @can_disable_rate_limit class EnrollmentCourseDetailView(APIView): """ -- GitLab