Commit cf308d87 by Braden MacDonald

New Hinted Login View - PR 8591

parent 7437bcfe
......@@ -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',
......
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();
}
},
......
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>
<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}'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment