From cb22822340258c1fc2990a12441b2489c0fbc3b1 Mon Sep 17 00:00:00 2001 From: sendr Date: Fri, 7 Aug 2015 10:51:26 -0400 Subject: [PATCH 01/13] Add staff mode to API --- common/djangoapps/enrollment/api.py | 4 ++-- common/djangoapps/enrollment/data.py | 9 +++++---- common/djangoapps/enrollment/views.py | 11 +++++++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py index 2345b025f5f9..febffe3f05d3 100644 --- a/common/djangoapps/enrollment/api.py +++ b/common/djangoapps/enrollment/api.py @@ -14,7 +14,7 @@ DEFAULT_DATA_API = 'enrollment.data' -def get_enrollments(user_id): +def get_enrollments(user_id=None, **kwargs): """Retrieves all the courses a user is enrolled in. Takes a user and retrieves all relative enrollments. Includes information regarding how the user is enrolled @@ -84,7 +84,7 @@ def get_enrollments(user_id): ] """ - return _data_api().get_course_enrollments(user_id) + return _data_api().get_course_enrollments(user_id, **kwargs) def get_enrollment(user_id, course_id): diff --git a/common/djangoapps/enrollment/data.py b/common/djangoapps/enrollment/data.py index ce05cea6c32a..f3543c61335f 100644 --- a/common/djangoapps/enrollment/data.py +++ b/common/djangoapps/enrollment/data.py @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) -def get_course_enrollments(user_id): +def get_course_enrollments(user_id=None, **kwargs): """Retrieve a list representing all aggregated data for a user's course enrollments. Construct a representation of all course enrollment data for a specific user. @@ -32,9 +32,10 @@ def get_course_enrollments(user_id): A serializable list of dictionaries of all aggregated enrollment data for a user. """ - qset = CourseEnrollment.objects.filter( - user__username=user_id, is_active=True - ).order_by('created') + qset = CourseEnrollment.objects.filter(is_active=True, **kwargs) + if user_id is not None: + qset.filter(user__username=user_id) + qset.order_by('created') return CourseEnrollmentSerializer(qset).data # pylint: disable=no-member diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index 8cfb06cc348a..7962ff4a4766 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -346,12 +346,19 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): @method_decorator(ensure_csrf_cookie_cross_domain) def get(self, request): """Gets a list of all course enrollments for the currently logged in user.""" - username = request.GET.get('user', request.user.username) - if request.user.username != username and not self.has_api_key_permissions(request): + username = request.GET.get('user', request.user.is_staff and None or request.user.username) + try: + course_key = CourseKey.from_string(request.GET.get('course')) + except InvalidKeyError: + course_key = None + + if (not request.user.is_staff and request.user.username != username) and not self.has_api_key_permissions(request): # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) try: + if course_key: + return Response(api.get_enrollments(username, course_id=course_key)) return Response(api.get_enrollments(username)) except CourseEnrollmentError: return Response( From afad5c5037bf8e944c25812cf363a9bba4403d38 Mon Sep 17 00:00:00 2001 From: sendr Date: Thu, 13 Aug 2015 09:15:23 -0400 Subject: [PATCH 02/13] Add update object signals --- cms/djangoapps/contentstore/signals.py | 37 +++++++++++++++++++ common/djangoapps/student/models.py | 51 +++++++++++++++++++++++++- lms/templates/navigation.html | 2 +- lms/urls.py | 1 + 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/signals.py b/cms/djangoapps/contentstore/signals.py index 8ada95bce1e1..f2e75738505e 100644 --- a/cms/djangoapps/contentstore/signals.py +++ b/cms/djangoapps/contentstore/signals.py @@ -1,12 +1,18 @@ """ receivers of course_published and library_updated events in order to trigger indexing task """ from datetime import datetime from pytz import UTC +import requests +import os +import logging from django.dispatch import receiver +from django.conf import settings from xmodule.modulestore.django import SignalHandler from contentstore.courseware_index import CoursewareSearchIndexer, LibrarySearchIndexer +from courseware.courses import get_course +log = logging.getLogger('course_signals') @receiver(SignalHandler.course_published) def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument @@ -28,3 +34,34 @@ def listen_for_library_update(sender, library_key, **kwargs): # pylint: disable from .tasks import update_library_index if LibrarySearchIndexer.indexing_is_enabled(): update_library_index.delay(unicode(library_key), datetime.now(UTC).isoformat()) + +@receiver(SignalHandler.course_published) +def push_objects_to_sso(sender, course_key, **kwargs): + if not hasattr(settings, 'SSO_API_URL'): + log.error('settings.SSO_API_URL is not defined') + return + + if not hasattr(settings, 'SSO_API_TOKEN'): + log.error('SSO_API_TOKEN is not defined') + return + + url = os.path.join(settings.SSO_API_URL, 'course/') + headers = {'Authorization': 'Token {}'.format(settings.SSO_API_TOKEN)} + course = get_course(course_key) + name = course.name or course_key.run + start = course.start and datetime.strftime(course.start, '%Y-%m-%dT%H:%M:%SZ') or None + end = course.end and datetime.strftime(course.end, '%Y-%m-%dT%H:%M:%SZ') or None + data = { + 'name': name, + 'course_id': course_key.html_id(), + 'start': start, + 'end': end, + 'org': course.org, + 'run': course_key.run, + } + + r = requests.post(url, headers=headers, data=data) + + if r.ok: + return r.text + log.error('API "{}" returned: {}'.format(url, r.status_code)) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 2c1cc1bb2c1f..8f006a1a3a39 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -10,6 +10,7 @@ 2. ./manage.py lms schemamigration student --auto description_of_your_change 3. Add the migration file created in edx-platform/common/djangoapps/student/migrations/ """ +import os from datetime import datetime, timedelta import hashlib import json @@ -19,6 +20,7 @@ from collections import defaultdict, OrderedDict import dogstats_wrapper as dog_stats_api from urllib import urlencode +import requests from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -28,7 +30,7 @@ from django.contrib.auth.signals import user_logged_in, user_logged_out from django.db import models, IntegrityError from django.db.models import Count -from django.db.models.signals import pre_save, post_save +from django.db.models.signals import pre_save, post_save, post_delete from django.dispatch import receiver, Signal from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext_noop @@ -1854,3 +1856,50 @@ def __unicode__(self): name=self.name, value=self.value, ) + + +sso_api_url = os.path.join(settings.SSO_API_URL, 'enrollment/') +sso_api_headers = {'Authorization': 'Token {}'.format(settings.SSO_API_TOKEN)} + +@receiver(post_save, sender=CourseEnrollment) +def push_enrollment_to_sso(sender, instance, **kwargs): + if not hasattr(settings, 'SSO_API_URL'): + log.error('settings.SSO_API_URL is not defined') + return + + if not hasattr(settings, 'SSO_API_TOKEN'): + log.error('SSO_API_TOKEN is not defined') + return + + data = { + 'mode': instance.mode, + 'is_active': instance.is_active, + 'course_id': str(instance.course.id), + 'user': instance.user.username + } + r = requests.post(sso_api_url, headers=sso_api_headers, data=data) + if r.ok: + return r.text + log.error('API "{}" returned: {}'.format(sso_api_url, r.status_code)) + + + +@receiver(post_delete, sender=CourseEnrollment) +def delete_enrollment_from_sso(sender, instance, **kwargs): + if not hasattr(settings, 'SSO_API_URL'): + log.error('settings.SSO_API_URL is not defined') + return + + if not hasattr(settings, 'SSO_API_TOKEN'): + log.error('SSO_API_TOKEN is not defined') + return + + data = { + 'course_id': str(instance.course.id), + 'user': instance.user.username + } + + r = requests.delete(sso_api_url, sso_api_headers=headers, data=data) + if r.ok: + return r.text + log.error('API "{}" returned: {}'.format(sso_api_url, r.status_code)) diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index 2ce84db750dc..46398d682a90 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -93,7 +93,7 @@

${course.display_org_with_defau
  • ${_("Account Settings")}
  • ${_("My Profile")}
  • -
  • ${_("Sign Out")}
  • +
  • ${_("Sign Out")}
  • diff --git a/lms/urls.py b/lms/urls.py index 819964165371..706ff5854599 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -40,6 +40,7 @@ name="disable_account_ajax"), url(r'^logout$', 'student.views.logout_user', name='logout'), + url(r'^social-logout', 'sso_edx_npoed.views.logout', name='social-logout'), url(r'^create_account$', 'student.views.create_account', name='create_account'), url(r'^activate/(?P[^/]*)$', 'student.views.activate_account', name="activate"), From 653352252fe281ed38c1f979c3fdc75cb6af179a Mon Sep 17 00:00:00 2001 From: sendr Date: Fri, 21 Aug 2015 10:39:08 -0400 Subject: [PATCH 03/13] Fix api --- common/djangoapps/student/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 8f006a1a3a39..54ec258ca5e8 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -1875,6 +1875,7 @@ def push_enrollment_to_sso(sender, instance, **kwargs): 'mode': instance.mode, 'is_active': instance.is_active, 'course_id': str(instance.course.id), + 'course_run': instance.course.id.run, 'user': instance.user.username } r = requests.post(sso_api_url, headers=sso_api_headers, data=data) @@ -1896,6 +1897,7 @@ def delete_enrollment_from_sso(sender, instance, **kwargs): data = { 'course_id': str(instance.course.id), + 'course_run': instance.course.id.run, 'user': instance.user.username } From 885240a0ba0f9922107594b27a2be424f0bdd5d6 Mon Sep 17 00:00:00 2001 From: sendr Date: Thu, 13 Aug 2015 09:15:23 -0400 Subject: [PATCH 04/13] Add update object signals --- cms/djangoapps/contentstore/signals.py | 37 ++++++++++++++++++ common/djangoapps/student/models.py | 53 +++++++++++++++++++++++++- lms/templates/navigation.html | 2 +- lms/urls.py | 1 + 4 files changed, 91 insertions(+), 2 deletions(-) diff --git a/cms/djangoapps/contentstore/signals.py b/cms/djangoapps/contentstore/signals.py index 8ada95bce1e1..f2e75738505e 100644 --- a/cms/djangoapps/contentstore/signals.py +++ b/cms/djangoapps/contentstore/signals.py @@ -1,12 +1,18 @@ """ receivers of course_published and library_updated events in order to trigger indexing task """ from datetime import datetime from pytz import UTC +import requests +import os +import logging from django.dispatch import receiver +from django.conf import settings from xmodule.modulestore.django import SignalHandler from contentstore.courseware_index import CoursewareSearchIndexer, LibrarySearchIndexer +from courseware.courses import get_course +log = logging.getLogger('course_signals') @receiver(SignalHandler.course_published) def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument @@ -28,3 +34,34 @@ def listen_for_library_update(sender, library_key, **kwargs): # pylint: disable from .tasks import update_library_index if LibrarySearchIndexer.indexing_is_enabled(): update_library_index.delay(unicode(library_key), datetime.now(UTC).isoformat()) + +@receiver(SignalHandler.course_published) +def push_objects_to_sso(sender, course_key, **kwargs): + if not hasattr(settings, 'SSO_API_URL'): + log.error('settings.SSO_API_URL is not defined') + return + + if not hasattr(settings, 'SSO_API_TOKEN'): + log.error('SSO_API_TOKEN is not defined') + return + + url = os.path.join(settings.SSO_API_URL, 'course/') + headers = {'Authorization': 'Token {}'.format(settings.SSO_API_TOKEN)} + course = get_course(course_key) + name = course.name or course_key.run + start = course.start and datetime.strftime(course.start, '%Y-%m-%dT%H:%M:%SZ') or None + end = course.end and datetime.strftime(course.end, '%Y-%m-%dT%H:%M:%SZ') or None + data = { + 'name': name, + 'course_id': course_key.html_id(), + 'start': start, + 'end': end, + 'org': course.org, + 'run': course_key.run, + } + + r = requests.post(url, headers=headers, data=data) + + if r.ok: + return r.text + log.error('API "{}" returned: {}'.format(url, r.status_code)) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 2c1cc1bb2c1f..54ec258ca5e8 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -10,6 +10,7 @@ 2. ./manage.py lms schemamigration student --auto description_of_your_change 3. Add the migration file created in edx-platform/common/djangoapps/student/migrations/ """ +import os from datetime import datetime, timedelta import hashlib import json @@ -19,6 +20,7 @@ from collections import defaultdict, OrderedDict import dogstats_wrapper as dog_stats_api from urllib import urlencode +import requests from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -28,7 +30,7 @@ from django.contrib.auth.signals import user_logged_in, user_logged_out from django.db import models, IntegrityError from django.db.models import Count -from django.db.models.signals import pre_save, post_save +from django.db.models.signals import pre_save, post_save, post_delete from django.dispatch import receiver, Signal from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext_noop @@ -1854,3 +1856,52 @@ def __unicode__(self): name=self.name, value=self.value, ) + + +sso_api_url = os.path.join(settings.SSO_API_URL, 'enrollment/') +sso_api_headers = {'Authorization': 'Token {}'.format(settings.SSO_API_TOKEN)} + +@receiver(post_save, sender=CourseEnrollment) +def push_enrollment_to_sso(sender, instance, **kwargs): + if not hasattr(settings, 'SSO_API_URL'): + log.error('settings.SSO_API_URL is not defined') + return + + if not hasattr(settings, 'SSO_API_TOKEN'): + log.error('SSO_API_TOKEN is not defined') + return + + data = { + 'mode': instance.mode, + 'is_active': instance.is_active, + 'course_id': str(instance.course.id), + 'course_run': instance.course.id.run, + 'user': instance.user.username + } + r = requests.post(sso_api_url, headers=sso_api_headers, data=data) + if r.ok: + return r.text + log.error('API "{}" returned: {}'.format(sso_api_url, r.status_code)) + + + +@receiver(post_delete, sender=CourseEnrollment) +def delete_enrollment_from_sso(sender, instance, **kwargs): + if not hasattr(settings, 'SSO_API_URL'): + log.error('settings.SSO_API_URL is not defined') + return + + if not hasattr(settings, 'SSO_API_TOKEN'): + log.error('SSO_API_TOKEN is not defined') + return + + data = { + 'course_id': str(instance.course.id), + 'course_run': instance.course.id.run, + 'user': instance.user.username + } + + r = requests.delete(sso_api_url, sso_api_headers=headers, data=data) + if r.ok: + return r.text + log.error('API "{}" returned: {}'.format(sso_api_url, r.status_code)) diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index 2ce84db750dc..46398d682a90 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -93,7 +93,7 @@

    ${course.display_org_with_defau
  • ${_("Account Settings")}
  • ${_("My Profile")}
  • -
  • ${_("Sign Out")}
  • +
  • ${_("Sign Out")}
  • diff --git a/lms/urls.py b/lms/urls.py index 819964165371..706ff5854599 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -40,6 +40,7 @@ name="disable_account_ajax"), url(r'^logout$', 'student.views.logout_user', name='logout'), + url(r'^social-logout', 'sso_edx_npoed.views.logout', name='social-logout'), url(r'^create_account$', 'student.views.create_account', name='create_account'), url(r'^activate/(?P[^/]*)$', 'student.views.activate_account', name="activate"), From a20beef9e9845284175e6c6ada19abea70342158 Mon Sep 17 00:00:00 2001 From: Maxim Starodubcev Date: Sat, 22 Aug 2015 12:35:28 -0400 Subject: [PATCH 05/13] LMS SSO login --- lms/templates/navigation.html | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index 46398d682a90..0a5e13868979 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -8,6 +8,8 @@ from microsite_configuration import microsite from microsite_configuration.templatetags.microsite import platform_name from ccx.overrides import get_current_ccx +import third_party_auth +from third_party_auth import provider, pipeline # App that handles subdomain specific branding import branding @@ -130,7 +132,15 @@

    ${course.display_org_with_defau % else: % endif % endif @@ -142,7 +152,15 @@

    ${course.display_org_with_defau % if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain: % else: - + % if third_party_auth.is_enabled(): + % for enabled in provider.Registry.enabled(): + % if not enabled.get_setting('use_for_cms'): + + % endif + % endfor + % else: + + % endif % endif % endif From 4d40dca32ea2e7f57cf35124f8983f6a15f6f637 Mon Sep 17 00:00:00 2001 From: Aleksey Radchenko Date: Sun, 23 Aug 2015 14:34:44 +0300 Subject: [PATCH 06/13] Added file with npoed specific settings --- cms/envs/npoed.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++ cms/startup.py | 4 ++++ cms/urls.py | 10 ++++++++++ lms/envs/npoed.py | 31 ++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 cms/envs/npoed.py create mode 100644 lms/envs/npoed.py diff --git a/cms/envs/npoed.py b/cms/envs/npoed.py new file mode 100644 index 000000000000..53b6b6bf59f4 --- /dev/null +++ b/cms/envs/npoed.py @@ -0,0 +1,51 @@ +import datetime + +from .aws import * + + +SSO_NPOED_URL = ENV_TOKENS.get('SSO_NPOED_URL') #'http://sso.rnoep.raccoongang.com' + +SSO_API_URL = "%s/api-edx/" % SSO_NPOED_URL #'http://sso.rnoep.raccoongang.com/api-edx/' +SSO_API_TOKEN = AUTH_TOKENS.get('SSO_API_TOKEN') #'b4c2b895087d457b86fc9096f344a687947b70fb' + + +SOCIAL_AUTH_EXCLUDE_URL_PATTERN = r'^/admin' +SOCIAL_AUTH_LOGOUT_URL = "%s/logout/" % SSO_NPOED_URL #'http://sso.rnoep.raccoongang.com/logout/' +SOCIAL_AUTH_RAISE_EXCEPTIONS = True + +MIDDLEWARE_CLASSES += ('sso_edx_npoed.middleware.SeamlessAuthorization', ) + +# We should login always with npoed-sso. There is specific backend for cms +# from sso_edx_npoed.backends.npoed import NpoedBackendCMS +# NpoedBackendCMS.name +SSO_NPOED_BACKEND_NAME = 'sso_npoed_cms-oauth2' +LOGIN_URL = '/auth/login/%s/' % SSO_NPOED_BACKEND_NAME + +# Add extra dir for mako templates finder +# '/edx/app/edxapp/venvs/edxapp/src/npoed-sso-edx-client/sso_edx_npoed/templates') +NPOED_MAKO_TEMPLATES = ENV_TOKENS.get('NPOED_MAKO_TEMPLATES', []) + +#TEMPLATE_DIRS.insert(0, '/edx/app/edxapp/venvs/edxapp/src/npoed-sso-edx-client/sso_edx_npoed') +MAKO_TEMPLATES['main'] = NPOED_MAKO_TEMPLATES + MAKO_TEMPLATES['main'] + + +CELERYBEAT_SCHEDULE = {} + +##### Third-party auth options ################################################ copied from lms/aws.py +if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): + THIRD_PARTY_AUTH_BACKENDS = ENV_TOKENS.get('THIRD_PARTY_AUTH_BACKENDS') + + if THIRD_PARTY_AUTH_BACKENDS: + AUTHENTICATION_BACKENDS = THIRD_PARTY_AUTH_BACKENDS + list(AUTHENTICATION_BACKENDS) + + # The reduced session expiry time during the third party login pipeline. (Value in seconds) + SOCIAL_AUTH_PIPELINE_TIMEOUT = ENV_TOKENS.get('SOCIAL_AUTH_PIPELINE_TIMEOUT', 600) + + # third_party_auth config moved to ConfigurationModels. This is for data migration only: + THIRD_PARTY_AUTH_OLD_CONFIG = AUTH_TOKENS.get('THIRD_PARTY_AUTH', None) + + if ENV_TOKENS.get('THIRD_PARTY_AUTH_SAML_FETCH_PERIOD_HOURS', 24) is not None: + CELERYBEAT_SCHEDULE['refresh-saml-metadata'] = { + 'task': 'third_party_auth.fetch_saml_metadata', + 'schedule': datetime.timedelta(hours=ENV_TOKENS.get('THIRD_PARTY_AUTH_SAML_FETCH_PERIOD_HOURS', 24)), + } \ No newline at end of file diff --git a/cms/startup.py b/cms/startup.py index bff8af0077ab..9b2eea2955df 100644 --- a/cms/startup.py +++ b/cms/startup.py @@ -21,6 +21,10 @@ def run(): add_mimetypes() + if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False): + from third_party_auth import settings as auth_settings + auth_settings.apply_settings(settings) + if settings.FEATURES.get('USE_CUSTOM_THEME', False): enable_theme() diff --git a/cms/urls.py b/cms/urls.py index 14a0fee56ad6..1f17cba1ead1 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -205,3 +205,13 @@ url(r'404', handler404), url(r'500', handler500), ) + +# Third-party auth. +if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): + urlpatterns += ( + url(r'', include('third_party_auth.urls')), + # NOTE: The following login_oauth_token endpoint is DEPRECATED. + # Please use the exchange_access_token endpoint instead. + url(r'^login_oauth_token/(?P[^/]+)/$', 'student.views.login_oauth_token'), + url(r'^social-logout', 'sso_edx_npoed.views.logout', name='social-logout'), + ) \ No newline at end of file diff --git a/lms/envs/npoed.py b/lms/envs/npoed.py new file mode 100644 index 000000000000..b855c711dca0 --- /dev/null +++ b/lms/envs/npoed.py @@ -0,0 +1,31 @@ +from .aws import * + + +SSO_NPOED_URL = ENV_TOKENS.get('SSO_NPOED_URL') #'http://sso.rnoep.raccoongang.com' + +SSO_API_URL = "%s/api-edx/" % SSO_NPOED_URL #'http://sso.rnoep.raccoongang.com/api-edx/' +SSO_API_TOKEN = AUTH_TOKENS.get('SSO_API_TOKEN') #'b4c2b895087d457b86fc9096f344a687947b70fb' + + +SOCIAL_AUTH_EXCLUDE_URL_PATTERN = r'^/admin' +SOCIAL_AUTH_LOGOUT_URL = "%s/logout/" % SSO_NPOED_URL #'http://sso.rnoep.raccoongang.com/logout/' +SOCIAL_AUTH_RAISE_EXCEPTIONS = True + +MIDDLEWARE_CLASSES += ('sso_edx_npoed.middleware.SeamlessAuthorization', ) + +# We should login always with npoed-sso +# from sso_edx_npoed.backends.npoed import NpoedBackend +# NpoedBackend.name +SSO_NPOED_BACKEND_NAME = 'sso_npoed-oauth2' +LOGIN_URL = '/auth/login/%s/' % SSO_NPOED_BACKEND_NAME + +# Add extra dir for mako templates finder +# '/edx/app/edxapp/venvs/edxapp/src/npoed-sso-edx-client/sso_edx_npoed/templates') +NPOED_MAKO_TEMPLATES = ENV_TOKENS.get('NPOED_MAKO_TEMPLATES', []) + +#TEMPLATE_DIRS.insert(0, '/edx/app/edxapp/venvs/edxapp/src/npoed-sso-edx-client/sso_edx_npoed') +MAKO_TEMPLATES['main'] = NPOED_MAKO_TEMPLATES + MAKO_TEMPLATES['main'] + + + +#OAUTH_OIDC_ISSUER = "https://rnoep.raccoongang.com/oauth2" From f49eaf4b0fa2dbd5c7c12b653b1be547da722937 Mon Sep 17 00:00:00 2001 From: Aleksey Radchenko Date: Sun, 23 Aug 2015 18:58:55 +0300 Subject: [PATCH 07/13] Revert changes in lms/templates/navigation.html --- lms/templates/navigation.html | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index 0a5e13868979..2ce84db750dc 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -8,8 +8,6 @@ from microsite_configuration import microsite from microsite_configuration.templatetags.microsite import platform_name from ccx.overrides import get_current_ccx -import third_party_auth -from third_party_auth import provider, pipeline # App that handles subdomain specific branding import branding @@ -95,7 +93,7 @@

    ${course.display_org_with_defau
  • ${_("Account Settings")}
  • ${_("My Profile")}
  • -
  • ${_("Sign Out")}
  • +
  • ${_("Sign Out")}
  • @@ -132,15 +130,7 @@

    ${course.display_org_with_defau % else: % endif % endif @@ -152,15 +142,7 @@

    ${course.display_org_with_defau % if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain: % else: - % if third_party_auth.is_enabled(): - % for enabled in provider.Registry.enabled(): - % if not enabled.get_setting('use_for_cms'): - - % endif - % endfor - % else: - - % endif + % endif % endif From b7d7a5bc4f079671962ea40f65a681791b53a22b Mon Sep 17 00:00:00 2001 From: Aleksey Radchenko Date: Mon, 24 Aug 2015 14:45:07 +0300 Subject: [PATCH 08/13] Added open_edx_api_extension to urls and requirements --- lms/urls.py | 5 +++++ requirements/edx/github.txt | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/lms/urls.py b/lms/urls.py index 706ff5854599..c3c6da28ee53 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -723,3 +723,8 @@ url(r'^404$', handler404), url(r'^500$', handler500), ) + +urlpatterns += ( + # Extend API + url(r'^api/extended/', include('open_edx_api_extension.urls', namespace='api_extension')), +) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 9fbcf423af59..633dcfeb7d65 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -58,3 +58,7 @@ git+https://github.com/edx/ecommerce-api-client.git@1.0.0#egg=ecommerce-api-clie # Third Party XBlocks -e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga -e git+https://github.com/open-craft/xblock-poll@v1.0#egg=xblock-poll + +# Npoed specific repos: +pip install -e git+https://github.com/raccoongang/open_edx_api_extension.git#egg=open_edx_api_extension +pip install -e git+https://github.com/miptliot/npoed-sso-edx-client.git#egg=sso_edx_npoed From 0b28aebaae6bcde9e2f3dbfdae34c4d10f1b2056 Mon Sep 17 00:00:00 2001 From: Aleksey Radchenko Date: Fri, 28 Aug 2015 12:53:23 +0300 Subject: [PATCH 09/13] Added PLPRedirection middleware to npoed settings --- lms/envs/npoed.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lms/envs/npoed.py b/lms/envs/npoed.py index b855c711dca0..cdd586a387d2 100644 --- a/lms/envs/npoed.py +++ b/lms/envs/npoed.py @@ -11,7 +11,10 @@ SOCIAL_AUTH_LOGOUT_URL = "%s/logout/" % SSO_NPOED_URL #'http://sso.rnoep.raccoongang.com/logout/' SOCIAL_AUTH_RAISE_EXCEPTIONS = True -MIDDLEWARE_CLASSES += ('sso_edx_npoed.middleware.SeamlessAuthorization', ) +MIDDLEWARE_CLASSES += ('sso_edx_npoed.middleware.PLPRedirection', + 'sso_edx_npoed.middleware.SeamlessAuthorization') + +PLP_URL = ENV_TOKENS.get('PLP_URL') # We should login always with npoed-sso # from sso_edx_npoed.backends.npoed import NpoedBackend From c26d24253d4efd2a5bcc2738d3928febaca0eac0 Mon Sep 17 00:00:00 2001 From: Maxim Starodubcev Date: Fri, 4 Sep 2015 13:01:03 +0300 Subject: [PATCH 10/13] Revert "Add update object signals" This reverts commit 885240a0ba0f9922107594b27a2be424f0bdd5d6. --- cms/djangoapps/contentstore/signals.py | 37 ------------------ common/djangoapps/student/models.py | 53 +------------------------- lms/urls.py | 1 - 3 files changed, 1 insertion(+), 90 deletions(-) diff --git a/cms/djangoapps/contentstore/signals.py b/cms/djangoapps/contentstore/signals.py index f2e75738505e..8ada95bce1e1 100644 --- a/cms/djangoapps/contentstore/signals.py +++ b/cms/djangoapps/contentstore/signals.py @@ -1,18 +1,12 @@ """ receivers of course_published and library_updated events in order to trigger indexing task """ from datetime import datetime from pytz import UTC -import requests -import os -import logging from django.dispatch import receiver -from django.conf import settings from xmodule.modulestore.django import SignalHandler from contentstore.courseware_index import CoursewareSearchIndexer, LibrarySearchIndexer -from courseware.courses import get_course -log = logging.getLogger('course_signals') @receiver(SignalHandler.course_published) def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument @@ -34,34 +28,3 @@ def listen_for_library_update(sender, library_key, **kwargs): # pylint: disable from .tasks import update_library_index if LibrarySearchIndexer.indexing_is_enabled(): update_library_index.delay(unicode(library_key), datetime.now(UTC).isoformat()) - -@receiver(SignalHandler.course_published) -def push_objects_to_sso(sender, course_key, **kwargs): - if not hasattr(settings, 'SSO_API_URL'): - log.error('settings.SSO_API_URL is not defined') - return - - if not hasattr(settings, 'SSO_API_TOKEN'): - log.error('SSO_API_TOKEN is not defined') - return - - url = os.path.join(settings.SSO_API_URL, 'course/') - headers = {'Authorization': 'Token {}'.format(settings.SSO_API_TOKEN)} - course = get_course(course_key) - name = course.name or course_key.run - start = course.start and datetime.strftime(course.start, '%Y-%m-%dT%H:%M:%SZ') or None - end = course.end and datetime.strftime(course.end, '%Y-%m-%dT%H:%M:%SZ') or None - data = { - 'name': name, - 'course_id': course_key.html_id(), - 'start': start, - 'end': end, - 'org': course.org, - 'run': course_key.run, - } - - r = requests.post(url, headers=headers, data=data) - - if r.ok: - return r.text - log.error('API "{}" returned: {}'.format(url, r.status_code)) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 54ec258ca5e8..2c1cc1bb2c1f 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -10,7 +10,6 @@ 2. ./manage.py lms schemamigration student --auto description_of_your_change 3. Add the migration file created in edx-platform/common/djangoapps/student/migrations/ """ -import os from datetime import datetime, timedelta import hashlib import json @@ -20,7 +19,6 @@ from collections import defaultdict, OrderedDict import dogstats_wrapper as dog_stats_api from urllib import urlencode -import requests from django.utils.translation import ugettext_lazy as _ from django.conf import settings @@ -30,7 +28,7 @@ from django.contrib.auth.signals import user_logged_in, user_logged_out from django.db import models, IntegrityError from django.db.models import Count -from django.db.models.signals import pre_save, post_save, post_delete +from django.db.models.signals import pre_save, post_save from django.dispatch import receiver, Signal from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext_noop @@ -1856,52 +1854,3 @@ def __unicode__(self): name=self.name, value=self.value, ) - - -sso_api_url = os.path.join(settings.SSO_API_URL, 'enrollment/') -sso_api_headers = {'Authorization': 'Token {}'.format(settings.SSO_API_TOKEN)} - -@receiver(post_save, sender=CourseEnrollment) -def push_enrollment_to_sso(sender, instance, **kwargs): - if not hasattr(settings, 'SSO_API_URL'): - log.error('settings.SSO_API_URL is not defined') - return - - if not hasattr(settings, 'SSO_API_TOKEN'): - log.error('SSO_API_TOKEN is not defined') - return - - data = { - 'mode': instance.mode, - 'is_active': instance.is_active, - 'course_id': str(instance.course.id), - 'course_run': instance.course.id.run, - 'user': instance.user.username - } - r = requests.post(sso_api_url, headers=sso_api_headers, data=data) - if r.ok: - return r.text - log.error('API "{}" returned: {}'.format(sso_api_url, r.status_code)) - - - -@receiver(post_delete, sender=CourseEnrollment) -def delete_enrollment_from_sso(sender, instance, **kwargs): - if not hasattr(settings, 'SSO_API_URL'): - log.error('settings.SSO_API_URL is not defined') - return - - if not hasattr(settings, 'SSO_API_TOKEN'): - log.error('SSO_API_TOKEN is not defined') - return - - data = { - 'course_id': str(instance.course.id), - 'course_run': instance.course.id.run, - 'user': instance.user.username - } - - r = requests.delete(sso_api_url, sso_api_headers=headers, data=data) - if r.ok: - return r.text - log.error('API "{}" returned: {}'.format(sso_api_url, r.status_code)) diff --git a/lms/urls.py b/lms/urls.py index c3c6da28ee53..bf784818a178 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -40,7 +40,6 @@ name="disable_account_ajax"), url(r'^logout$', 'student.views.logout_user', name='logout'), - url(r'^social-logout', 'sso_edx_npoed.views.logout', name='social-logout'), url(r'^create_account$', 'student.views.create_account', name='create_account'), url(r'^activate/(?P[^/]*)$', 'student.views.activate_account', name="activate"), From 678f93f9fbecdad7167010c3bb60e7f96bd7f44d Mon Sep 17 00:00:00 2001 From: Maxim Starodubcev Date: Fri, 4 Sep 2015 13:09:50 +0300 Subject: [PATCH 11/13] Revert "Add staff mode to API" This reverts commit cb22822340258c1fc2990a12441b2489c0fbc3b1. --- common/djangoapps/enrollment/api.py | 4 ++-- common/djangoapps/enrollment/data.py | 9 ++++----- common/djangoapps/enrollment/views.py | 11 ++--------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py index febffe3f05d3..2345b025f5f9 100644 --- a/common/djangoapps/enrollment/api.py +++ b/common/djangoapps/enrollment/api.py @@ -14,7 +14,7 @@ DEFAULT_DATA_API = 'enrollment.data' -def get_enrollments(user_id=None, **kwargs): +def get_enrollments(user_id): """Retrieves all the courses a user is enrolled in. Takes a user and retrieves all relative enrollments. Includes information regarding how the user is enrolled @@ -84,7 +84,7 @@ def get_enrollments(user_id=None, **kwargs): ] """ - return _data_api().get_course_enrollments(user_id, **kwargs) + return _data_api().get_course_enrollments(user_id) def get_enrollment(user_id, course_id): diff --git a/common/djangoapps/enrollment/data.py b/common/djangoapps/enrollment/data.py index f3543c61335f..ce05cea6c32a 100644 --- a/common/djangoapps/enrollment/data.py +++ b/common/djangoapps/enrollment/data.py @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) -def get_course_enrollments(user_id=None, **kwargs): +def get_course_enrollments(user_id): """Retrieve a list representing all aggregated data for a user's course enrollments. Construct a representation of all course enrollment data for a specific user. @@ -32,10 +32,9 @@ def get_course_enrollments(user_id=None, **kwargs): A serializable list of dictionaries of all aggregated enrollment data for a user. """ - qset = CourseEnrollment.objects.filter(is_active=True, **kwargs) - if user_id is not None: - qset.filter(user__username=user_id) - qset.order_by('created') + qset = CourseEnrollment.objects.filter( + user__username=user_id, is_active=True + ).order_by('created') return CourseEnrollmentSerializer(qset).data # pylint: disable=no-member diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index 7962ff4a4766..8cfb06cc348a 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -346,19 +346,12 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): @method_decorator(ensure_csrf_cookie_cross_domain) def get(self, request): """Gets a list of all course enrollments for the currently logged in user.""" - username = request.GET.get('user', request.user.is_staff and None or request.user.username) - try: - course_key = CourseKey.from_string(request.GET.get('course')) - except InvalidKeyError: - course_key = None - - if (not request.user.is_staff and request.user.username != username) and not self.has_api_key_permissions(request): + username = request.GET.get('user', request.user.username) + if request.user.username != username and not self.has_api_key_permissions(request): # Return a 404 instead of a 403 (Unauthorized). If one user is looking up # other users, do not let them deduce the existence of an enrollment. return Response(status=status.HTTP_404_NOT_FOUND) try: - if course_key: - return Response(api.get_enrollments(username, course_id=course_key)) return Response(api.get_enrollments(username)) except CourseEnrollmentError: return Response( From 863504287f3ce3d0dd12ebc981d0a0699a6d015e Mon Sep 17 00:00:00 2001 From: Maxim Starodubcev Date: Fri, 4 Sep 2015 14:30:15 +0300 Subject: [PATCH 12/13] Add social logout URL --- lms/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/urls.py b/lms/urls.py index bf784818a178..d9ccaff65b88 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -726,4 +726,5 @@ urlpatterns += ( # Extend API url(r'^api/extended/', include('open_edx_api_extension.urls', namespace='api_extension')), + url(r'^social-logout', 'sso_edx_npoed.views.logout', name='social-logout'), ) From 421359db66542df3eea218950912a2f1b1695119 Mon Sep 17 00:00:00 2001 From: Maxim Starodubcev Date: Fri, 11 Sep 2015 13:47:29 +0300 Subject: [PATCH 13/13] Fix requirements --- requirements/edx/github.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 633dcfeb7d65..35b80a6c2c2b 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -60,5 +60,5 @@ git+https://github.com/edx/ecommerce-api-client.git@1.0.0#egg=ecommerce-api-clie -e git+https://github.com/open-craft/xblock-poll@v1.0#egg=xblock-poll # Npoed specific repos: -pip install -e git+https://github.com/raccoongang/open_edx_api_extension.git#egg=open_edx_api_extension -pip install -e git+https://github.com/miptliot/npoed-sso-edx-client.git#egg=sso_edx_npoed +-e git+https://github.com/raccoongang/open_edx_api_extension.git#egg=open_edx_api_extension +-e git+https://github.com/miptliot/npoed-sso-edx-client.git#egg=sso_edx_npoed