From 8b5e8968ed22d089c9cbcf01dd7c59860b4700b1 Mon Sep 17 00:00:00 2001 From: irfanuddinahmad Date: Fri, 1 Nov 2019 16:26:38 +0500 Subject: [PATCH] updated the login flow for multiple enterprise --- .../multiple_enterprise_spec.js | 79 +++++++++++++++++++ .../js/student_account/multiple_enterprise.js | 52 ++++++++++++ lms/static/js/student_account/utils.js | 28 +++++++ .../js/student_account/views/AccessView.js | 28 ++++++- lms/static/lms/js/spec/main.js | 1 + .../djangoapps/user_api/accounts/utils.py | 14 ++++ .../core/djangoapps/user_api/config/waffle.py | 1 + .../djangoapps/user_authn/views/login_form.py | 8 +- 8 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 lms/static/js/spec/student_account/multiple_enterprise_spec.js create mode 100644 lms/static/js/student_account/multiple_enterprise.js create mode 100644 lms/static/js/student_account/utils.js diff --git a/lms/static/js/spec/student_account/multiple_enterprise_spec.js b/lms/static/js/spec/student_account/multiple_enterprise_spec.js new file mode 100644 index 0000000000..86ba7d504c --- /dev/null +++ b/lms/static/js/spec/student_account/multiple_enterprise_spec.js @@ -0,0 +1,79 @@ +define([ + 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', + 'js/student_account/multiple_enterprise', + 'js/student_account/utils' +], + function(AjaxHelpers, MultipleEnterpriseInterface, Utils) { + 'use strict'; + + describe('MultipleEnterpriseInterface', function() { + var LEARNER_URL = '/enterprise/api/v1/enterprise-learner/?username=test-learner', + NEXT_URL = '/dashboard', + REDIRECT_URL = '/enterprise/select/active/?success_url=/dashboard'; + + beforeEach(function() { + // Mock the redirect call + spyOn(MultipleEnterpriseInterface, 'redirect').and.callFake(function() {}); + spyOn(Utils, 'userFromEdxUserCookie').and.returnValue({username: 'test-learner'}); + }); + + it('gets learner information and checks redirect to enterprise selection page', function() { + // Spy on Ajax requests + var requests = AjaxHelpers.requests(this); + + // Attempt to fetch a learner + MultipleEnterpriseInterface.check(NEXT_URL); + + // Expect that the correct request was made to the server + AjaxHelpers.expectRequest( + requests, + 'GET', + LEARNER_URL, + null + ); + + // Simulate a successful response from the server + AjaxHelpers.respondWithJson(requests, {count: 2}); + + // Verify that the user was redirected correctly + expect(MultipleEnterpriseInterface.redirect).toHaveBeenCalledWith(REDIRECT_URL); + }); + + it('gets learner information and checks that enterprise selection page is bypassed', function() { + // Spy on Ajax requests + var requests = AjaxHelpers.requests(this); + + // Attempt to fetch a learner + MultipleEnterpriseInterface.check(NEXT_URL); + + // Expect that the correct request was made to the server + AjaxHelpers.expectRequest( + requests, + 'GET', + LEARNER_URL, + null + ); + + // Simulate a successful response from the server + AjaxHelpers.respondWithJson(requests, {count: 1}); + + // Verify that the user was redirected correctly + expect(MultipleEnterpriseInterface.redirect).toHaveBeenCalledWith(NEXT_URL); + }); + + it('correctly redirects the user if learner information call fails', function() { + // Spy on Ajax requests + var requests = AjaxHelpers.requests(this); + + // Attempt to fetch a learner + MultipleEnterpriseInterface.check(NEXT_URL); + + // Simulate an error response from the server + AjaxHelpers.respondWithError(requests); + + // Verify that the user was redirected + expect(MultipleEnterpriseInterface.redirect).toHaveBeenCalledWith(NEXT_URL); + }); + }); + } +); diff --git a/lms/static/js/student_account/multiple_enterprise.js b/lms/static/js/student_account/multiple_enterprise.js new file mode 100644 index 0000000000..94046fdf2c --- /dev/null +++ b/lms/static/js/student_account/multiple_enterprise.js @@ -0,0 +1,52 @@ +(function(define) { + 'use strict'; + define(['jquery', 'js/student_account/utils', 'jquery.cookie'], function($, Utils) { + var MultipleEnterpriseInterface = { + + urls: { + learners: '/enterprise/api/v1/enterprise-learner/', + multipleEnterpriseUrl: '/enterprise/select/active/?success_url=' + }, + + headers: { + 'X-CSRFToken': $.cookie('csrftoken') + }, + + /** + * Fetch the learner data, then redirect the user to a enterprise selection page if multiple + * enterprises were found. + * @param {string} nextUrl The URL to redirect to after multiple enterprise selection. + */ + check: function(nextUrl) { + var redirectUrl = this.urls.multipleEnterpriseUrl + encodeURI(nextUrl); + var username = Utils.userFromEdxUserCookie().username; + var next = nextUrl || '/'; + $.ajax({ + url: this.urls.learners + '?username=' + username, + type: 'GET', + contentType: 'application/json; charset=utf-8', + headers: this.headers, + context: this + }).fail(function() { + this.redirect(next); + }).done(function(response) { + if (response.count > 1 && redirectUrl) { + this.redirect(redirectUrl); + } else { + this.redirect(next); + } + }); + }, + + /** + * Redirect to a URL. + * @param {string} url The URL to redirect to. + */ + redirect: function(url) { + window.location.href = url; + } + }; + + return MultipleEnterpriseInterface; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/utils.js b/lms/static/js/student_account/utils.js new file mode 100644 index 0000000000..5f5417f696 --- /dev/null +++ b/lms/static/js/student_account/utils.js @@ -0,0 +1,28 @@ +(function(define) { + 'use strict'; + define(['jquery'], function($) { + var userFromEdxUserCookie = function() { + var hostname = window.location.hostname; + var isLocalhost = hostname.indexOf('localhost') >= 0; + var isStage = hostname.indexOf('stage') >= 0; + var edxUserCookie, prefix, user; + + if (isLocalhost) { + // localhost doesn't have prefixes + edxUserCookie = 'edx-user-info'; + } else { + // does not take sandboxes into account + prefix = isStage ? 'stage' : 'prod'; + edxUserCookie = prefix + '-edx-user-info'; + } + // returns the user object from cookie. Replaces '054' with ',' and removes '\' + user = $.cookie(edxUserCookie).replace(/\\/g, '').replace(/054/g, ','); + user = user.substring(1, user.length - 1); + return JSON.parse(user); + }; + + return { + userFromEdxUserCookie: userFromEdxUserCookie + }; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index 0aa0a3df16..88c6db9222 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -16,10 +16,12 @@ 'js/student_account/views/InstitutionLoginView', 'js/student_account/views/HintedLoginView', 'edx-ui-toolkit/js/utils/html-utils', + 'js/student_account/multiple_enterprise', 'js/vendor/history' ], function($, utility, _, _s, Backbone, LoginModel, PasswordResetModel, RegisterModel, AccountRecoveryModel, - LoginView, PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView, HtmlUtils) { + LoginView, PasswordResetView, RegisterView, InstitutionLoginView, HintedLoginView, HtmlUtils, + multipleEnterpriseInterface) { return Backbone.View.extend({ tpl: '#access-tpl', events: { @@ -43,7 +45,6 @@ * Underscore namespace */ _.mixin(_s.exports()); - this.tpl = $(this.tpl).html(); this.activeForm = options.initial_mode || 'login'; @@ -70,7 +71,6 @@ institution_login: null, hinted_login: null }; - this.platformName = options.platform_name; this.supportURL = options.support_link; this.passwordResetSupportUrl = options.password_reset_support_link; @@ -79,6 +79,8 @@ this.pipelineUserDetails = options.third_party_auth.pipeline_user_details; this.enterpriseName = options.enterprise_name || ''; this.isAccountRecoveryFeatureEnabled = options.is_account_recovery_feature_enabled || false; + this.isMultipleUserEnterprisesFeatureEnabled = + options.is_multiple_user_enterprises_feature_enabled || false; // The login view listens for 'sync' events from the reset model this.resetModel = new PasswordResetModel({}, { @@ -158,7 +160,11 @@ this.listenTo(this.subview.login, 'password-help', this.resetPassword); // Listen for 'auth-complete' event so we can enroll/redirect the user appropriately. - this.listenTo(this.subview.login, 'auth-complete', this.authComplete); + if (this.isMultipleUserEnterprisesFeatureEnabled === true) { + this.listenTo(this.subview.login, 'auth-complete', this.loginComplete); + } else { + this.listenTo(this.subview.login, 'auth-complete', this.authComplete); + } }, reset: function(data) { @@ -286,6 +292,20 @@ } }, + /** + /** + * Take a learner attached to multiple enterprises to the enterprise selection page: + * + */ + loginComplete: function() { + if (this.thirdPartyAuth && this.thirdPartyAuth.finishAuthUrl) { + multipleEnterpriseInterface.check(this.thirdPartyAuth.finishAuthUrl); + // Note: the third party auth URL likely contains another redirect URL embedded inside + } else { + multipleEnterpriseInterface.check(this.nextUrl); + } + }, + /** * Redirect to a URL. Mainly useful for mocking out in tests. * @param {string} url The URL to redirect to. diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 23ae49a23b..c310def38d 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -786,6 +786,7 @@ 'js/spec/student_account/institution_login_spec.js', 'js/spec/student_account/login_spec.js', 'js/spec/student_account/logistration_factory_spec.js', + 'js/spec/student_account/multiple_enterprise_spec.js', 'js/spec/student_account/password_reset_spec.js', 'js/spec/student_account/register_spec.js', 'js/spec/student_account/shoppingcart_spec.js', diff --git a/openedx/core/djangoapps/user_api/accounts/utils.py b/openedx/core/djangoapps/user_api/accounts/utils.py index fdc9bf7b0d..c281079d6a 100644 --- a/openedx/core/djangoapps/user_api/accounts/utils.py +++ b/openedx/core/djangoapps/user_api/accounts/utils.py @@ -18,6 +18,10 @@ from six.moves.urllib.parse import urlparse # pylint: disable=import-error from openedx.core.djangoapps.site_configuration.models import SiteConfiguration from openedx.core.djangoapps.theming.helpers import get_config_value_from_site_or_settings, get_current_site +from openedx.core.djangoapps.user_api.config.waffle import ( + ENABLE_MULTIPLE_USER_ENTERPRISES_FEATURE, + waffle as user_api_waffle +) from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError @@ -214,3 +218,13 @@ def is_secondary_email_feature_enabled_for_user(user): # import is placed here to avoid cyclic import. from openedx.features.enterprise_support.utils import is_enterprise_learner return is_secondary_email_feature_enabled() and is_enterprise_learner(user) + + +def is_multiple_user_enterprises_feature_enabled(): + """ + Checks to see if the django-waffle switch for enabling the multiple user enterprises feature is active + + Returns: + Boolean value representing switch status + """ + return user_api_waffle().is_enabled(ENABLE_MULTIPLE_USER_ENTERPRISES_FEATURE) diff --git a/openedx/core/djangoapps/user_api/config/waffle.py b/openedx/core/djangoapps/user_api/config/waffle.py index fc1166bd41..b226ab1405 100644 --- a/openedx/core/djangoapps/user_api/config/waffle.py +++ b/openedx/core/djangoapps/user_api/config/waffle.py @@ -12,6 +12,7 @@ WAFFLE_NAMESPACE = u'user_api' # Switches PREVENT_AUTH_USER_WRITES = u'prevent_auth_user_writes' +ENABLE_MULTIPLE_USER_ENTERPRISES_FEATURE = u'enable_multiple_user_enterprises_feature' def waffle(): diff --git a/openedx/core/djangoapps/user_authn/views/login_form.py b/openedx/core/djangoapps/user_authn/views/login_form.py index 1843297232..6c7f5750bc 100644 --- a/openedx/core/djangoapps/user_authn/views/login_form.py +++ b/openedx/core/djangoapps/user_authn/views/login_form.py @@ -17,7 +17,10 @@ from django.views.decorators.http import require_http_methods import third_party_auth from edxmako.shortcuts import render_to_response from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers -from openedx.core.djangoapps.user_api.accounts.utils import is_secondary_email_feature_enabled +from openedx.core.djangoapps.user_api.accounts.utils import ( + is_secondary_email_feature_enabled, + is_multiple_user_enterprises_feature_enabled +) from openedx.core.djangoapps.user_api.api import ( get_login_session_form, ) @@ -124,7 +127,8 @@ def login_and_registration_form(request, initial_mode="login"): 'password_reset_form_desc': json.loads(form_descriptions['password_reset']), 'account_creation_allowed': configuration_helpers.get_value( 'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)), - 'is_account_recovery_feature_enabled': is_secondary_email_feature_enabled() + 'is_account_recovery_feature_enabled': is_secondary_email_feature_enabled(), + 'is_multiple_user_enterprises_feature_enabled': is_multiple_user_enterprises_feature_enabled() }, 'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header 'responsive': True,