New Hinted Login View - PR 8591
This commit is contained in:
@@ -211,6 +211,15 @@ class FieldsMixin(object):
|
||||
query = self.q(css='.u-field-link-title-{}'.format(field_id))
|
||||
return query.text[0] if query.present else None
|
||||
|
||||
def wait_for_link_title_for_link_field(self, field_id, expected_title):
|
||||
"""
|
||||
Wait until the title of the specified link field equals expected_title.
|
||||
"""
|
||||
return EmptyPromise(
|
||||
lambda: self.link_title_for_link_field(field_id) == expected_title,
|
||||
"Link field with link title \"{0}\" is visible.".format(expected_title)
|
||||
).fulfill()
|
||||
|
||||
def click_on_link_in_link_field(self, field_id):
|
||||
"""
|
||||
Click the link in a link field.
|
||||
|
||||
@@ -281,6 +281,8 @@ class CombinedLoginAndRegisterPage(PageObject):
|
||||
return "login"
|
||||
elif self.q(css=".js-reset").visible:
|
||||
return "password-reset"
|
||||
elif self.q(css=".proceed-button").visible:
|
||||
return "hinted-login"
|
||||
|
||||
@property
|
||||
def email_value(self):
|
||||
@@ -335,3 +337,9 @@ class CombinedLoginAndRegisterPage(PageObject):
|
||||
return (True, msg_element.text[0])
|
||||
return (False, None)
|
||||
return Promise(_check_func, "Result of third party auth is visible").fulfill()
|
||||
|
||||
@property
|
||||
def hinted_login_prompt(self):
|
||||
"""Get the message displayed to the user on the hinted-login form"""
|
||||
if self.q(css=".wrapper-other-login .instructions").visible:
|
||||
return self.q(css=".wrapper-other-login .instructions").text[0]
|
||||
|
||||
@@ -164,7 +164,41 @@ class LoginFromCombinedPageTest(UniqueCourseTest):
|
||||
|
||||
self.dashboard_page.wait_for_page()
|
||||
|
||||
# Now unlink the account (To test the account settings view and also to prevent cross-test side effects)
|
||||
self._unlink_dummy_account()
|
||||
|
||||
def test_hinted_login(self):
|
||||
""" Test the login page when coming from course URL that specified which third party provider to use """
|
||||
# Create a user account and link it to third party auth with the dummy provider:
|
||||
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
||||
self._link_dummy_account()
|
||||
LogoutPage(self.browser).visit()
|
||||
|
||||
# When not logged in, try to load a course URL that includes the provider hint ?tpa_hint=...
|
||||
course_page = CoursewarePage(self.browser, self.course_id)
|
||||
self.browser.get(course_page.url + '?tpa_hint=oa2-dummy')
|
||||
|
||||
# We should now be redirected to the login page
|
||||
self.login_page.wait_for_page()
|
||||
self.assertIn("Would you like to sign in using your Dummy credentials?", self.login_page.hinted_login_prompt)
|
||||
self.login_page.click_third_party_dummy_provider()
|
||||
|
||||
# We should now be redirected to the course page
|
||||
course_page.wait_for_page()
|
||||
|
||||
self._unlink_dummy_account()
|
||||
|
||||
def _link_dummy_account(self):
|
||||
""" Go to Account Settings page and link the user's account to the Dummy provider """
|
||||
account_settings = AccountSettingsPage(self.browser).visit()
|
||||
field_id = "auth-oa2-dummy"
|
||||
account_settings.wait_for_field(field_id)
|
||||
self.assertEqual("Link", account_settings.link_title_for_link_field(field_id))
|
||||
account_settings.click_on_link_in_link_field(field_id)
|
||||
account_settings.wait_for_link_title_for_link_field(field_id, "Unlink")
|
||||
|
||||
def _unlink_dummy_account(self):
|
||||
""" Verify that the 'Dummy' third party auth provider is linked, then unlink it """
|
||||
# This must be done after linking the account, or we'll get cross-test side effects
|
||||
account_settings = AccountSettingsPage(self.browser).visit()
|
||||
field_id = "auth-oa2-dummy"
|
||||
account_settings.wait_for_field(field_id)
|
||||
|
||||
@@ -329,6 +329,11 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
|
||||
]
|
||||
self._assert_third_party_auth_data(response, current_backend, current_provider, expected_providers)
|
||||
|
||||
def test_hinted_login(self):
|
||||
params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")]
|
||||
response = self.client.get(reverse('account_login'), params)
|
||||
self.assertContains(response, "data-third-party-auth-hint='oa2-google-oauth2'")
|
||||
|
||||
@override_settings(SITE_NAME=settings.MICROSITE_TEST_HOSTNAME)
|
||||
def test_microsite_uses_old_login_page(self):
|
||||
# Retrieve the login page from a microsite domain
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import logging
|
||||
import json
|
||||
import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -77,12 +78,26 @@ def login_and_registration_form(request, initial_mode="login"):
|
||||
if ext_auth_response is not None:
|
||||
return ext_auth_response
|
||||
|
||||
# Our ?next= URL may itself contain a parameter 'tpa_hint=x' that we need to check.
|
||||
# If present, we display a login page focused on third-party auth with that provider.
|
||||
third_party_auth_hint = None
|
||||
if '?' in redirect_to:
|
||||
try:
|
||||
next_args = urlparse.parse_qs(urlparse.urlparse(redirect_to).query)
|
||||
provider_id = next_args['tpa_hint'][0]
|
||||
if third_party_auth.provider.Registry.get(provider_id=provider_id):
|
||||
third_party_auth_hint = provider_id
|
||||
initial_mode = "hinted_login"
|
||||
except (KeyError, ValueError, IndexError):
|
||||
pass
|
||||
|
||||
# Otherwise, render the combined login/registration page
|
||||
context = {
|
||||
'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in the header
|
||||
'disable_courseware_js': True,
|
||||
'initial_mode': initial_mode,
|
||||
'third_party_auth': json.dumps(_third_party_auth_context(request, redirect_to)),
|
||||
'third_party_auth_hint': third_party_auth_hint or '',
|
||||
'platform_name': settings.PLATFORM_NAME,
|
||||
'responsive': True,
|
||||
|
||||
|
||||
@@ -1271,6 +1271,7 @@ student_account_js = [
|
||||
'js/student_account/models/PasswordResetModel.js',
|
||||
'js/student_account/views/FormView.js',
|
||||
'js/student_account/views/LoginView.js',
|
||||
'js/student_account/views/HintedLoginView.js',
|
||||
'js/student_account/views/RegisterView.js',
|
||||
'js/student_account/views/PasswordResetView.js',
|
||||
'js/student_account/views/AccessView.js',
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
'js/student_account/models/RegisterModel': 'js/student_account/models/RegisterModel',
|
||||
'js/student_account/views/RegisterView': 'js/student_account/views/RegisterView',
|
||||
'js/student_account/views/AccessView': 'js/student_account/views/AccessView',
|
||||
'js/student_account/views/HintedLoginView': 'js/student_account/views/HintedLoginView',
|
||||
'js/student_profile/profile': 'js/student_profile/profile',
|
||||
'js/student_profile/views/learner_profile_fields': 'js/student_profile/views/learner_profile_fields',
|
||||
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory',
|
||||
@@ -448,6 +449,15 @@
|
||||
'js/student_account/views/FormView'
|
||||
]
|
||||
},
|
||||
'js/student_account/views/HintedLoginView': {
|
||||
exports: 'edx.student.account.HintedLoginView',
|
||||
deps: [
|
||||
'jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'gettext'
|
||||
]
|
||||
},
|
||||
'js/student_account/views/AccessView': {
|
||||
exports: 'edx.student.account.AccessView',
|
||||
deps: [
|
||||
@@ -622,6 +632,7 @@
|
||||
'lms/include/js/spec/student_account/account_spec.js',
|
||||
'lms/include/js/spec/student_account/access_spec.js',
|
||||
'lms/include/js/spec/student_account/finish_auth_spec.js',
|
||||
'lms/include/js/spec/student_account/hinted_login_spec.js',
|
||||
'lms/include/js/spec/student_account/login_spec.js',
|
||||
'lms/include/js/spec/student_account/institution_login_spec.js',
|
||||
'lms/include/js/spec/student_account/register_spec.js',
|
||||
|
||||
71
lms/static/js/spec/student_account/hinted_login_spec.js
Normal file
71
lms/static/js/spec/student_account/hinted_login_spec.js
Normal file
@@ -0,0 +1,71 @@
|
||||
define([
|
||||
'jquery',
|
||||
'underscore',
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'common/js/spec_helpers/ajax_helpers',
|
||||
'js/student_account/views/HintedLoginView',
|
||||
], function($, _, TemplateHelpers, AjaxHelpers, HintedLoginView) {
|
||||
'use strict';
|
||||
describe('edx.student.account.HintedLoginView', function() {
|
||||
|
||||
var view = null,
|
||||
requests = null,
|
||||
PLATFORM_NAME = 'edX',
|
||||
THIRD_PARTY_AUTH = {
|
||||
currentProvider: null,
|
||||
providers: [
|
||||
{
|
||||
id: 'oa2-google-oauth2',
|
||||
name: 'Google',
|
||||
iconClass: 'fa-google-plus',
|
||||
loginUrl: '/auth/login/google-oauth2/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/google-oauth2/?auth_entry=account_register'
|
||||
},
|
||||
{
|
||||
id: 'oa2-facebook',
|
||||
name: 'Facebook',
|
||||
iconClass: 'fa-facebook',
|
||||
loginUrl: '/auth/login/facebook/?auth_entry=account_login',
|
||||
registerUrl: '/auth/login/facebook/?auth_entry=account_register'
|
||||
}
|
||||
]
|
||||
},
|
||||
HINTED_PROVIDER = "oa2-google-oauth2";
|
||||
|
||||
var createHintedLoginView = function(test) {
|
||||
// Initialize the login view
|
||||
view = new HintedLoginView({
|
||||
thirdPartyAuth: THIRD_PARTY_AUTH,
|
||||
hintedProvider: HINTED_PROVIDER,
|
||||
platformName: PLATFORM_NAME
|
||||
});
|
||||
|
||||
// Mock the redirect call
|
||||
spyOn( view, 'redirect' ).andCallFake( function() {} );
|
||||
|
||||
view.render();
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<div id="hinted-login-form"></div>');
|
||||
TemplateHelpers.installTemplate('templates/student_account/hinted_login');
|
||||
});
|
||||
|
||||
it('displays a choice as two buttons', function() {
|
||||
createHintedLoginView(this);
|
||||
|
||||
expect($('.proceed-button.button-oa2-google-oauth2')).toBeVisible();
|
||||
expect($('.form-toggle')).toBeVisible();
|
||||
expect($('.proceed-button.button-oa2-facebook')).not.toBeVisible();
|
||||
});
|
||||
|
||||
it('redirects the user to the hinted provider if the user clicks the proceed button', function() {
|
||||
createHintedLoginView(this);
|
||||
|
||||
// Click the "Yes, proceed" button
|
||||
$('.proceed-button').click();
|
||||
|
||||
expect(view.redirect).toHaveBeenCalledWith( '/auth/login/google-oauth2/?auth_entry=account_login' );
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ var edx = edx || {};
|
||||
return new edx.student.account.AccessView({
|
||||
mode: container.data('initial-mode'),
|
||||
thirdPartyAuth: container.data('third-party-auth'),
|
||||
thirdPartyAuthHint: container.data('third-party-auth-hint'),
|
||||
nextUrl: container.data('next-url'),
|
||||
platformName: container.data('platform-name'),
|
||||
loginFormDesc: container.data('login-form-desc'),
|
||||
|
||||
@@ -19,7 +19,8 @@ var edx = edx || {};
|
||||
login: {},
|
||||
register: {},
|
||||
passwordHelp: {},
|
||||
institutionLogin: {}
|
||||
institutionLogin: {},
|
||||
hintedLogin: {}
|
||||
},
|
||||
|
||||
nextUrl: '/dashboard',
|
||||
@@ -43,6 +44,8 @@ var edx = edx || {};
|
||||
providers: []
|
||||
};
|
||||
|
||||
this.thirdPartyAuthHint = obj.thirdPartyAuthHint || null;
|
||||
|
||||
if (obj.nextUrl) {
|
||||
// Ensure that the next URL is internal for security reasons
|
||||
if ( ! window.isExternal( obj.nextUrl ) ) {
|
||||
@@ -54,7 +57,8 @@ var edx = edx || {};
|
||||
login: obj.loginFormDesc,
|
||||
register: obj.registrationFormDesc,
|
||||
reset: obj.passwordResetFormDesc,
|
||||
institution_login: null
|
||||
institution_login: null,
|
||||
hinted_login: null
|
||||
};
|
||||
|
||||
this.platformName = obj.platformName;
|
||||
@@ -160,6 +164,16 @@ var edx = edx || {};
|
||||
});
|
||||
|
||||
this.subview.institutionLogin.render();
|
||||
},
|
||||
|
||||
hinted_login: function ( unused ) {
|
||||
this.subview.hintedLogin = new edx.student.account.HintedLoginView({
|
||||
thirdPartyAuth: this.thirdPartyAuth,
|
||||
hintedProvider: this.thirdPartyAuthHint,
|
||||
platformName: this.platformName
|
||||
});
|
||||
|
||||
this.subview.hintedLogin.render();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
52
lms/static/js/student_account/views/HintedLoginView.js
Normal file
52
lms/static/js/student_account/views/HintedLoginView.js
Normal file
@@ -0,0 +1,52 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function($, _, gettext) {
|
||||
'use strict';
|
||||
|
||||
edx.student = edx.student || {};
|
||||
edx.student.account = edx.student.account || {};
|
||||
|
||||
edx.student.account.HintedLoginView = Backbone.View.extend({
|
||||
el: '#hinted-login-form',
|
||||
|
||||
tpl: '#hinted_login-tpl',
|
||||
|
||||
events: {
|
||||
'click .proceed-button': 'proceedWithHintedAuth'
|
||||
},
|
||||
|
||||
formType: 'hinted-login',
|
||||
|
||||
initialize: function( data ) {
|
||||
this.tpl = $(this.tpl).html();
|
||||
this.providers = data.thirdPartyAuth.providers || [];
|
||||
this.hintedProvider = _.findWhere(this.providers, {id: data.hintedProvider})
|
||||
this.platformName = data.platformName;
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
$(this.el).html( _.template( this.tpl, {
|
||||
// We pass the context object to the template so that
|
||||
// we can perform variable interpolation using sprintf
|
||||
providers: this.providers,
|
||||
platformName: this.platformName,
|
||||
hintedProvider: this.hintedProvider
|
||||
}));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
proceedWithHintedAuth: function( event ) {
|
||||
this.redirect(this.hintedProvider.loginUrl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirect to a URL. Mainly useful for mocking out in tests.
|
||||
* @param {string} url The URL to redirect to.
|
||||
*/
|
||||
redirect: function( url ) {
|
||||
window.location.href = url;
|
||||
}
|
||||
});
|
||||
})(jQuery, _, gettext);
|
||||
@@ -13,3 +13,7 @@
|
||||
<section id="institution_login-anchor" class="form-type">
|
||||
<div id="institution_login-form" class="form-wrapper hidden" aria-hidden="true"></div>
|
||||
</section>
|
||||
|
||||
<section id="hinted-login-anchor" class="form-type">
|
||||
<div id="hinted-login-form" class="form-wrapper <% if ( mode !== 'hinted_login' ) { %>hidden<% } %>"></div>
|
||||
</section>
|
||||
|
||||
24
lms/templates/student_account/hinted_login.underscore
Normal file
24
lms/templates/student_account/hinted_login.underscore
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="wrapper-other-login">
|
||||
<div class="section-title lines">
|
||||
<h2>
|
||||
<span class="text"><%- gettext("Sign in") %></span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<p class="instructions"><%- _.sprintf( gettext("Would you like to sign in using your %(providerName)s credentials?"), { providerName: hintedProvider.name } ) %></p>
|
||||
|
||||
<button class="action action-primary action-update proceed-button button-<%- hintedProvider.id %> hinted-login-<%- hintedProvider.id %>">
|
||||
<div class="icon fa <%- hintedProvider.iconClass %>" aria-hidden="true"></div>
|
||||
<%- _.sprintf( gettext("Sign in using %(providerName)s"), { providerName: hintedProvider.name } ) %>
|
||||
</button>
|
||||
|
||||
<div class="section-title lines">
|
||||
<h2>
|
||||
<span class="text"><%- gettext("or") %></span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="toggle-form">
|
||||
<button class="nav-btn form-toggle" data-type="login"><%- gettext("Show me other ways to sign in or register") %></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -15,7 +15,7 @@
|
||||
</%block>
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["account", "access", "form_field", "login", "register", "institution_login", "institution_register", "password_reset"]:
|
||||
% for template_name in ["account", "access", "form_field", "login", "register", "institution_login", "institution_register", "password_reset", "hinted_login"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="student_account/${template_name}.underscore" />
|
||||
</script>
|
||||
@@ -27,6 +27,7 @@
|
||||
class="login-register"
|
||||
data-initial-mode="${initial_mode}"
|
||||
data-third-party-auth='${third_party_auth|h}'
|
||||
data-third-party-auth-hint='${third_party_auth_hint}'
|
||||
data-next-url='${login_redirect_url|h}'
|
||||
data-platform-name='${platform_name}'
|
||||
data-login-form-desc='${login_form_desc|h}'
|
||||
|
||||
Reference in New Issue
Block a user