コード例 #1
0
    def ext_service(self, entity_id, typ, service, binding=None):
        known_entity = False
        for key, _md in self.metadata.items():
            srvs = _md.ext_service(entity_id, typ, service, binding)
            if srvs:
                return srvs
            elif srvs is None:
                pass
            else:
                known_entity = True

        if known_entity:
            raise UnsupportedBinding(binding)
        else:
            raise UnknownSystemEntity(entity_id)
コード例 #2
0
ファイル: mdstore.py プロジェクト: sigmunau/pysaml2
    def _ext_service(self, entity_id, typ, service, binding=None):
        known_principal = False
        for key, md in self.metadata.items():
            srvs = md._ext_service(entity_id, typ, service, binding)
            if srvs:
                return srvs
            elif srvs is None:
                pass
            else:
                known_principal = True

        if known_principal:
            raise UnsupportedBinding(binding)
        else:
            raise UnknownPrincipal(entity_id)
コード例 #3
0
    def service(self, entity_id, typ, service, binding=None):
        known_principal = False
        for key, _md in self.metadata.items():
            srvs = _md.service(entity_id, typ, service, binding)
            if srvs:
                return srvs
            elif srvs is None:
                pass
            else:
                known_principal = True

        if known_principal:
            logger.error("Unsupported binding: %s (%s)" % (binding, entity_id))
            raise UnsupportedBinding(binding)
        else:
            logger.error("Unknown principal: %s" % entity_id)
            raise UnknownPrincipal(entity_id)
コード例 #4
0
    def service(self, entity_id, typ, service, binding=None):
        known_entity = False
        logger.debug("service(%s, %s, %s, %s)", entity_id, typ, service,
                     binding)
        for key, _md in self.metadata.items():
            srvs = _md.service(entity_id, typ, service, binding)
            if srvs:
                return srvs
            elif srvs is None:
                pass
            else:
                known_entity = True

        if known_entity:
            logger.error("Unsupported binding: %s (%s)", binding, entity_id)
            raise UnsupportedBinding(binding)
        else:
            logger.error("Unknown system entity: %s", entity_id)
            raise UnknownSystemEntity(entity_id)
コード例 #5
0
ファイル: views.py プロジェクト: italia/spid-django
def spid_login(
    request,
    config_loader_path=None,
    wayf_template="wayf.html",
    authorization_error_template="djangosaml2/auth_error.html",
):
    """SAML Authorization Request initiator

    This view initiates the SAML2 Authorization handshake
    using the pysaml2 library to create the AuthnRequest.
    It uses the SAML 2.0 Http POST protocol binding.
    """
    logger.debug("SPID Login process started")

    next_url = request.GET.get("next", settings.LOGIN_REDIRECT_URL)
    if not next_url:
        logger.warning("The next parameter exists but is empty")
        next_url = settings.LOGIN_REDIRECT_URL

    # Ensure the user-originating redirection url is safe.
    if not validate_referral_url(request, next_url):
        next_url = settings.LOGIN_REDIRECT_URL

    if request.user.is_authenticated:
        redirect_authenticated_user = getattr(
            settings, "SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN", True)
        if redirect_authenticated_user:
            return HttpResponseRedirect(next_url)
        else:  # pragma: no cover
            logger.debug("User is already logged in")
            return render(request, authorization_error_template,
                          {"came_from": next_url})

    # this works only if request came from wayf
    selected_idp = request.GET.get("idp", None)

    conf = get_config(config_loader_path, request)

    # is a embedded wayf needed?
    idps = available_idps(conf)
    if selected_idp is None and len(idps) > 1:
        logger.debug("A discovery process is needed")
        return render(
            request,
            wayf_template,
            {
                "available_idps": idps.items(),
                "next_url": next_url
            },
        )
    else:
        # otherwise is the first one
        _msg = "Unable to know which IdP to use"
        try:
            selected_idp = selected_idp or list(idps.keys())[0]
        except TypeError as e:  # pragma: no cover
            logger.error(f"{_msg}: {e}")
            return HttpResponseNotFound(_msg)
        except IndexError as e:  # pragma: no cover
            logger.error(f"{_msg}: {e}")
            return HttpResponseNotFound(_msg)

    # ensure our selected binding is supported by the IDP
    logger.debug(
        f"Trying binding {SAML2_DEFAULT_BINDING} for IDP {selected_idp}")
    supported_bindings = get_idp_sso_supported_bindings(selected_idp,
                                                        config=conf)
    if not supported_bindings:
        _msg = "IdP Metadata not found or not valid"
        return HttpResponseNotFound(_msg)

    if SAML2_DEFAULT_BINDING not in supported_bindings:
        _msg = (f"Requested: {SAML2_DEFAULT_BINDING} but the selected "
                f"IDP [{selected_idp}] doesn't support "
                f"{BINDING_HTTP_POST} or {BINDING_HTTP_REDIRECT}. "
                f"Check if IdP Metadata is correctly loaded and updated.")
        logger.error(_msg)
        raise UnsupportedBinding(_msg)

    # SPID things here
    try:
        login_response = spid_sp_authn_request(conf, selected_idp, next_url)
    except UnknownSystemEntity as e:  # pragma: no cover
        _msg = f"Unknown IDP Entity ID: {selected_idp}"
        logger.error(f"{_msg}: {e}")
        return HttpResponseNotFound(_msg)

    session_id = login_response["session_id"]
    http_response = login_response["http_response"]

    # success, so save the session ID and return our response
    logger.debug(
        f"Saving session-id {session_id} in the OutstandingQueries cache")
    oq_cache = OutstandingQueriesCache(request.saml_session)
    oq_cache.set(session_id, next_url)

    if SAML2_DEFAULT_BINDING == saml2.BINDING_HTTP_POST:
        return HttpResponse(http_response["data"])
    elif SAML2_DEFAULT_BINDING == saml2.BINDING_HTTP_REDIRECT:
        headers = dict(login_response["http_response"]["headers"])
        return HttpResponseRedirect(headers["Location"])
コード例 #6
0
    def get(self, request, *args, **kwargs):
        logger.debug('Login process started')
        next_path = self.get_next_path(request)

        # if the user is already authenticated that maybe because of two reasons:
        # A) He has this URL in two browser windows and in the other one he
        #    has already initiated the authenticated session.
        # B) He comes from a view that (incorrectly) send him here because
        #    he does not have enough permissions. That view should have shown
        #    an authorization error in the first place.
        # We can only make one thing here and that is configurable with the
        # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting
        # is True (default value) we will redirect him to the next_path path.
        # Otherwise, we will show an (configurable) authorization error.
        if request.user.is_authenticated:
            if get_custom_setting('SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN',
                                  True):
                return HttpResponseRedirect(next_path)
            logger.debug('User is already logged in')
            return render(request, self.authorization_error_template, {
                'came_from': next_path,
            })

        try:
            conf = self.get_sp_config(request)
        except SourceNotFound as excp:
            msg = ('Error, IdP EntityID was not found in metadata: {}')
            logger.exception(msg.format(excp))
            return HttpResponse(
                msg.format('Please contact technical support.'), status=500)

        # is a embedded wayf or DiscoveryService needed?
        configured_idps = available_idps(conf)
        selected_idp = request.GET.get('idp', None)

        # Do we have a Discovery Service?
        if not selected_idp:
            discovery_service = getattr(settings, 'SAML2_DISCO_URL', None)
            if discovery_service:
                # We have to build the URL to redirect to with all the information
                # for the Discovery Service to know how to send the flow back to us
                logger.debug(
                    ("A discovery process is needed trough a"
                     "Discovery Service: {}").format(discovery_service))
                login_url = request.build_absolute_uri(reverse('saml2_login'))
                login_url = '{0}?next={1}'.format(login_url,
                                                  urlquote(next_path, safe=''))
                ds_url = '{0}?entityID={1}&return={2}&returnIDParam=idp'
                ds_url = ds_url.format(
                    discovery_service,
                    urlquote(getattr(conf, 'entityid'), safe=''),
                    urlquote(login_url, safe=''))
                return HttpResponseRedirect(ds_url)

            elif len(configured_idps) > 1:
                logger.debug('A discovery process trough WAYF page is needed')
                return render(
                    request, self.wayf_template, {
                        'available_idps': configured_idps.items(),
                        'came_from': next_path,
                    })

        # is the first one, otherwise next logger message will print None
        if not configured_idps:
            raise IdPConfigurationMissing(
                ('IdP configuration is missing or its metadata is expired.'))
        if selected_idp is None:
            selected_idp = list(configured_idps.keys())[0]

        # choose a binding to try first
        binding = getattr(settings, 'SAML_DEFAULT_BINDING',
                          saml2.BINDING_HTTP_POST)
        logger.debug(f'Trying binding {binding} for IDP {selected_idp}')

        # ensure our selected binding is supported by the IDP
        supported_bindings = get_idp_sso_supported_bindings(selected_idp,
                                                            config=conf)

        if binding not in supported_bindings:
            logger.debug(
                f'Binding {binding} not in IDP {selected_idp} '
                f'supported bindings: {supported_bindings}. Trying to switch ...',
            )
            if binding == saml2.BINDING_HTTP_POST:
                logger.warning(
                    f'IDP {selected_idp} does not support {binding} '
                    f'trying {saml2.BINDING_HTTP_REDIRECT}', )
                binding = saml2.BINDING_HTTP_REDIRECT
            else:
                logger.warning(
                    f'IDP {selected_idp} does not support {binding} '
                    f'trying {saml2.BINDING_HTTP_POST}', )
                binding = saml2.BINDING_HTTP_POST
            # if switched binding still not supported, give up
            if binding not in supported_bindings:
                raise UnsupportedBinding(
                    f'IDP {selected_idp} does not support '
                    f'{saml2.BINDING_HTTP_POST} and {saml2.BINDING_HTTP_REDIRECT}'
                )

        client = Saml2Client(conf)
        http_response = None

        # SSO options
        sign_requests = getattr(conf, '_sp_authn_requests_signed', False)
        sso_kwargs = {}
        if sign_requests:
            sso_kwargs["sigalg"] = settings.SAML_CONFIG['service']['sp']\
                                           .get('signing_algorithm',
                                                saml2.xmldsig.SIG_RSA_SHA256)
            sso_kwargs["digest_alg"] = settings.SAML_CONFIG['service']['sp']\
                                           .get('digest_algorithm',
                                                saml2.xmldsig.DIGEST_SHA256)

        # pysaml needs a string otherwise: "cannot serialize True (type bool)"
        if getattr(conf, '_sp_force_authn', False):
            sso_kwargs['force_authn'] = "true"
        if getattr(conf, '_sp_allow_create', False):
            sso_kwargs['allow_create'] = "true"

        # custom nsprefixes
        sso_kwargs['nsprefix'] = get_namespace_prefixes()

        logger.debug(f'Redirecting user to the IdP via {binding} binding.')
        _msg = 'Unable to know which IdP to use'
        if binding == saml2.BINDING_HTTP_REDIRECT:
            try:
                session_id, result = client.prepare_for_authenticate(
                    entityid=selected_idp,
                    relay_state=next_path,
                    binding=binding,
                    sign=sign_requests,
                    **sso_kwargs)
            except TypeError as e:
                logger.error(f'{_msg}: {e}')
                return HttpResponse(_msg)
            else:
                http_response = HttpResponseRedirect(get_location(result))
        elif binding == saml2.BINDING_HTTP_POST:
            if self.post_binding_form_template:
                # get request XML to build our own html based on the template
                try:
                    location = client.sso_location(selected_idp, binding)
                except TypeError as e:
                    logger.error(f'{_msg}: {e}')
                    return HttpResponse(_msg)
                session_id, request_xml = client.create_authn_request(
                    location, binding=binding, **sso_kwargs)
                try:
                    if isinstance(request_xml, AuthnRequest):
                        # request_xml will be an instance of AuthnRequest if the message is not signed
                        request_xml = str(request_xml)
                    saml_request = base64.b64encode(bytes(
                        request_xml, 'UTF-8')).decode('utf-8')

                    http_response = render(
                        request, self.post_binding_form_template, {
                            'target_url': location,
                            'params': {
                                'SAMLRequest': saml_request,
                                'RelayState': next_path,
                            },
                        })
                except TemplateDoesNotExist as e:
                    logger.error(f'TemplateDoesNotExist: {e}')

            if not http_response:
                # use the html provided by pysaml2 if no template was specified or it doesn't exist
                try:
                    session_id, result = client.prepare_for_authenticate(
                        entityid=selected_idp,
                        relay_state=next_path,
                        binding=binding)
                except TypeError as e:
                    _msg = f"Can't prepare the authentication for {selected_idp}"
                    logger.error(f'{_msg}: {e}')
                    return HttpResponse(_msg)
                else:
                    http_response = HttpResponse(result['data'])
        else:
            raise UnsupportedBinding(f'Unsupported binding: {binding}')

        # success, so save the session ID and return our response
        oq_cache = OutstandingQueriesCache(request.saml_session)
        oq_cache.set(session_id, next_path)
        logger.debug(
            f'Saving the session_id "{oq_cache.__dict__}" '
            'in the OutstandingQueries cache', )

        # idp hinting support, add idphint url parameter if present in this request
        response = add_idp_hinting(request, http_response) or http_response
        return response
コード例 #7
0
ファイル: views.py プロジェクト: AmbientLighter/djangosaml2
def login(request,
          config_loader_path=None,
          wayf_template='djangosaml2/wayf.html',
          authorization_error_template='djangosaml2/auth_error.html',
          post_binding_form_template='djangosaml2/post_binding_form.html'):
    """SAML Authorization Request initiator

    This view initiates the SAML2 Authorization handshake
    using the pysaml2 library to create the AuthnRequest.
    It uses the SAML 2.0 Http Redirect protocol binding.

    * post_binding_form_template - path to a template containing HTML form with
    hidden input elements, used to send the SAML message data when HTTP POST
    binding is being used. You can customize this template to include custom
    branding and/or text explaining the automatic redirection process. Please
    see the example template in
    templates/djangosaml2/example_post_binding_form.html
    If set to None or nonexistent template, default form from the saml2 library
    will be rendered.
    """
    logger.debug('Login process started')

    came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL)
    if not came_from:
        logger.warning('The next parameter exists but is empty')
        came_from = settings.LOGIN_REDIRECT_URL

    # Ensure the user-originating redirection url is safe.
    if not is_safe_url(url=came_from, host=request.get_host()):
        came_from = settings.LOGIN_REDIRECT_URL

    # if the user is already authenticated that maybe because of two reasons:
    # A) He has this URL in two browser windows and in the other one he
    #    has already initiated the authenticated session.
    # B) He comes from a view that (incorrectly) send him here because
    #    he does not have enough permissions. That view should have shown
    #    an authorization error in the first place.
    # We can only make one thing here and that is configurable with the
    # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting
    # is True (default value) we will redirect him to the came_from view.
    # Otherwise, we will show an (configurable) authorization error.
    if not request.user.is_anonymous():
        try:
            redirect_authenticated_user = settings.SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN
        except AttributeError:
            redirect_authenticated_user = True

        if redirect_authenticated_user:
            return HttpResponseRedirect(came_from)
        else:
            logger.debug('User is already logged in')
            return render(request, authorization_error_template, {
                'came_from': came_from,
            })

    selected_idp = request.GET.get('idp', None)
    conf = get_config(config_loader_path, request)

    # is a embedded wayf needed?
    idps = available_idps(conf)
    if selected_idp is None and len(idps) > 1:
        logger.debug('A discovery process is needed')
        return render(request, wayf_template, {
            'available_idps': idps.items(),
            'came_from': came_from,
        })

    # choose a binding to try first
    sign_requests = getattr(conf, '_sp_authn_requests_signed', False)
    binding = BINDING_HTTP_POST if sign_requests else BINDING_HTTP_REDIRECT
    logger.debug('Trying binding %s for IDP %s', binding, selected_idp)

    # ensure our selected binding is supported by the IDP
    supported_bindings = get_idp_sso_supported_bindings(selected_idp,
                                                        config=conf)
    if binding not in supported_bindings:
        logger.debug('Binding %s not in IDP %s supported bindings: %s',
                     binding, selected_idp, supported_bindings)
        if binding == BINDING_HTTP_POST:
            logger.warning('IDP %s does not support %s,  trying %s',
                           selected_idp, binding, BINDING_HTTP_REDIRECT)
            binding = BINDING_HTTP_REDIRECT
        else:
            logger.warning('IDP %s does not support %s,  trying %s',
                           selected_idp, binding, BINDING_HTTP_POST)
            binding = BINDING_HTTP_POST
        # if switched binding still not supported, give up
        if binding not in supported_bindings:
            raise UnsupportedBinding('IDP %s does not support %s or %s',
                                     selected_idp, BINDING_HTTP_POST,
                                     BINDING_HTTP_REDIRECT)

    client = Saml2Client(conf)
    http_response = None

    logger.debug('Redirecting user to the IdP via %s binding.', binding)
    if binding == BINDING_HTTP_REDIRECT:
        try:
            # do not sign the xml itself, instead us the sigalg to
            # generate the signature as a URL param
            sigalg = SIG_RSA_SHA1 if sign_requests else None
            session_id, result = client.prepare_for_authenticate(
                entityid=selected_idp,
                relay_state=came_from,
                binding=binding,
                sign=False,
                sigalg=sigalg)
        except TypeError as e:
            logger.error('Unable to know which IdP to use')
            return HttpResponse(text_type(e))
        else:
            http_response = HttpResponseRedirect(get_location(result))
    elif binding == BINDING_HTTP_POST:
        # use the html provided by pysaml2 if no template specified
        if not post_binding_form_template:
            try:
                session_id, result = client.prepare_for_authenticate(
                    entityid=selected_idp,
                    relay_state=came_from,
                    binding=binding)
            except TypeError as e:
                logger.error('Unable to know which IdP to use')
                return HttpResponse(text_type(e))
            else:
                http_response = HttpResponse(result['data'])
        # get request XML to build our own html based on the template
        else:
            try:
                location = client.sso_location(selected_idp, binding)
            except TypeError as e:
                logger.error('Unable to know which IdP to use')
                return HttpResponse(text_type(e))
            session_id, request_xml = client.create_authn_request(
                location, binding=binding)
            http_response = render(
                request, post_binding_form_template, {
                    'target_url': location,
                    'params': {
                        'SAMLRequest': base64.b64encode(
                            binary_type(request_xml)),
                        'RelayState': came_from,
                    },
                })
    else:
        raise UnsupportedBinding('Unsupported binding: %s', binding)

    # success, so save the session ID and return our response
    logger.debug('Saving the session_id in the OutstandingQueries cache')
    oq_cache = OutstandingQueriesCache(request.session)
    oq_cache.set(session_id, came_from)
    return http_response
コード例 #8
0
ファイル: views.py プロジェクト: daggaz/djangosaml2
def login(request,
          config_loader_path=None,
          wayf_template='djangosaml2/wayf.html',
          authorization_error_template='djangosaml2/auth_error.html',
          post_binding_form_template='djangosaml2/post_binding_form.html'):
    """SAML Authorization Request initiator

    This view initiates the SAML2 Authorization handshake
    using the pysaml2 library to create the AuthnRequest.
    It uses the SAML 2.0 Http Redirect protocol binding.

    * post_binding_form_template - path to a template containing HTML form with
    hidden input elements, used to send the SAML message data when HTTP POST
    binding is being used. You can customize this template to include custom
    branding and/or text explaining the automatic redirection process. Please
    see the example template in
    templates/djangosaml2/example_post_binding_form.html
    If set to None or nonexistent template, default form from the saml2 library
    will be rendered.
    """
    logger.debug('Login process started')

    came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL)
    if not came_from:
        logger.warning('The next parameter exists but is empty')
        came_from = settings.LOGIN_REDIRECT_URL
    came_from = validate_referral_url(request, came_from)

    # if the user is already authenticated that maybe because of two reasons:
    # A) He has this URL in two browser windows and in the other one he
    #    has already initiated the authenticated session.
    # B) He comes from a view that (incorrectly) send him here because
    #    he does not have enough permissions. That view should have shown
    #    an authorization error in the first place.
    # We can only make one thing here and that is configurable with the
    # SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN setting. If that setting
    # is True (default value) we will redirect him to the came_from view.
    # Otherwise, we will show an (configurable) authorization error.
    if request.user.is_authenticated:
        redirect_authenticated_user = getattr(
            settings, 'SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN', True)
        if redirect_authenticated_user:
            return HttpResponseRedirect(came_from)
        else:
            logger.debug('User is already logged in')
            return render(request, authorization_error_template, {
                'came_from': came_from,
            })

    selected_idp = request.GET.get('idp', None)
    try:
        conf = get_config(config_loader_path, request)
    except SourceNotFound as excp:
        msg = ('Error, IdP EntityID was not found ' 'in metadata: {}')
        logger.exception(msg.format(excp))
        return HttpResponse(msg.format(('Please contact '
                                        'technical support.')),
                            status=500)

    kwargs = {}
    # pysaml needs a string otherwise: "cannot serialize True (type bool)"
    if getattr(conf, '_sp_force_authn', False):
        kwargs['force_authn'] = "true"
    if getattr(conf, '_sp_allow_create', False):
        kwargs['allow_create'] = "true"

    # is a embedded wayf needed?
    idps = available_idps(conf)
    if selected_idp is None and len(idps) > 1:
        logger.debug('A discovery process is needed')
        return render(request, wayf_template, {
            'available_idps': idps.items(),
            'came_from': came_from,
        })
    else:
        # is the first one, otherwise next logger message will print None
        if not idps:
            raise IdPConfigurationMissing(('IdP configuration is missing or '
                                           'its metadata is expired.'))
        if selected_idp is None:
            selected_idp = list(idps.keys())[0]

    # choose a binding to try first
    sign_requests = getattr(conf, '_sp_authn_requests_signed', False)
    binding = BINDING_HTTP_POST if sign_requests else BINDING_HTTP_REDIRECT
    logger.debug('Trying binding %s for IDP %s', binding, selected_idp)

    # ensure our selected binding is supported by the IDP
    supported_bindings = get_idp_sso_supported_bindings(selected_idp,
                                                        config=conf)
    if binding not in supported_bindings:
        logger.debug('Binding %s not in IDP %s supported bindings: %s',
                     binding, selected_idp, supported_bindings)
        if binding == BINDING_HTTP_POST:
            logger.warning('IDP %s does not support %s,  trying %s',
                           selected_idp, binding, BINDING_HTTP_REDIRECT)
            binding = BINDING_HTTP_REDIRECT
        else:
            logger.warning('IDP %s does not support %s,  trying %s',
                           selected_idp, binding, BINDING_HTTP_POST)
            binding = BINDING_HTTP_POST
        # if switched binding still not supported, give up
        if binding not in supported_bindings:
            raise UnsupportedBinding('IDP %s does not support %s or %s',
                                     selected_idp, BINDING_HTTP_POST,
                                     BINDING_HTTP_REDIRECT)

    client = Saml2Client(conf)
    http_response = None

    logger.debug('Redirecting user to the IdP via %s binding.', binding)
    if binding == BINDING_HTTP_REDIRECT:
        try:
            nsprefix = get_namespace_prefixes()
            if sign_requests:
                # do not sign the xml itself, instead use the sigalg to
                # generate the signature as a URL param
                sig_alg_option_map = {
                    'sha1': SIG_RSA_SHA1,
                    'sha256': SIG_RSA_SHA256
                }
                sig_alg_option = getattr(conf, '_sp_authn_requests_signed_alg',
                                         'sha1')
                kwargs["sigalg"] = sig_alg_option_map[sig_alg_option]
            session_id, result = client.prepare_for_authenticate(
                entityid=selected_idp,
                relay_state=came_from,
                binding=binding,
                sign=False,
                nsprefix=nsprefix,
                **kwargs)
        except TypeError as e:
            logger.error('Unable to know which IdP to use')
            return HttpResponse(str(e))
        else:
            http_response = HttpResponseRedirect(get_location(result))
    elif binding == BINDING_HTTP_POST:
        if post_binding_form_template:
            # get request XML to build our own html based on the template
            try:
                location = client.sso_location(selected_idp, binding)
            except TypeError as e:
                logger.error('Unable to know which IdP to use')
                return HttpResponse(str(e))
            session_id, request_xml = client.create_authn_request(
                location, binding=binding, **kwargs)
            try:
                if isinstance(request_xml, AuthnRequest):
                    # request_xml will be an instance of AuthnRequest if the message is not signed
                    request_xml = str(request_xml)
                saml_request = base64.b64encode(bytes(request_xml,
                                                      'UTF-8')).decode('utf-8')

                http_response = render(
                    request, post_binding_form_template, {
                        'target_url': location,
                        'params': {
                            'SAMLRequest': saml_request,
                            'RelayState': came_from,
                        },
                    })
            except TemplateDoesNotExist:
                pass

        if not http_response:
            # use the html provided by pysaml2 if no template was specified or it didn't exist
            try:
                session_id, result = client.prepare_for_authenticate(
                    entityid=selected_idp,
                    relay_state=came_from,
                    binding=binding)
            except TypeError as e:
                logger.error('Unable to know which IdP to use')
                return HttpResponse(str(e))
            else:
                http_response = HttpResponse(result['data'])
    else:
        raise UnsupportedBinding('Unsupported binding: %s', binding)

    # success, so save the session ID and return our response
    logger.debug('Saving the session_id in the OutstandingQueries cache')
    oq_cache = OutstandingQueriesCache(request.session)
    oq_cache.set(session_id, came_from)
    return http_response
コード例 #9
0
    def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT):
        """Parse a Authentication Request
        
        :param enc_request: The request in its transport format
        :param binding: Which binding that was used to transport the message
            to this entity.
        :return: A dictionary with keys:
            consumer_url - as gotten from the SPs entity_id and the metadata
            id - the id of the request
            sp_entity_id - the entity id of the SP
            request - The verified request
        """
        
        response = {}
        _log_info = logger.info
        _log_debug = logger.debug

        # The addresses I should receive messages like this on
        receiver_addresses = self.conf.endpoint("single_sign_on_service",
                                                 binding)
        _log_info("receiver addresses: %s" % receiver_addresses)
        _log_info("Binding: %s" % binding)


        try:
            timeslack = self.conf.accepted_time_diff
            if not timeslack:
                timeslack = 0
        except AttributeError:
            timeslack = 0

        authn_request = AuthnRequest(self.sec,
                                     self.conf.attribute_converters,
                                     receiver_addresses, timeslack=timeslack)

        if binding == BINDING_SOAP or binding == BINDING_PAOS:
            # not base64 decoding and unzipping
            authn_request.debug=True
            _log_info("Don't decode")
            authn_request = authn_request.loads(enc_request, decode=False)
        else:
            authn_request = authn_request.loads(enc_request)

        _log_debug("Loaded authn_request")

        if authn_request:
            authn_request = authn_request.verify()

        _log_debug("Verified authn_request")

        if not authn_request:
            return None
            
        response["id"] = authn_request.message.id # put in in_reply_to

        sp_entity_id = authn_request.message.issuer.text
        # try to find return address in metadata
        # What's the binding ? ProtocolBinding
        if authn_request.message.protocol_binding == BINDING_HTTP_REDIRECT:
            _binding = BINDING_HTTP_POST
        else:
            _binding = authn_request.message.protocol_binding

        try:
            srvs = self.metadata.assertion_consumer_service(sp_entity_id,
                                                           binding=_binding)
            consumer_url = destinations(srvs)[0]
        except (KeyError, IndexError):
            _log_info("Failed to find consumer URL for %s" % sp_entity_id)
            _log_info("Binding: %s" % _binding)
            _log_info("entities: %s" % self.metadata.keys())
            raise UnknownPrincipal(sp_entity_id)

        if not consumer_url: # what to do ?
            _log_info("Couldn't find a consumer URL binding=%s entity_id=%s" % (
                                        _binding,sp_entity_id))
            raise UnsupportedBinding(sp_entity_id)

        response["sp_entity_id"] = sp_entity_id

        if authn_request.message.assertion_consumer_service_url:
            return_destination = \
                        authn_request.message.assertion_consumer_service_url
        
            if consumer_url != return_destination:
                # serious error on someones behalf
                _log_info("%s != %s" % (consumer_url, return_destination))
                raise OtherError("ConsumerURL and return destination mismatch")
        
        response["consumer_url"] = consumer_url
        response["request"] = authn_request.message

        return response
コード例 #10
0
def spid_login(request,
          config_loader_path=None,
          wayf_template='wayf.html',
          authorization_error_template='djangosaml2/auth_error.html'):
    """SAML Authorization Request initiator

    This view initiates the SAML2 Authorization handshake
    using the pysaml2 library to create the AuthnRequest.
    It uses the SAML 2.0 Http POST protocol binding.
    """
    
    logger.debug('SPID Login process started')
    next_url = request.GET.get('next', settings.LOGIN_REDIRECT_URL)
    if not next_url:
        logger.warning('The next parameter exists but is empty')
        next_url = settings.LOGIN_REDIRECT_URL

    # Ensure the user-originating redirection url is safe.
    if not validate_referral_url(request, next_url):
        next_url = settings.LOGIN_REDIRECT_URL
    
    if request.user.is_authenticated:
        redirect_authenticated_user = getattr(settings,
                                              'SAML_IGNORE_AUTHENTICATED_USERS_ON_LOGIN',
                                              True)
        if redirect_authenticated_user:
            return HttpResponseRedirect(next_url)
        else: # pragma: no cover
            logger.debug('User is already logged in')
            return render(request, authorization_error_template, {
                    'came_from': next_url})
    
    # this works only if request came from wayf
    selected_idp = request.GET.get('idp', None)

    conf = get_config(config_loader_path, request)

    # is a embedded wayf needed?
    idps = available_idps(conf)
    if selected_idp is None and len(idps) > 1:
        logger.debug('A discovery process is needed')
        return render(request, wayf_template, 
            {
                'available_idps': idps.items(),
                'next_url': next_url
            }
        )
    else:
        # otherwise is the first one
        _msg = 'Unable to know which IdP to use'
        try:
            selected_idp = selected_idp or list(idps.keys())[0]
        except TypeError as e: # pragma: no cover
            logger.error(f'{_msg}: {e}')
            return HttpResponseError(_msg)
        except IndexError as e: # pragma: no cover
            logger.error(f'{_msg}: {e}')
            return HttpResponseNotFound(_msg)
    
    binding = settings.SPID_DEFAULT_BINDING
    logger.debug(f'Trying binding {binding} for IDP {selected_idp}')
    # ensure our selected binding is supported by the IDP
    supported_bindings = get_idp_sso_supported_bindings(selected_idp, config=conf)
    if binding not in supported_bindings:
        _msg = (
                f"Requested: {binding} but the selected "
                f"IDP [{selected_idp}] doesn't support "
                f"{BINDING_HTTP_POST} or {BINDING_HTTP_REDIRECT}. "
                f"Check if IdP Metadata is correctly loaded and updated."
        )
        logger.error(_msg)
        raise UnsupportedBinding(_msg)

    # SPID things here
    try:
        login_response = spid_sp_authn_request(conf, 
                                               selected_idp, 
                                               binding, 
                                               settings.SPID_NAMEID_FORMAT,
                                               settings.SPID_AUTH_CONTEXT,
                                               settings.SPID_SIG_ALG,
                                               settings.SPID_DIG_ALG,
                                               next_url
        )
    except UnknownSystemEntity as e: # pragma: no cover
        _msg = f'Unknown IDP Entity ID: {selected_idp}'
        logger.error(f'{_msg}: {e}')
        return HttpResponseNotFound(_msg)
    
    session_id = login_response['session_id']
    http_response = login_response['http_response']
    
    # success, so save the session ID and return our response
    logger.debug(f'Saving session-id {session_id} in the OutstandingQueries cache')
    oq_cache = OutstandingQueriesCache(request.saml_session)
    oq_cache.set(session_id, next_url)
    
    if binding == saml2.BINDING_HTTP_POST:
        return HttpResponse(http_response['data'])
    elif binding == saml2.BINDING_HTTP_REDIRECT:
        headers = dict(login_response['http_response']['headers'])
        return HttpResponseRedirect(headers['Location'])