Commit 65dafe9f by Jesse Shapiro

Add Enterprise sidebar to login and registration views; remove unneeded fields;…

Add Enterprise sidebar to login and registration views; remove unneeded fields; make registration form more narrow
parent 8540e8f4
......@@ -367,20 +367,38 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
@ddt.data("signin_user", "register_user")
def test_third_party_auth_disabled(self, url_name):
response = self.client.get(reverse(url_name))
self._assert_third_party_auth_data(response, None, None, [])
self._assert_third_party_auth_data(response, None, None, [], None)
@mock.patch('student_account.views.enterprise_customer_for_request')
@ddt.data(
("signin_user", None, None),
("register_user", None, None),
("signin_user", "google-oauth2", "Google"),
("register_user", "google-oauth2", "Google"),
("signin_user", "facebook", "Facebook"),
("register_user", "facebook", "Facebook"),
("signin_user", "dummy", "Dummy"),
("register_user", "dummy", "Dummy"),
("signin_user", None, None, None),
("register_user", None, None, None),
("signin_user", "google-oauth2", "Google", None),
("register_user", "google-oauth2", "Google", None),
("signin_user", "facebook", "Facebook", None),
("register_user", "facebook", "Facebook", None),
("signin_user", "dummy", "Dummy", None),
("register_user", "dummy", "Dummy", None),
(
"signin_user",
"google-oauth2",
"Google",
{
'name': 'FakeName',
'logo': 'https://host.com/logo.jpg',
'welcome_msg': 'No message'
}
)
)
@ddt.unpack
def test_third_party_auth(self, url_name, current_backend, current_provider):
def test_third_party_auth(
self,
url_name,
current_backend,
current_provider,
expected_enterprise_customer_mock_attrs,
enterprise_customer_mock
):
params = [
('course_id', 'course-v1:Org+Course+Run'),
('enrollment_action', 'enroll'),
......@@ -389,6 +407,21 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
('next', '/custom/final/destination'),
]
if expected_enterprise_customer_mock_attrs:
expected_ec = mock.MagicMock(
branding_configuration=mock.MagicMock(
logo=mock.MagicMock(
url=expected_enterprise_customer_mock_attrs['logo']
),
welcome_message=expected_enterprise_customer_mock_attrs['welcome_msg']
)
)
expected_ec.name = expected_enterprise_customer_mock_attrs['name']
else:
expected_ec = None
enterprise_customer_mock.return_value = expected_ec
# Simulate a running pipeline
if current_backend is not None:
pipeline_target = "student_account.views.third_party_auth.pipeline"
......@@ -426,7 +459,13 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
"registerUrl": self._third_party_login_url("google-oauth2", "register", params)
},
]
self._assert_third_party_auth_data(response, current_backend, current_provider, expected_providers)
self._assert_third_party_auth_data(
response,
current_backend,
current_provider,
expected_providers,
expected_ec
)
def test_hinted_login(self):
params = [("next", "/courses/something/?tpa_hint=oa2-google-oauth2")]
......@@ -455,6 +494,59 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
target_status_code=302
)
@mock.patch('student_account.views.enterprise_customer_for_request')
@ddt.data(
('signin_user', False, None, None, None),
('register_user', False, None, None, None),
('signin_user', True, 'Fake EC', 'http://logo.com/logo.jpg', u'{enterprise_name} - {platform_name}'),
('register_user', True, 'Fake EC', 'http://logo.com/logo.jpg', u'{enterprise_name} - {platform_name}'),
('signin_user', True, 'Fake EC', None, u'{enterprise_name} - {platform_name}'),
('register_user', True, 'Fake EC', None, u'{enterprise_name} - {platform_name}'),
('signin_user', True, 'Fake EC', 'http://logo.com/logo.jpg', None),
('register_user', True, 'Fake EC', 'http://logo.com/logo.jpg', None),
('signin_user', True, 'Fake EC', None, None),
('register_user', True, 'Fake EC', None, None),
)
@ddt.unpack
def test_enterprise_register(self, url_name, ec_present, ec_name, logo_url, welcome_message, mock_get_ec):
"""
Verify that when an EnterpriseCustomer is received on the login and register views,
the appropriate sidebar is rendered.
"""
if ec_present:
mock_ec = mock_get_ec.return_value
mock_ec.name = ec_name
if logo_url:
mock_ec.branding_configuration.logo.url = logo_url
else:
mock_ec.branding_configuration.logo = None
if welcome_message:
mock_ec.branding_configuration.welcome_message = welcome_message
else:
del mock_ec.branding_configuration.welcome_message
else:
mock_get_ec.return_value = None
response = self.client.get(reverse(url_name), HTTP_ACCEPT="text/html")
enterprise_sidebar_div_id = u'enterprise-content-container'
if not ec_present:
self.assertNotContains(response, text=enterprise_sidebar_div_id)
else:
self.assertContains(response, text=enterprise_sidebar_div_id)
if not welcome_message:
welcome_message = settings.ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
expected_message = welcome_message.format(
start_bold=u'<b>',
end_bold=u'</b>',
enterprise_name=ec_name,
platform_name=settings.PLATFORM_NAME
)
self.assertContains(response, expected_message)
if logo_url:
self.assertContains(response, logo_url)
@override_settings(SITE_NAME=settings.MICROSITE_TEST_HOSTNAME)
def test_microsite_uses_old_login_page(self):
# Retrieve the login page from a microsite domain
......@@ -494,7 +586,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
self.assertEqual(resp['X-Frame-Options'], 'ALLOW')
def _assert_third_party_auth_data(self, response, current_backend, current_provider, providers):
def _assert_third_party_auth_data(self, response, current_backend, current_provider, providers, expected_ec):
"""Verify that third party auth info is rendered correctly in a DOM data attribute. """
finish_auth_url = None
if current_backend:
......@@ -507,6 +599,9 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
"finishAuthUrl": finish_auth_url,
"errorMessage": None,
}
if expected_ec is not None:
# If we set an EnterpriseCustomer, third-party auth providers ought to be hidden.
auth_info['providers'] = []
auth_info = dump_js_escaped_json(auth_info)
expected_data = '"third_party_auth": {auth_info}'.format(
......
......@@ -18,7 +18,7 @@ from django.utils.translation import ugettext as _
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from django_countries import countries
from edxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response, render_to_string
import pytz
from commerce.models import CommerceConfiguration
......@@ -34,7 +34,10 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site
from openedx.core.djangoapps.user_api.accounts.api import request_password_change
from openedx.core.djangoapps.user_api.errors import UserNotFound
from openedx.features.enterprise_support.api import set_enterprise_branding_filter_param
from openedx.features.enterprise_support.api import (
enterprise_customer_for_request,
set_enterprise_branding_filter_param
)
from openedx.core.lib.time_zone_utils import TIME_ZONE_CHOICES
from openedx.core.lib.edx_api_utils import get_edx_api_data
from student.models import UserProfile
......@@ -144,6 +147,8 @@ def login_and_registration_form(request, initial_mode="login"):
),
}
context = update_context_for_enterprise(request, context)
return render_to_response('student_account/login_and_register.html', context)
......@@ -197,6 +202,89 @@ def password_change_request_handler(request):
return HttpResponseBadRequest(_("No email address provided."))
def update_context_for_enterprise(request, context):
"""
Take the processed context produced by the view, determine if it's relevant
to a particular Enterprise Customer, and update it to include that customer's
enterprise metadata.
"""
context = context.copy()
sidebar_context = enterprise_sidebar_context(request)
if sidebar_context:
context['data']['registration_form_desc']['fields'] = enterprise_fields_only(
context['data']['registration_form_desc']
)
context.update(sidebar_context)
context['enable_enterprise_sidebar'] = True
else:
context['enable_enterprise_sidebar'] = False
return context
def enterprise_fields_only(fields):
"""
Take the received field definition, and exclude those fields that we don't want
to require if the user is going to be a member of an Enterprise Customer.
"""
enterprise_exclusions = configuration_helpers.get_value(
'ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS',
settings.ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS
)
return [field for field in fields['fields'] if field['name'] not in enterprise_exclusions]
def enterprise_sidebar_context(request):
"""
Given the current request, render the HTML of a sidebar for the current
logistration view that depicts Enterprise-related information.
"""
enterprise_customer = enterprise_customer_for_request(request)
if not enterprise_customer:
return {}
platform_name = configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
if enterprise_customer.branding_configuration.logo:
enterprise_logo_url = enterprise_customer.branding_configuration.logo.url
else:
enterprise_logo_url = ''
if getattr(enterprise_customer.branding_configuration, 'welcome_message', None):
branded_welcome_template = enterprise_customer.branding_configuration.welcome_message
else:
branded_welcome_template = configuration_helpers.get_value(
'ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE',
settings.ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
)
branded_welcome_string = branded_welcome_template.format(
start_bold=u'<b>',
end_bold=u'</b>',
enterprise_name=enterprise_customer.name,
platform_name=platform_name
)
platform_welcome_template = configuration_helpers.get_value(
'ENTERPRISE_PLATFORM_WELCOME_TEMPLATE',
settings.ENTERPRISE_PLATFORM_WELCOME_TEMPLATE
)
platform_welcome_string = platform_welcome_template.format(platform_name=platform_name)
context = {
'enterprise_name': enterprise_customer.name,
'enterprise_logo_url': enterprise_logo_url,
'enterprise_branded_welcome_string': branded_welcome_string,
'platform_welcome_string': platform_welcome_string,
}
return context
def _third_party_auth_context(request, redirect_to, tpa_hint=None):
"""Context for third party auth providers and the currently running pipeline.
......@@ -221,24 +309,25 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None):
}
if third_party_auth.is_enabled():
for enabled in third_party_auth.provider.Registry.displayed_for_login(tpa_hint=tpa_hint):
info = {
"id": enabled.provider_id,
"name": enabled.name,
"iconClass": enabled.icon_class or None,
"iconImage": enabled.icon_image.url if enabled.icon_image else None,
"loginUrl": pipeline.get_login_url(
enabled.provider_id,
pipeline.AUTH_ENTRY_LOGIN,
redirect_url=redirect_to,
),
"registerUrl": pipeline.get_login_url(
enabled.provider_id,
pipeline.AUTH_ENTRY_REGISTER,
redirect_url=redirect_to,
),
}
context["providers" if not enabled.secondary else "secondaryProviders"].append(info)
if not enterprise_customer_for_request(request):
for enabled in third_party_auth.provider.Registry.displayed_for_login(tpa_hint=tpa_hint):
info = {
"id": enabled.provider_id,
"name": enabled.name,
"iconClass": enabled.icon_class or None,
"iconImage": enabled.icon_image.url if enabled.icon_image else None,
"loginUrl": pipeline.get_login_url(
enabled.provider_id,
pipeline.AUTH_ENTRY_LOGIN,
redirect_url=redirect_to,
),
"registerUrl": pipeline.get_login_url(
enabled.provider_id,
pipeline.AUTH_ENTRY_REGISTER,
redirect_url=redirect_to,
),
}
context["providers" if not enabled.secondary else "secondaryProviders"].append(info)
running_pipeline = pipeline.get(request)
if running_pipeline is not None:
......
......@@ -951,6 +951,25 @@ ENTERPRISE_API_CACHE_TIMEOUT = ENV_TOKENS.get(
ENTERPRISE_API_CACHE_TIMEOUT
)
############## ENTERPRISE SERVICE LMS CONFIGURATION ##################################
# The LMS has some features embedded that are related to the Enterprise service, but
# which are not provided by the Enterprise service. These settings override the
# base values for the parameters as defined in common.py
ENTERPRISE_PLATFORM_WELCOME_TEMPLATE = ENV_TOKENS.get(
'ENTERPRISE_PLATFORM_WELCOME_TEMPLATE',
ENTERPRISE_PLATFORM_WELCOME_TEMPLATE
)
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE = ENV_TOKENS.get(
'ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE',
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
)
ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS = set(
ENV_TOKENS.get(
'ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS',
ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS
)
)
############## CATALOG/DISCOVERY SERVICE API CLIENT CONFIGURATION ######################
# The LMS communicates with the Catalog service via the EdxRestApiClient class
......
......@@ -3142,6 +3142,26 @@ ENTERPRISE_SERVICE_WORKER_USERNAME = 'enterprise_worker'
ENTERPRISE_API_CACHE_TIMEOUT = 3600 # Value is in seconds
ENTERPRISE_CUSTOMER_LOGO_IMAGE_SIZE = 512 # Enterprise logo image size limit in KB's
############## ENTERPRISE SERVICE LMS CONFIGURATION ##################################
# The LMS has some features embedded that are related to the Enterprise service, but
# which are not provided by the Enterprise service. These settings provide base values
# for those features.
ENTERPRISE_PLATFORM_WELCOME_TEMPLATE = _(u'Welcome to {platform_name}.')
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE = _(
u'{start_bold}{enterprise_name}{end_bold} has partnered with {start_bold}'
'{platform_name}{end_bold} to offer you high-quality learning opportunities '
'from the world\'s best universities.'
)
ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS = {
'age',
'level_of_education',
'gender',
'goals',
'year_of_birth',
'mailing_address',
}
############## Settings for Course Enrollment Modes ######################
COURSE_ENROLLMENT_MODES = {
"audit": 1,
......
......@@ -3,16 +3,50 @@
@import '../base/grid-settings';
@import "neat/neat"; // lib - Neat
.login-register {
@media (min-width: 768px) {
.enterprise-content {
width: 20%;
float: left;
height: 100%;
padding-left: $baseline;
padding-right: $baseline;
}
.login-register.border-left {
border-left: 1px solid #d9d9d9;
padding-left: ($baseline*1.5);
padding-right: $baseline;
}
}
@media (max-width: 767px) {
.enterprise-content {
margin: auto auto;
display: block;
padding-left: ($baseline/2);
padding-right: ($baseline/2);
img.enterprise-logo {
display: none;
}
}
}
.window-wrap {
background: $white;
}
.login-register-content {
@include box-sizing(border-box);
@include outer-container;
$grid-columns: 12;
background: $white;
min-height: 100%;
width: 100%;
padding-left: ($baseline/2);
padding-right: ($baseline/2);
$third-party-button-height: ($baseline*1.75);
justify-content: center;
margin-top: $baseline;
background: $white;
display: flex;
flex-wrap: wrap;
-webkit-flex-wrap: wrap;
-moz-flex-wrap: wrap;
h2 {
@extend %t-title4;
......@@ -35,6 +69,21 @@
@extend %expand-clickable-area;
}
a {
text-decoration: underline;
}
}
.login-register {
$grid-columns: 12;
background: $white;
min-height: 100%;
padding-left: ($baseline/2);
padding-right: ($baseline/2);
$third-party-button-height: ($baseline*1.75);
display: inline-block;
max-width: 500px;
.instructions {
@extend %t-copy-base;
}
......@@ -587,3 +636,26 @@
.supplemental-link {
margin: 1rem 0;
}
.enterprise-content {
display: inline-block;
text-align: left;
vertical-align: top;
max-width: 500px;
.centered-div {
margin: 0 auto;
margin-right: 0px;
float: right;
}
img {
height: 100px;
}
h2 {
font-size: 16px;
line-height: 1.5;
color: $gray-d2;
}
}
<div id="enterprise-content-container" class="enterprise-content">
<div class="centered-div">
% if enterprise_logo_url:
<img src="${enterprise_logo_url}" alt="${enterprise_name}" class="enterprise-logo">
% endif
<h2>
${enterprise_branded_welcome_string}
</h2>
<h2>
${platform_welcome_string}
</h2>
</div>
</div>
......@@ -29,11 +29,22 @@
<script type="text/template" id="${template_name}-tpl">
<%static:include path="student_account/${template_name}.underscore" />
</script>
% endfor
% endfor
</%block>
<div class="section-bkg-wrapper">
<main id="main" aria-label="Content" tabindex="-1">
<div id="login-and-registration-container" class="login-register" />
<div id="content-container" class="login-register-content">
% if enable_enterprise_sidebar:
<%include file="enterprise_sidebar.html" />
<%
border_class = 'border-left'
%>
% else:
<%
border_class = ''
%>
% endif
<div id="login-and-registration-container" class="login-register ${border_class}"></div>
</div>
</main>
</div>
......@@ -19,7 +19,8 @@ from slumber.exceptions import HttpClientError, HttpServerError
from edx_rest_api_client.client import EdxRestApiClient
try:
from enterprise import utils as enterprise_utils
from enterprise.models import EnterpriseCourseEnrollment
from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomer
from enterprise.tpa_pipeline import get_enterprise_customer_for_request
from enterprise.utils import consent_necessary_for_course
except ImportError:
pass
......@@ -233,6 +234,32 @@ def enterprise_enabled():
return 'enterprise' in settings.INSTALLED_APPS and getattr(settings, 'ENABLE_ENTERPRISE_INTEGRATION', True)
def enterprise_customer_for_request(request, tpa_hint=None):
"""
Check all the context clues of the request to determine if
the request being made is tied to a particular EnterpriseCustomer.
"""
if not enterprise_enabled():
return None
ec = get_enterprise_customer_for_request(request)
if not ec and tpa_hint:
try:
ec = EnterpriseCustomer.objects.get(enterprise_customer_identity_provider__provider_id=tpa_hint)
except EnterpriseCustomer.DoesNotExist:
pass
ec_uuid = request.GET.get('enterprise_customer')
if not ec and ec_uuid:
try:
ec = EnterpriseCustomer.objects.get(uuid=ec_uuid)
except (EnterpriseCustomer.DoesNotExist, ValueError):
ec = None
return ec
def consent_needed_for_course(user, course_id):
"""
Wrap the enterprise app check to determine if the user needs to grant
......
......@@ -10,6 +10,7 @@ from django.test.utils import override_settings
from openedx.features.enterprise_support.api import (
enterprise_enabled,
enterprise_customer_for_request,
insert_enterprise_pipeline_elements,
data_sharing_consent_required,
set_enterprise_branding_filter_param,
......@@ -117,6 +118,48 @@ class TestEnterpriseApi(unittest.TestCase):
logo_url = get_enterprise_customer_logo_url(request)
self.assertEqual(logo_url, None)
@override_settings(ENABLE_ENTERPRISE_INTEGRATION=True)
@mock.patch('openedx.features.enterprise_support.api.get_enterprise_customer_for_request')
@mock.patch('openedx.features.enterprise_support.api.EnterpriseCustomer')
def test_enterprise_customer_for_request(self, ec_class_mock, get_ec_pipeline_mock):
"""
Test that the correct EnterpriseCustomer, if any, is returned.
"""
def get_ec_mock(**kwargs):
by_provider_id_kw = 'enterprise_customer_identity_provider__provider_id'
provider_id = kwargs.get(by_provider_id_kw, '')
uuid = kwargs.get('uuid', '')
if uuid == 'real-uuid' or provider_id == 'real-provider-id':
return 'this-is-actually-an-enterprise-customer'
elif uuid == 'not-a-uuid':
raise ValueError
else:
raise Exception
ec_class_mock.DoesNotExist = Exception
ec_class_mock.objects.get.side_effect = get_ec_mock
get_ec_pipeline_mock.return_value = None
request = mock.MagicMock()
request.GET.get.return_value = 'real-uuid'
self.assertEqual(enterprise_customer_for_request(request), 'this-is-actually-an-enterprise-customer')
request.GET.get.return_value = 'not-a-uuid'
self.assertEqual(enterprise_customer_for_request(request), None)
request.GET.get.return_value = 'fake-uuid'
self.assertEqual(enterprise_customer_for_request(request), None)
request.GET.get.return_value = None
self.assertEqual(
enterprise_customer_for_request(request, tpa_hint='real-provider-id'),
'this-is-actually-an-enterprise-customer'
)
self.assertEqual(enterprise_customer_for_request(request, tpa_hint='fake-provider-id'), None)
self.assertEqual(enterprise_customer_for_request(request, tpa_hint=None), None)
get_ec_pipeline_mock.return_value = 'also-a-real-enterprise'
self.assertEqual(enterprise_customer_for_request(request), 'also-a-real-enterprise')
def check_data_sharing_consent(self, consent_required=False, consent_url=None):
"""
Used to test the data_sharing_consent_required view decorator.
......
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