Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions common/djangoapps/third_party_auth/apps.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# lint-amnesty, pylint: disable=missing-module-docstring

from django.apps import AppConfig
from django.conf import settings


class ThirdPartyAuthConfig(AppConfig): # lint-amnesty, pylint: disable=missing-class-docstring
Expand All @@ -11,16 +10,3 @@ class ThirdPartyAuthConfig(AppConfig): # lint-amnesty, pylint: disable=missing-
def ready(self):
# Import signal handlers to register them
from .signals import handlers # noqa: F401 pylint: disable=unused-import

# To override the settings before loading social_django.
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
self._enable_third_party_auth()

def _enable_third_party_auth(self):
"""
Enable the use of third_party_auth, which allows users to sign in to edX
using other identity providers. For configuration details, see
common/djangoapps/third_party_auth/settings.py.
"""
from common.djangoapps.third_party_auth import settings as auth_settings
auth_settings.apply_settings(settings)
4 changes: 4 additions & 0 deletions common/djangoapps/third_party_auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ class ProviderConfig(ConfigurationModel):
help_text="Use the presence of a profile from a trusted third party as proof of identity verification.",
)

# Enterprise-only field: excludes this provider from the EnterpriseCustomer Django admin IDP
# dropdown. Added in ENT-1366 after social auth providers (Facebook, Google, etc.) were linked
# as enterprise IDPs, incorrectly associating all their users with an enterprise. Should ideally
# be migrated into the enterprise plugin.
disable_for_enterprise_sso = models.BooleanField(
default=False,
verbose_name='Disabled for Enterprise TPA',
Expand Down
81 changes: 8 additions & 73 deletions common/djangoapps/third_party_auth/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,7 @@ def B(*args, **kwargs):
from openedx.core.djangoapps.user_authn.views.utils import get_auto_generated_username
from common.djangoapps.third_party_auth.utils import (
get_associated_user_by_email_response,
get_user_from_email,
is_enterprise_customer_user,
is_oauth_provider,
is_saml_provider,
user_exists,
)
from common.djangoapps.third_party_auth.toggles import is_tpa_next_url_on_dispatch_enabled
Expand Down Expand Up @@ -794,80 +791,18 @@ def associate_by_email_if_oauth(auth_entry, backend, details, user, strategy, *a
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.
Deprecated — enterprise SAML email association moved to
enterprise.tpa_pipeline.enterprise_associate_by_email.

This association is done ONLY if the user entered the pipeline belongs to SAML provider.
Retained as a no-op for backwards compatibility with custom pipeline configs.
"""
from openedx.features.enterprise_support.api import enterprise_is_enabled

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 {})

@enterprise_is_enabled()
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(
'[Multiple_SSO_SAML_Accounts_Association_to_User] Enterprise user verification:'
'User Email: {email}, User ID: {user_id}, Provider ID: {provider_id},'
' is_enterprise_customer_user: {enterprise_customer_user}'.format(
email=current_user.email,
user_id=current_user.id,
provider_id=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, user_is_active = get_associated_user_by_email_response(
backend, details, user, *args, **kwargs)

if not user_is_active:
logger.info(
'[Multiple_SSO_SAML_Accounts_Association_to_User] User association account is not'
' active: User Email: {email}, User ID: {user_id}, Provider ID: {provider_id},'
' is_enterprise_customer_user: {enterprise_customer_user}'.format(
email=current_user.email,
user_id=current_user.id,
provider_id=current_provider.provider_id,
enterprise_customer_user=enterprise_customer_user
)
)
return None

return association_response

except Exception as ex: # pylint: disable=broad-except
logger.exception('[Multiple_SSO_SAML_Accounts_Association_to_User] Error in'
' saml multiple accounts association: User ID: %s, User Email: %s:,'
'Provider ID: %s, Exception: %s', current_user.id, current_user.email,
current_provider.provider_id, ex)

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
logger.warning(
"associate_by_email_if_saml is deprecated and is now a no-op. "
"Enterprise SAML email association has moved to "
"enterprise.tpa_pipeline.enterprise_associate_by_email."
)
Comment thread
pwnage101 marked this conversation as resolved.


def user_details_force_sync(auth_entry, strategy, details, user=None, *args, **kwargs): # lint-amnesty, pylint: disable=keyword-arg-before-vararg
Expand Down
21 changes: 16 additions & 5 deletions common/djangoapps/third_party_auth/saml.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,22 @@ def auth_url(self):

def disconnect(self, *args, **kwargs):
"""
Override of SAMLAuth.disconnect to unlink the learner from enterprise customer if associated.
"""
from openedx.features.enterprise_support.api import unlink_enterprise_user_from_idp
user = kwargs.get('user', None)
unlink_enterprise_user_from_idp(self.strategy.request, user, self.name)
Override of SAMLAuth.disconnect to emit a signal when a user disconnects their SAML account.
"""
from common.djangoapps.third_party_auth.signals import SAMLAccountDisconnected
# Emit the signal before super().disconnect() so that handlers (e.g. enterprise
# user unlinking) run while the social auth record still exists.
user = kwargs['user'] # Upstream social_core always passes a non-None user.
log.info(
'[THIRD_PARTY_AUTH] Emitting SAMLAccountDisconnected signal for user_id=%s, backend=%s',
user.id, self.name,
)
SAMLAccountDisconnected.send(
sender=self.__class__,
request=self.strategy.request,
user=user,
saml_backend=self,
)
return super().disconnect(*args, **kwargs)

def _check_entitlements(self, idp, attributes):
Expand Down
107 changes: 0 additions & 107 deletions common/djangoapps/third_party_auth/settings.py

This file was deleted.

6 changes: 5 additions & 1 deletion common/djangoapps/third_party_auth/signals/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Signal handlers for third_party_auth app
"""
Signals and signal handlers for third_party_auth app
"""

from common.djangoapps.third_party_auth.signals.signals import SAMLAccountDisconnected # noqa: F401
12 changes: 12 additions & 0 deletions common/djangoapps/third_party_auth/signals/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Signals defined by the third_party_auth app.
"""
from django.dispatch import Signal

# Signal fired when a user disconnects a SAML account.
#
# Keyword arguments sent with this signal:
# request (HttpRequest): The HTTP request during which the disconnect occurred.
# user (User): The Django User disconnecting the social auth account.
Comment thread
pwnage101 marked this conversation as resolved.
# saml_backend (social_core.backends.saml.SAMLAuth): The SAMLAuth backend instance (has a ``name`` attribute).
SAMLAccountDisconnected = Signal()
Loading
Loading