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