Files
edx-platform/lms/djangoapps/oauth2_handler/handlers.py
Carlos Andrés Rocha 9cd26e551a List all courses during OAut2/OpenID Connect
Previously, only courses respective to the branded instance were returned.

AN-3610
2014-10-03 14:15:41 -04:00

200 lines
5.9 KiB
Python

""" Handlers for OpenID Connect provider. """
from django.conf import settings
from courseware.access import has_access
from student.models import anonymous_id_for_user
from student.models import UserProfile
from user_api.models import UserPreference
from lang_pref import LANGUAGE_KEY
from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor
class OpenIDHandler(object):
""" Basic OpenID Connect scope handler. """
def scope_openid(self, _data):
""" Only override the sub (subject) claim. """
return ['sub']
def claim_sub(self, data):
"""
Return the value of the sub (subject) claim. The value should be
unique for each user.
"""
# Use the anonymous ID without any course as unique identifier.
# Note that this ID is derived using the value of the `SECRET_KEY`
# setting, this means that users will have different sub
# values for different deployments.
value = anonymous_id_for_user(data['user'], None)
return value
class ProfileHandler(object):
""" Basic OpenID Connect `profile` scope handler with `locale` claim. """
def scope_profile(self, _data):
""" Add specialized claims. """
return ['name', 'locale']
def claim_name(self, data):
""" User displayable full name. """
user = data['user']
profile = UserProfile.objects.get(user=user)
return profile.name
def claim_locale(self, data):
"""
Return the locale for the users based on their preferences.
Does not return a value if the users have not set their locale preferences.
"""
language = UserPreference.get_preference(data['user'], LANGUAGE_KEY)
# If the user has no language specified, return the default one.
if not language:
language = getattr(settings, 'LANGUAGE_CODE')
return language
class CourseAccessHandler(object):
"""
Defines two new scopes: `course_instructor` and `course_staff`. Each one is
valid only if the user is instructor or staff of at least one course.
Each new scope has a corresponding claim: `instructor_courses` and
`staff_courses` that lists the course_ids for which the user as instructor
or staff privileges.
The claims support claim request values. In other words, if no claim is
requested it returns all the courses for the corresponding privileges. If a
claim request is used, then it only returns the from the list of requested
values that have the corresponding privileges.
For example, if the user is staff of course_a and course_b but not
course_c, the request:
scope = openid course_staff
will return:
{staff_courses: [course_a, course_b] }
If the request is:
claims = {userinfo: {staff_courses=[course_b, course_d]}}
the result will be:
{staff_courses: [course_b] }.
This is useful to quickly determine if a user has the right
privileges for a given course.
For a description of the function naming and arguments, see:
`oauth2_provider/oidc/handlers.py`
"""
def scope_course_instructor(self, data):
"""
Scope `course_instructor` valid only if the user is an instructor
of at least one course.
"""
course_ids = self._courses_with_access_type(data, 'instructor')
return ['instructor_courses'] if course_ids else None
def scope_course_staff(self, data):
"""
Scope `course_staff` valid only if the user is an instructor of at
least one course.
"""
course_ids = self._courses_with_access_type(data, 'staff')
return ['staff_courses'] if course_ids else None
def claim_instructor_courses(self, data):
"""
Claim `instructor_courses` with list of course_ids for which the
user has instructor privileges.
"""
return self._courses_with_access_type(data, 'instructor')
def claim_staff_courses(self, data):
"""
Claim `staff_courses` with list of course_ids for which the user
has staff privileges.
"""
return self._courses_with_access_type(data, 'staff')
def _courses_with_access_type(self, data, access_type):
"""
Utility function to list all courses for a user according to the
access type.
The field `data` follows the handler specification in:
`oauth2_provider/oidc/handlers.py`
"""
user = data['user']
values = set(data.get('values', []))
courses = _get_all_courses()
courses = (c for c in courses if has_access(user, access_type, c))
course_ids = (unicode(c.id) for c in courses)
# If values was provided, return only the requested authorized courses
if values:
return [c for c in course_ids if c in values]
else:
return [c for c in course_ids]
class IDTokenHandler(OpenIDHandler, ProfileHandler, CourseAccessHandler):
"""
Configure the ID Token handler for the LMS.
Note that the values of the claims `instructor_courses` and
`staff_courses` are not included in the ID Token. The rationale is
that for global staff, the list of courses returned could be very
large. Instead they could check for specific courses using the
UserInfo endpoint.
"""
def claim_instructor_courses(self, data):
# Don't return list of courses in ID Tokens
return None
def claim_staff_courses(self, data):
# Don't return list of courses in ID Tokens
return None
class UserInfoHandler(OpenIDHandler, ProfileHandler, CourseAccessHandler):
""" Configure the UserInfo handler for the LMS. """
pass
def _get_all_courses():
"""
Utitilty function to list all available courses.
"""
ms_courses = modulestore().get_courses()
courses = [c for c in ms_courses if isinstance(c, CourseDescriptor)]
return courses