<commit_before>"""
Slightly customized python-social-auth backend for SAML 2.0 support
"""


import logging
from copy import deepcopy

import requests
from django.contrib.sites.models import Site
from django.http import Http404
from django.utils.functional import cached_property
from django_countries import countries
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from six import text_type
from social_core.backends.saml import OID_EDU_PERSON_ENTITLEMENT, SAMLAuth, SAMLIdentityProvider
from social_core.exceptions import AuthForbidden

from openedx.core.djangoapps.theming.helpers import get_current_request
from third_party_auth.exceptions import IncorrectConfigurationException

STANDARD_SAML_PROVIDER_KEY = u'standard_saml_provider'
SAP_SUCCESSFACTORS_SAML_KEY = u'sap_success_factors'
log = logging.getLogger(__name__)


class SAMLAuthBackend(SAMLAuth):  # pylint: disable=abstract-method
    """
    Customized version of SAMLAuth that gets the list of IdPs from third_party_auth's list of
    enabled providers.
    """
    name = "tpa-saml"

    def get_idp(self, idp_name):
        """ Given the name of an IdP, get a SAMLIdentityProvider instance """
        from .models import SAMLProviderConfig
        return SAMLProviderConfig.current(idp_name).get_config()

    def setting(self, name, default=None):
        """ Get a setting, from SAMLConfiguration """
        try:
            return self._config.get_setting(name)
        except KeyError:
            return self.strategy.setting(name, default, backend=self)

    def get_idp_setting(self, idp, name, default=None):
        try:
            return idp.saml_sp_configuration.get_setting(name)
        except KeyError:
            return self.setting(name, default)

    def generate_saml_config(self, idp=None):
        """
        Override of SAMLAuth.generate_saml_config to use an idp's configured saml_sp_configuration if given.
        """
        if idp:
            abs_completion_url = self.redirect_uri
            config = {
                'contactPerson': {
                    'technical': self.get_idp_setting(idp, 'TECHNICAL_CONTACT'),
                    'support': self.get_idp_setting(idp, 'SUPPORT_CONTACT')
                },
                'debug': True,
                'idp': idp.saml_config_dict if idp else {},
                'organization': self.get_idp_setting(idp, 'ORG_INFO'),
                'security': {
                    'metadataValidUntil': '',
                    'metadataCacheDuration': 'P10D',  # metadata valid for ten days
                },
                'sp': {
                    'assertionConsumerService': {
                        'url': abs_completion_url,
                        # python-saml only supports HTTP-POST
                        'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
                    },
                    'entityId': self.get_idp_setting(idp, 'SP_ENTITY_ID'),
                    'x509cert': self.get_idp_setting(idp, 'SP_PUBLIC_CERT'),
                    'privateKey': self.get_idp_setting(idp, 'SP_PRIVATE_KEY'),
                },
                'strict': True,  # We must force strict mode - for security
            }
            config["security"].update(self.get_idp_setting(idp, "SECURITY_CONFIG", {}))
            config["sp"].update(self.get_idp_setting(idp, "SP_EXTRA", {}))
            return config
        else:
            return super(SAMLAuthBackend, self).generate_saml_config()

    def get_user_id(self, details, response):
        """
        Calling the parent function and handling the exception properly.
        """
        try:
            return super(SAMLAuthBackend, self).get_user_id(details, response)
        except KeyError as ex:
            log.warning(
                u'[THIRD_PARTY_AUTH] Error in SAML authentication flow. '
                u'Provider: {idp_name}, Message: {message}'.format(
                    message=ex.message,
                    idp_name=response.get('idp_name')
                )
            )
            raise IncorrectConfigurationException(self)

    def generate_metadata_xml(self, idp_name=None):  # pylint: disable=arguments-differ
        """
        Override of SAMLAuth.generate_metadata_xml to accept an optional idp parameter.
        """
        idp = self.get_idp(idp_name) if idp_name else None
        config = self.generate_saml_config(idp)
        saml_settings = OneLogin_Saml2_Settings(
            config,
            sp_validation_only=True
        )
        metadata = saml_settings.get_sp_metadata()
        errors = saml_settings.validate_metadata(metadata)<commit_msg>Fix nested SAML exception<commit_after>"""
Slightly customized python-social-auth backend for SAML 2.0 support
"""


import logging
from copy import deepcopy

import requests
from django.contrib.sites.models import Site
from django.http import Http404
from django.utils.functional import cached_property
from django_countries import countries
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from six import text_type
from social_core.backends.saml import OID_EDU_PERSON_ENTITLEMENT, SAMLAuth, SAMLIdentityProvider
from social_core.exceptions import AuthForbidden

from openedx.core.djangoapps.theming.helpers import get_current_request
from third_party_auth.exceptions import IncorrectConfigurationException

STANDARD_SAML_PROVIDER_KEY = u'standard_saml_provider'
SAP_SUCCESSFACTORS_SAML_KEY = u'sap_success_factors'
log = logging.getLogger(__name__)


class SAMLAuthBackend(SAMLAuth):  # pylint: disable=abstract-method
    """
    Customized version of SAMLAuth that gets the list of IdPs from third_party_auth's list of
    enabled providers.
    """
    name = "tpa-saml"

    def get_idp(self, idp_name):
        """ Given the name of an IdP, get a SAMLIdentityProvider instance """
        from .models import SAMLProviderConfig
        return SAMLProviderConfig.current(idp_name).get_config()

    def setting(self, name, default=None):
        """ Get a setting, from SAMLConfiguration """
        try:
            return self._config.get_setting(name)
        except KeyError:
            return self.strategy.setting(name, default, backend=self)

    def get_idp_setting(self, idp, name, default=None):
        try:
            return idp.saml_sp_configuration.get_setting(name)
        except KeyError:
            return self.setting(name, default)

    def generate_saml_config(self, idp=None):
        """
        Override of SAMLAuth.generate_saml_config to use an idp's configured saml_sp_configuration if given.
        """
        if idp:
            abs_completion_url = self.redirect_uri
            config = {
                'contactPerson': {
                    'technical': self.get_idp_setting(idp, 'TECHNICAL_CONTACT'),
                    'support': self.get_idp_setting(idp, 'SUPPORT_CONTACT')
                },
                'debug': True,
                'idp': idp.saml_config_dict if idp else {},
                'organization': self.get_idp_setting(idp, 'ORG_INFO'),
                'security': {
                    'metadataValidUntil': '',
                    'metadataCacheDuration': 'P10D',  # metadata valid for ten days
                },
                'sp': {
                    'assertionConsumerService': {
                        'url': abs_completion_url,
                        # python-saml only supports HTTP-POST
                        'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
                    },
                    'entityId': self.get_idp_setting(idp, 'SP_ENTITY_ID'),
                    'x509cert': self.get_idp_setting(idp, 'SP_PUBLIC_CERT'),
                    'privateKey': self.get_idp_setting(idp, 'SP_PRIVATE_KEY'),
                },
                'strict': True,  # We must force strict mode - for security
            }
            config["security"].update(self.get_idp_setting(idp, "SECURITY_CONFIG", {}))
            config["sp"].update(self.get_idp_setting(idp, "SP_EXTRA", {}))
            return config
        else:
            return super(SAMLAuthBackend, self).generate_saml_config()

    def get_user_id(self, details, response):
        """
        Calling the parent function and handling the exception properly.
        """
        try:
            return super(SAMLAuthBackend, self).get_user_id(details, response)
        except KeyError as ex:
            log.warning(
                u'[THIRD_PARTY_AUTH] Error in SAML authentication flow. '
                u'Provider: {idp_name}, Message:'.format(
                    idp_name=response.get('idp_name')
                ),
                exc_info=True
            )
            raise IncorrectConfigurationException(self)

    def generate_metadata_xml(self, idp_name=None):  # pylint: disable=arguments-differ
        """
        Override of SAMLAuth.generate_metadata_xml to accept an optional idp parameter.
        """
        idp = self.get_idp(idp_name) if idp_name else None
        config = self.generate_saml_config(idp)
        saml_settings = OneLogin_Saml2_Settings(
            config,
            sp_validation_only=True
        )
        metadata = saml_settings.get_sp_metadata()
        errors = saml_settings.validate_metadata(metadata)
{'fraction_non_alphanumeric': 0.08661417322834646, 'fraction_numerical': 0.00323589688275267, 'mean_word_length': 2.3569876900796523, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4217654', 'n_tokens_mistral': 2750, 'n_tokens_neox': 2627, 'n_words': 575}