Unverified Commit 79d95e9c by Saleem Latif Committed by GitHub

Merge pull request #16691 from edx/saleem-latif/ENT-760

[ENT-760]:  Disable linking of personal accounts to enterprise customers via SSO
parents aefaab72 86da9c1c
...@@ -84,6 +84,7 @@ import student ...@@ -84,6 +84,7 @@ import student
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from eventtracking import tracker from eventtracking import tracker
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from third_party_auth.utils import user_exists
from . import provider from . import provider
...@@ -501,7 +502,7 @@ def set_pipeline_timeout(strategy, user, *args, **kwargs): ...@@ -501,7 +502,7 @@ def set_pipeline_timeout(strategy, user, *args, **kwargs):
# choice of the user. # choice of the user.
def redirect_to_custom_form(request, auth_entry, kwargs): def redirect_to_custom_form(request, auth_entry, details, kwargs):
""" """
If auth_entry is found in AUTH_ENTRY_CUSTOM, this is used to send provider If auth_entry is found in AUTH_ENTRY_CUSTOM, this is used to send provider
data to an external server's registration/login page. data to an external server's registration/login page.
...@@ -520,7 +521,7 @@ def redirect_to_custom_form(request, auth_entry, kwargs): ...@@ -520,7 +521,7 @@ def redirect_to_custom_form(request, auth_entry, kwargs):
"auth_entry": auth_entry, "auth_entry": auth_entry,
"backend_name": backend_name, "backend_name": backend_name,
"provider_id": provider_id, "provider_id": provider_id,
"user_details": kwargs['details'], "user_details": details,
}) })
digest = hmac.new(secret_key, msg=data_str, digestmod=hashlib.sha256).digest() digest = hmac.new(secret_key, msg=data_str, digestmod=hashlib.sha256).digest()
# Store the data in the session temporarily, then redirect to a page that will POST it to # Store the data in the session temporarily, then redirect to a page that will POST it to
...@@ -535,7 +536,7 @@ def redirect_to_custom_form(request, auth_entry, kwargs): ...@@ -535,7 +536,7 @@ def redirect_to_custom_form(request, auth_entry, kwargs):
@partial.partial @partial.partial
def ensure_user_information(strategy, auth_entry, backend=None, user=None, social=None, current_partial=None, def ensure_user_information(strategy, auth_entry, backend=None, user=None, social=None, current_partial=None,
allow_inactive_user=False, *args, **kwargs): allow_inactive_user=False, details=None, *args, **kwargs):
""" """
Ensure that we have the necessary information about a user (either an Ensure that we have the necessary information about a user (either an
existing account or registration data) to proceed with the pipeline. existing account or registration data) to proceed with the pipeline.
...@@ -567,6 +568,11 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia ...@@ -567,6 +568,11 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia
(current_provider.skip_email_verification or current_provider.send_to_registration_first)) (current_provider.skip_email_verification or current_provider.send_to_registration_first))
if not user: if not user:
if user_exists(details or {}):
# User has not already authenticated and the details sent over from
# identity provider belong to an existing user.
return dispatch_to_login()
if is_api(auth_entry): if is_api(auth_entry):
return HttpResponseBadRequest() return HttpResponseBadRequest()
elif auth_entry == AUTH_ENTRY_LOGIN: elif auth_entry == AUTH_ENTRY_LOGIN:
...@@ -583,7 +589,7 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia ...@@ -583,7 +589,7 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia
raise AuthEntryError(backend, 'auth_entry is wrong. Settings requires a user.') raise AuthEntryError(backend, 'auth_entry is wrong. Settings requires a user.')
elif auth_entry in AUTH_ENTRY_CUSTOM: elif auth_entry in AUTH_ENTRY_CUSTOM:
# Pass the username, email, etc. via query params to the custom entry page: # Pass the username, email, etc. via query params to the custom entry page:
return redirect_to_custom_form(strategy.request, auth_entry, kwargs) return redirect_to_custom_form(strategy.request, auth_entry, details or {}, kwargs)
else: else:
raise AuthEntryError(backend, 'auth_entry invalid') raise AuthEntryError(backend, 'auth_entry invalid')
......
...@@ -894,8 +894,9 @@ class IntegrationTest(testutil.TestCase, test.TestCase): ...@@ -894,8 +894,9 @@ class IntegrationTest(testutil.TestCase, test.TestCase):
strategy.storage.user.create_user(username=self.get_username(), email='user@email.com', password='password') strategy.storage.user.create_user(username=self.get_username(), email='user@email.com', password='password')
backend = strategy.request.backend backend = strategy.request.backend
backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy)) backend.auth_complete = mock.MagicMock(return_value=self.fake_auth_complete(strategy))
# If learner already has an account then make sure login page is served instead of registration.
# pylint: disable=protected-access # pylint: disable=protected-access
self.assert_redirect_to_register_looks_correct(actions.do_complete(backend, social_views._do_login)) self.assert_redirect_to_login_looks_correct(actions.do_complete(backend, social_views._do_login))
distinct_username = pipeline.get(request)['kwargs']['username'] distinct_username = pipeline.get(request)['kwargs']['username']
self.assertNotEqual(original_username, distinct_username) self.assertNotEqual(original_username, distinct_username)
......
...@@ -35,8 +35,8 @@ class AzureADOauth2IntegrationTest(base.Oauth2IntegrationTest): ...@@ -35,8 +35,8 @@ class AzureADOauth2IntegrationTest(base.Oauth2IntegrationTest):
'aud': 'abcdefgh-1234-5678-900a-0aa0a00aa0aa', 'aud': 'abcdefgh-1234-5678-900a-0aa0a00aa0aa',
'tid': 'abcdefgh-1234-5678-900a-0aa0a00aa0aa', 'tid': 'abcdefgh-1234-5678-900a-0aa0a00aa0aa',
'amr': ['pwd'], 'amr': ['pwd'],
'unique_name': 'email_value@example.com', 'unique_name': 'user@email.com',
'upn': 'email_value@example.com', 'upn': 'user@email.com',
'family_name': 'family_name_value', 'family_name': 'family_name_value',
'name': 'name_value', 'name': 'name_value',
'given_name': 'given_name_value', 'given_name': 'given_name_value',
......
...@@ -31,7 +31,7 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest): ...@@ -31,7 +31,7 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest):
'token_type': 'token_type_value', 'token_type': 'token_type_value',
} }
USER_RESPONSE_DATA = { USER_RESPONSE_DATA = {
'email': 'email_value@example.com', 'email': 'user@email.com',
'family_name': 'family_name_value', 'family_name': 'family_name_value',
'given_name': 'given_name_value', 'given_name': 'given_name_value',
'id': 'id_value', 'id': 'id_value',
...@@ -85,8 +85,8 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest): ...@@ -85,8 +85,8 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest):
'backend_name': 'google-oauth2', 'backend_name': 'google-oauth2',
'provider_id': 'oa2-google-oauth2', 'provider_id': 'oa2-google-oauth2',
'user_details': { 'user_details': {
'username': 'email_value', 'username': 'user',
'email': 'email_value@example.com', 'email': 'user@email.com',
'fullname': 'name_value', 'fullname': 'name_value',
'first_name': 'given_name_value', 'first_name': 'given_name_value',
'last_name': 'family_name_value', 'last_name': 'family_name_value',
......
"""
Tests for third_party_auth utility functions.
"""
import unittest
from django.conf import settings
from third_party_auth.tests.testutil import TestCase
from third_party_auth.utils import user_exists
from student.tests.factories import UserFactory
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestUtils(TestCase):
"""
Test the utility functions.
"""
def test_user_exists(self):
"""
Verify that user_exists function returns correct response.
"""
# Create users from factory
UserFactory(username='test_user', email='test_user@example.com')
self.assertTrue(
user_exists({'username': 'test_user', 'email': 'test_user@example.com'}),
)
self.assertTrue(
user_exists({'username': 'test_user'}),
)
self.assertTrue(
user_exists({'email': 'test_user@example.com'}),
)
self.assertFalse(
user_exists({'username': 'invalid_user'}),
)
"""
Utility functions for third_party_auth
"""
from django.contrib.auth.models import User
def user_exists(details):
"""
Return True if user with given details exist in the system.
Arguments:
details (dict): dictionary containing user infor like email, username etc.
Returns:
(bool): True if user with given details exists, `False` otherwise.
"""
user_queryset_filter = {}
email = details.get('email')
username = details.get('username')
if email:
user_queryset_filter['email'] = email
elif username:
user_queryset_filter['username'] = username
if user_queryset_filter:
return User.objects.filter(**user_queryset_filter).exists()
return False
...@@ -640,6 +640,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi ...@@ -640,6 +640,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
"finishAuthUrl": finish_auth_url, "finishAuthUrl": finish_auth_url,
"errorMessage": None, "errorMessage": None,
"registerFormSubmitButtonText": "Create Account", "registerFormSubmitButtonText": "Create Account",
"syncLearnerProfileData": False,
} }
if expected_ec is not None: if expected_ec is not None:
# If we set an EnterpriseCustomer, third-party auth providers ought to be hidden. # If we set an EnterpriseCustomer, third-party auth providers ought to be hidden.
......
...@@ -326,6 +326,7 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None): ...@@ -326,6 +326,7 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None):
"finishAuthUrl": None, "finishAuthUrl": None,
"errorMessage": None, "errorMessage": None,
"registerFormSubmitButtonText": _("Create Account"), "registerFormSubmitButtonText": _("Create Account"),
"syncLearnerProfileData": False,
} }
if third_party_auth.is_enabled(): if third_party_auth.is_enabled():
...@@ -357,6 +358,7 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None): ...@@ -357,6 +358,7 @@ def _third_party_auth_context(request, redirect_to, tpa_hint=None):
if current_provider is not None: if current_provider is not None:
context["currentProvider"] = current_provider.name context["currentProvider"] = current_provider.name
context["finishAuthUrl"] = pipeline.get_complete_url(current_provider.backend_name) context["finishAuthUrl"] = pipeline.get_complete_url(current_provider.backend_name)
context["syncLearnerProfileData"] = current_provider.sync_learner_profile_data
if current_provider.skip_registration_form: if current_provider.skip_registration_form:
# For enterprise (and later for everyone), we need to get explicit consent to the # For enterprise (and later for everyone), we need to get explicit consent to the
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length
); );
this.currentProvider = data.thirdPartyAuth.currentProvider || ''; this.currentProvider = data.thirdPartyAuth.currentProvider || '';
this.syncLearnerProfileData = data.thirdPartyAuth.syncLearnerProfileData || false;
this.errorMessage = data.thirdPartyAuth.errorMessage || ''; this.errorMessage = data.thirdPartyAuth.errorMessage || '';
this.platformName = data.platformName; this.platformName = data.platformName;
this.resetModel = data.resetModel; this.resetModel = data.resetModel;
...@@ -63,6 +64,7 @@ ...@@ -63,6 +64,7 @@
context: { context: {
fields: fields, fields: fields,
currentProvider: this.currentProvider, currentProvider: this.currentProvider,
syncLearnerProfileData: this.syncLearnerProfileData,
providers: this.providers, providers: this.providers,
hasSecondaryProviders: this.hasSecondaryProviders, hasSecondaryProviders: this.hasSecondaryProviders,
platformName: this.platformName, platformName: this.platformName,
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length
); );
this.currentProvider = data.thirdPartyAuth.currentProvider || ''; this.currentProvider = data.thirdPartyAuth.currentProvider || '';
this.syncLearnerProfileData = data.thirdPartyAuth.syncLearnerProfileData || false;
this.errorMessage = data.thirdPartyAuth.errorMessage || ''; this.errorMessage = data.thirdPartyAuth.errorMessage || '';
this.platformName = data.platformName; this.platformName = data.platformName;
this.autoSubmit = data.thirdPartyAuth.autoSubmitRegForm; this.autoSubmit = data.thirdPartyAuth.autoSubmitRegForm;
...@@ -141,6 +142,7 @@ ...@@ -141,6 +142,7 @@
context: { context: {
fields: fields, fields: fields,
currentProvider: this.currentProvider, currentProvider: this.currentProvider,
syncLearnerProfileData: this.syncLearnerProfileData,
providers: this.providers, providers: this.providers,
hasSecondaryProviders: this.hasSecondaryProviders, hasSecondaryProviders: this.hasSecondaryProviders,
platformName: this.platformName, platformName: this.platformName,
......
<div class="js-form-feedback" aria-live="assertive" tabindex="-1"> <div class="js-form-feedback" aria-live="assertive" tabindex="-1">
</div> </div>
<% if ( context.createAccountOption !== false ) { %> <% if ( context.createAccountOption !== false && !context.syncLearnerProfileData) { %>
<div class="toggle-form"> <div class="toggle-form">
<span class="text"><%- gettext("First time here?") %></span> <span class="text"><%- gettext("First time here?") %></span>
<a href="#login" class="form-toggle" data-type="register"><%- gettext("Create an Account.") %></a> <a href="#login" class="form-toggle" data-type="register"><%- gettext("Create an Account.") %></a>
......
<div class="js-form-feedback" aria-live="assertive" tabindex="-1"> <div class="js-form-feedback" aria-live="assertive" tabindex="-1">
</div> </div>
<div class="toggle-form"> <% if (!context.syncLearnerProfileData) { %>
<span class="text"><%- edx.StringUtils.interpolate(gettext('Already have an {platformName} account?'), {platformName: context.platformName }) %></span> <div class="toggle-form">
<a href="#login" class="form-toggle" data-type="login"><%- gettext("Sign in.") %></a> <span class="text"><%- edx.StringUtils.interpolate(gettext('Already have an {platformName} account?'), {platformName: context.platformName }) %></span>
</div> <a href="#login" class="form-toggle" data-type="login"><%- gettext("Sign in.") %></a>
</div>
<% } %>
<h2><%- gettext('Create an Account')%></h2>
<form id="register" class="register-form" autocomplete="off" tabindex="-1" method="POST"> <form id="register" class="register-form" autocomplete="off" tabindex="-1" method="POST">
......
...@@ -843,8 +843,11 @@ class RegistrationFormFactory(object): ...@@ -843,8 +843,11 @@ class RegistrationFormFactory(object):
# When the TPA Provider is configured to skip the registration form and we are in an # When the TPA Provider is configured to skip the registration form and we are in an
# enterprise context, we need to hide all fields except for terms of service and # enterprise context, we need to hide all fields except for terms of service and
# ensure that the user explicitly checks that field. # ensure that the user explicitly checks that field.
hide_registration_fields_except_tos = (current_provider.skip_registration_form and hide_registration_fields_except_tos = (
enterprise_customer_for_request(request)) (
current_provider.skip_registration_form and enterprise_customer_for_request(request)
) or current_provider.sync_learner_profile_data
)
for field_name in self.DEFAULT_FIELDS + self.EXTRA_FIELDS: for field_name in self.DEFAULT_FIELDS + self.EXTRA_FIELDS:
if field_name in field_overrides: if field_name in field_overrides:
......
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