ENT-3798 Multiple_SSO_Accounts_Association_to_SAML_User (#26170)

This commit is contained in:
Zaman Afzal
2021-02-01 11:44:12 +05:00
committed by GitHub
parent ec5b78c625
commit b99a64c385
7 changed files with 212 additions and 2 deletions

View File

@@ -87,9 +87,15 @@ from lms.djangoapps.verify_student.models import SSOVerification
from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api import accounts
from openedx.core.djangoapps.user_api.accounts.utils import is_multiple_sso_accounts_association_to_saml_user_enabled
from openedx.core.djangoapps.user_authn import cookies as user_authn_cookies
from openedx.core.djangoapps.user_authn.utils import should_redirect_to_authn_microfrontend
from common.djangoapps.third_party_auth.utils import user_exists
from common.djangoapps.third_party_auth.utils import (
get_user_from_email,
is_enterprise_customer_user,
is_saml_provider,
user_exists,
)
from common.djangoapps.track import segment
from common.djangoapps.util.json_request import JsonResponse
@@ -740,6 +746,92 @@ def associate_by_email_if_login_api(auth_entry, backend, details, user, current_
return association_response
@partial.partial
def associate_by_email_if_saml(auth_entry, backend, details, user, strategy, *args, **kwargs):
"""
This pipeline step associates the current social auth with the user with the
same email address in the database. It defers to the social library's associate_by_email
implementation, which verifies that only a single database user is associated with the email.
This association is done ONLY if the user entered the pipeline belongs to SAML provider.
"""
def get_user():
"""
This is the helper method to get the user from system by matching email.
"""
user_details = {'email': details.get('email')} if details else None
return get_user_from_email(user_details or {})
def associate_by_email_if_enterprise_user():
"""
If the learner arriving via SAML is already linked to the enterprise customer linked to the same IdP,
they should not be prompted for their edX password.
"""
try:
enterprise_customer_user = is_enterprise_customer_user(current_provider.provider_id, current_user)
logger.info(
u'[Multiple_SSO_SAML_Accounts_Association_to_User] Enterprise user verification:'
u'Email: {email}, user_id: {user_id}, Provider: {provider},'
u' enterprise_customer_user: {enterprise_customer_user}'.format(
email=current_user.email,
user_id=current_user.id,
provider=current_provider.provider_id,
enterprise_customer_user=enterprise_customer_user,
)
)
if enterprise_customer_user:
# this is python social auth pipeline default method to automatically associate social accounts
# if the email already matches a user account.
association_response = associate_by_email(backend, details, user, *args, **kwargs)
if (
association_response and
association_response.get('user') and
association_response['user'].is_active
):
# Only return the user matched by email if their email has been activated.
# Otherwise, an illegitimate user can create an account with another user's
# email address and the legitimate user would now login to the illegitimate
# account.
return association_response
elif (
association_response and
association_response.get('user') and
not association_response['user'].is_active
):
logger.info(
u'[Multiple_SSO_SAML_Accounts_Association_to_User] User association account is not'
u' active: Email: {email}, user_id: {user_id}, Provider: {provider},'
u' enterprise_customer_user: {enterprise_customer_user}'.format(
email=current_user.email,
user_id=current_user.id,
provider=current_provider.provider_id,
enterprise_customer_user=enterprise_customer_user
)
)
return None
except Exception as ex: # pylint: disable=broad-except
logger.exception('[Multiple_SSO_SAML_Accounts_Association_to_User] Error in'
' saml multiple accounts association: %s:, %s:', current_user.id, ex)
# this is waffle switch to enable and disable this functionality from admin panel.
if is_multiple_sso_accounts_association_to_saml_user_enabled():
saml_provider, current_provider = is_saml_provider(strategy.request.backend.name, kwargs)
if saml_provider:
# get the user by matching email if the pipeline user is not available.
current_user = user if user else get_user()
# Verify that the user linked to enterprise customer of current identity provider and an active user
associate_response = associate_by_email_if_enterprise_user() if current_user else None
if associate_response:
return associate_response
def user_details_force_sync(auth_entry, strategy, details, user=None, *args, **kwargs):
"""
Update normally protected user details using data from provider.