コード例 #1
0
    def get_enabled_by_backend_name(cls, backend_name):
        """Generator returning all enabled providers that use the specified
        backend on the current site.

        Example:
            >>> list(get_enabled_by_backend_name("tpa-saml"))
                [<SAMLProviderConfig>, <SAMLProviderConfig>]

        Args:
            backend_name: The name of a python-social-auth backend used by
                one or more providers.

        Yields:
            Instances of ProviderConfig.
        """
        if backend_name in _PSA_OAUTH2_BACKENDS:
            oauth2_backend_names = OAuth2ProviderConfig.key_values(
                'backend_name', flat=True)
            for oauth2_backend_name in oauth2_backend_names:
                provider = OAuth2ProviderConfig.current(oauth2_backend_name)
                if provider.backend_name == backend_name and provider.enabled_for_current_site:
                    yield provider
        elif backend_name in _PSA_SAML_BACKENDS and SAMLConfiguration.is_enabled(
                Site.objects.get_current(get_current_request()), 'default'):
            idp_names = SAMLProviderConfig.key_values('slug', flat=True)
            for idp_name in idp_names:
                provider = SAMLProviderConfig.current(idp_name)
                if provider.backend_name == backend_name and provider.enabled_for_current_site:
                    yield provider
        elif backend_name in _LTI_BACKENDS:
            for consumer_key in LTIProviderConfig.key_values(
                    'lti_consumer_key', flat=True):
                provider = LTIProviderConfig.current(consumer_key)
                if provider.backend_name == backend_name and provider.enabled_for_current_site:
                    yield provider
コード例 #2
0
 def configure_saml_provider(self, **kwargs):
     """ Update the settings for a SAML-based third party auth provider """
     assert SAMLConfiguration.is_enabled(Site.objects.get_current(), 'default'), \
         'SAML Provider Configuration only works if SAML is enabled.'
     obj = SAMLProviderConfig(**kwargs)
     obj.save()
     return obj
コード例 #3
0
 def _enabled_providers(cls):
     """
     Helper method that returns a generator used to iterate over all providers
     of the current site.
     """
     oauth2_backend_names = OAuth2ProviderConfig.key_values('backend_name',
                                                            flat=True)
     for oauth2_backend_name in oauth2_backend_names:
         provider = OAuth2ProviderConfig.current(oauth2_backend_name)
         if provider.enabled_for_current_site and provider.backend_name in _PSA_OAUTH2_BACKENDS:
             yield provider
     if SAMLConfiguration.is_enabled(
             Site.objects.get_current(get_current_request()), 'default'):
         idp_slugs = SAMLProviderConfig.key_values('slug', flat=True)
         for idp_slug in idp_slugs:
             provider = SAMLProviderConfig.current(idp_slug)
             if provider.enabled_for_current_site and provider.backend_name in _PSA_SAML_BACKENDS:
                 yield provider
     for consumer_key in LTIProviderConfig.key_values('lti_consumer_key',
                                                      flat=True):
         provider = LTIProviderConfig.current(consumer_key)
         if provider.enabled_for_current_site and provider.backend_name in _LTI_BACKENDS:
             yield provider
コード例 #4
0
def fetch_saml_metadata():
    """
    Fetch and store/update the metadata of all IdPs

    This task should be run on a daily basis.
    It's OK to run this whether or not SAML is enabled.

    Return value:
        tuple(num_skipped, num_attempted, num_updated, num_failed, failure_messages)
        num_total: Total number of providers found in the database
        num_skipped: Number of providers skipped for various reasons (see L52)
        num_attempted: Number of providers whose metadata was fetched
        num_updated: Number of providers that are either new or whose metadata has changed
        num_failed: Number of providers that could not be updated
        failure_messages: List of error messages for the providers that could not be updated
    """

    # First make a list of all the metadata XML URLs:
    saml_providers = SAMLProviderConfig.key_values('slug', flat=True)
    num_total = len(saml_providers)
    num_skipped = 0
    url_map = {}
    for idp_slug in saml_providers:
        config = SAMLProviderConfig.current(idp_slug)
        saml_config_slug = config.saml_configuration.slug if config.saml_configuration else 'default'

        # Skip SAML provider configurations which do not qualify for fetching
        if any([
                not config.enabled, not config.automatic_refresh_enabled,
                not SAMLConfiguration.is_enabled(config.site, saml_config_slug)
        ]):
            num_skipped += 1
            continue

        url = config.metadata_source
        if url not in url_map:
            url_map[url] = []
        if config.entity_id not in url_map[url]:
            url_map[url].append(config.entity_id)

    # Now attempt to fetch the metadata for the remaining SAML providers:
    num_attempted = len(url_map)
    num_updated = 0
    failure_messages = []  # We return the length of this array for num_failed
    for url, entity_ids in url_map.items():
        try:
            log.info(u"Fetching %s", url)
            if not url.lower().startswith('https'):
                log.warning(
                    u"This SAML metadata URL is not secure! It should use HTTPS. (%s)",
                    url)
            response = requests.get(
                url, verify=True
            )  # May raise HTTPError or SSLError or ConnectionError
            response.raise_for_status()  # May raise an HTTPError

            try:
                parser = etree.XMLParser(remove_comments=True)
                xml = etree.fromstring(response.content, parser)
            except etree.XMLSyntaxError:
                raise
            # TODO: Can use OneLogin_Saml2_Utils to validate signed XML if anyone is using that

            for entity_id in entity_ids:
                log.info(u"Processing IdP with entityID %s", entity_id)
                public_key, sso_url, expires_at = _parse_metadata_xml(
                    xml, entity_id)
                changed = _update_data(entity_id, public_key, sso_url,
                                       expires_at)
                if changed:
                    log.info(u"→ Created new record for SAMLProviderData")
                    num_updated += 1
                else:
                    log.info(
                        u"→ Updated existing SAMLProviderData. Nothing has changed."
                    )
        except (exceptions.SSLError, exceptions.HTTPError,
                exceptions.RequestException, MetadataParseError) as error:
            # Catch and process exception in case of errors during fetching and processing saml metadata.
            # Here is a description of each exception.
            # SSLError is raised in case of errors caused by SSL (e.g. SSL cer verification failure etc.)
            # HTTPError is raised in case of unexpected status code (e.g. 500 error etc.)
            # RequestException is the base exception for any request related error that "requests" lib raises.
            # MetadataParseError is raised if there is error in the fetched meta data (e.g. missing @entityID etc.)

            log.exception(text_type(error))
            failure_messages.append(
                u"{error_type}: {error_message}\nMetadata Source: {url}\nEntity IDs: \n{entity_ids}."
                .format(error_type=type(error).__name__,
                        error_message=text_type(error),
                        url=url,
                        entity_ids="\n".join([
                            u"\t{}: {}".format(count, item)
                            for count, item in enumerate(entity_ids, start=1)
                        ], )))
        except etree.XMLSyntaxError as error:
            log.exception(text_type(error))
            failure_messages.append(
                u"XMLSyntaxError: {error_message}\nMetadata Source: {url}\nEntity IDs: \n{entity_ids}."
                .format(error_message=str(error.error_log),
                        url=url,
                        entity_ids="\n".join([
                            u"\t{}: {}".format(count, item)
                            for count, item in enumerate(entity_ids, start=1)
                        ], )))

    # Return counts for total, skipped, attempted, updated, and failed, along with any failure messages
    return num_total, num_skipped, num_attempted, num_updated, len(
        failure_messages), failure_messages