Пример #1
0
def logout(request, config_loader_path=None):
    """SAML Logout Request initiator

    This view initiates the SAML2 Logout request
    using the pysaml2 library to create the LogoutRequest.
    """
    state = StateCache(request.session)
    conf = get_config(config_loader_path, request)

    client = Saml2Client(conf,
                         state_cache=state,
                         identity_cache=IdentityCache(request.session))
    subject_id = _get_subject_id(request.session)
    if subject_id is None:
        logger.warning(
            'The session does not contain the subject id for user %s',
            request.user)

    sign_requests = getattr(conf, '_sp_logout_requests_signed', False)
    sign_alg = getattr(conf, 'signing_algorithm', SIG_RSA_SHA1)
    digest_alg = getattr(conf, 'digest_algorithm', DIGEST_SHA1)
    result = client.global_logout(subject_id,
                                  sign=sign_requests,
                                  sign_alg=sign_alg,
                                  digest_alg=digest_alg)

    state.sync()

    if not result:
        logger.error("Looks like the user %s is not logged in any IdP/AA",
                     subject_id)
        return HttpResponseBadRequest("You are not logged in any IdP/AA")

    if len(result) > 1:
        logger.error(
            'Sorry, I do not know how to logout from several sources. I will logout just from the first one'
        )

    for entityid, logout_info in result.items():
        if isinstance(logout_info, tuple):
            binding, http_info = logout_info
            if binding == BINDING_HTTP_POST:
                logger.debug(
                    'Returning form to the IdP to continue the logout process')
                body = ''.join(http_info['data'])
                return HttpResponse(body)
            elif binding == BINDING_HTTP_REDIRECT:
                logger.debug(
                    'Redirecting to the IdP to continue the logout process')
                return HttpResponseRedirect(get_location(http_info))
            else:
                logger.error('Unknown binding: %s', binding)
                return HttpResponseServerError('Failed to log out')
        else:
            # We must have had a soap logout
            return finish_logout(request, logout_info)

    logger.error(
        'Could not logout because there only the HTTP_REDIRECT is supported')
    return HttpResponseServerError('Logout Binding not supported')
Пример #2
0
    def logout(self, request, data, binding):
        conf = get_config(request=request)

        state = StateCache(request.session)
        client = Saml2Client(conf, state_cache=state,
                             identity_cache=IdentityCache(request.session))

        if 'SAMLResponse' in data:
            # Logout started by us
            client.parse_logout_request_response(data['SAMLResponse'], binding)
            http_response = logout_completed()
        else:
            # Logout started by IdP
            subject_id = _get_subject_id(request.session)
            if subject_id is None:
                http_response = logout_completed()
            else:
                http_info = client.handle_logout_request(data['SAMLRequest'], subject_id, binding,
                                                         relay_state=data.get('RelayState', ''))
                http_response = HttpResponseRedirect(get_location(http_info))

        state.sync()
        user = request.user
        if user.is_anonymous:
            return http_response
        Token.objects.get(user=user).delete()
        auth.logout(request)
        event_logger.saml2_auth.info(
            'User {user_username} with full name {user_full_name} logged out successfully with SAML2.',
            event_type='auth_logged_out_with_saml2', event_context={'user': user}
        )
        return http_response
Пример #3
0
def do_logout_service(request,
                      data,
                      binding,
                      config_loader_path=None,
                      next_page=None,
                      logout_error_template='djangosaml2/logout_error.html'):
    """SAML Logout Response endpoint

    The IdP will send the logout response to this view,
    which will process it with pysaml2 help and log the user
    out.
    Note that the IdP can request a logout even when
    we didn't initiate the process as a single logout
    request started by another SP.
    """
    logger.debug('Logout service started')
    conf = get_config(config_loader_path, request)

    state = StateCache(request.session)
    client = Saml2Client(conf,
                         state_cache=state,
                         identity_cache=IdentityCache(request.session))

    if 'SAMLResponse' in data:  # we started the logout
        logger.debug('Receiving a logout response from the IdP')
        response = client.parse_logout_request_response(
            data['SAMLResponse'], binding)
        state.sync()
        return finish_logout(request, response, next_page=next_page)

    elif 'SAMLRequest' in data:  # logout started by the IdP
        logger.debug('Receiving a logout request from the IdP')
        subject_id = _get_subject_id(request.session)
        if subject_id is None:
            logger.warning(
                'The session does not contain the subject id for user %s. Performing local logout',
                request.user)
            auth.logout(request)
            return render(request, logout_error_template, status=403)
        else:
            http_info = client.handle_logout_request(data['SAMLRequest'],
                                                     subject_id,
                                                     binding,
                                                     relay_state=data.get(
                                                         'RelayState', ''))
            state.sync()
            auth.logout(request)
            if (http_info.get('method', 'GET') == 'POST'
                    and 'data' in http_info and
                ('Content-type', 'text/html') in http_info.get('headers', [])):
                # need to send back to the IDP a signed POST response with user session
                # return HTML form content to browser with auto form validation
                # to finally send request to the IDP
                return HttpResponse(http_info['data'])
            else:
                return HttpResponseRedirect(get_location(http_info))
    else:
        logger.error('No SAMLResponse or SAMLRequest parameter found')
        raise Http404('No SAMLResponse or SAMLRequest parameter found')
Пример #4
0
def logout(request, config_loader_path=None):
    """SAML Logout Request initiator

    This view initiates the SAML2 Logout request
    using the pysaml2 library to create the LogoutRequest.
    """
    initiate_global_logout = get_custom_setting('DJANGOSAML_INITIATE_GLOBAL_LOGOUT', default=True)
    # Handle SP that should NOT initiate a global logout
    if not initiate_global_logout:
        logger.debug('SP cannot initiate a global logout. Doing local logout for %s and redirecting to %s' % (request.user, settings.LOGOUT_REDIRECT_URL))
        auth.logout(request)
        return HttpResponseRedirect(settings.LOGOUT_REDIRECT_URL)    
    state = StateCache(request.session)
    conf = get_config(config_loader_path, request)

    client = Saml2Client(conf, state_cache=state,
                         identity_cache=IdentityCache(request.session))
    subject_id = _get_subject_id(request.session)
    if subject_id is None:
        logger.warning(
            'The session does not contain the subject id for user %s',
            request.user)

    result = client.global_logout(subject_id)

    state.sync()

    if not result:
        logger.error("Looks like the user %s is not logged in any IdP/AA", subject_id)
        return HttpResponseBadRequest("You are not logged in any IdP/AA")

    if len(result) > 1:
        logger.error('Sorry, I do not know how to logout from several sources. I will logout just from the first one')

    for entityid, logout_info in result.items():
        if isinstance(logout_info, tuple):
            binding, http_info = logout_info
            if binding == BINDING_HTTP_POST:
                logger.debug('Returning form to the IdP to continue the logout process')
                body = ''.join(http_info['data'])
                return HttpResponse(body)
            elif binding == BINDING_HTTP_REDIRECT:
                logger.debug('Redirecting to the IdP to continue the logout process')
                return HttpResponseRedirect(get_location(http_info))
            else:
                logger.error('Unknown binding: %s', binding)
                return HttpResponseServerError('Failed to log out')
        else:
            # We must have had a soap logout
            return finish_logout(request, logout_info)

    logger.error('Could not logout because there only the HTTP_REDIRECT is supported')
    return HttpResponseServerError('Logout Binding not supported')
Пример #5
0
def echo_attributes(request,
                    config_loader_path=None,
                    template='djangosaml2/echo_attributes.html'):
    """Example view that echo the SAML attributes of an user"""
    state = StateCache(request.session)
    conf = get_config(config_loader_path, request)

    client = Saml2Client(conf, state_cache=state,
                         identity_cache=IdentityCache(request.session))
    subject_id = _get_subject_id(request.session)
    identity = client.users.get_identity(subject_id,
                                         check_not_on_or_after=False)
    return render(request, template, {'attributes': identity[0]})
Пример #6
0
def logout_service(request, config_loader_path=None, next_page=None):
    """SAML Logout Response endpoint

    The IdP will send the logout response to this view,
    which will process it with pysaml2 help and log the user
    out.
    Note that the IdP can request a logout even when
    we didn't initiate the process as a single logout
    request started by another SP.
    """
    logger.debug('Logout service started')
    conf = get_config(config_loader_path, request)

    state = StateCache(request.session)
    client = Saml2Client(conf,
                         state_cache=state,
                         identity_cache=IdentityCache(request.session),
                         logger=logger)

    if 'SAMLResponse' in request.GET:  # we started the logout
        logger.debug('Receiving a logout response from the IdP')
        response = client.logout_response(request.GET['SAMLResponse'],
                                          binding=BINDING_HTTP_REDIRECT)
        state.sync()
        if response and response[1] == '200 Ok':
            return django_logout(request, next_page=next_page)
        else:
            logger.error('Unknown error during the logout')
            return HttpResponse('Error during logout')

    elif 'SAMLRequest' in request.GET:  # logout started by the IdP
        logger.debug('Receiving a logout request from the IdP')
        subject_id = _get_subject_id(request.session)
        response, success = client.logout_request(request.GET, subject_id)
        state.sync()
        if success:
            auth.logout(request)
            assert response[0][0] == 'Location'
            url = response[0][1]
            return HttpResponseRedirect(url)
        elif response is not None:
            assert response[0][0] == 'Location'
            url = response[0][1]
            return HttpResponseRedirect(url)
        else:
            logger.error('Unknown error during the logout')
            return HttpResponse('Error during logout')
    else:
        logger.error('No SAMLResponse or SAMLRequest parameter found')
        raise Http404('No SAMLResponse or SAMLRequest parameter found')
Пример #7
0
def do_logout_service(request,
                      data,
                      binding,
                      config_loader_path=None,
                      next_page=None,
                      logout_error_template='djangosaml2/logout_error.html'):
    """SAML Logout Response endpoint

    The IdP will send the logout response to this view,
    which will process it with pysaml2 help and log the user
    out.
    Note that the IdP can request a logout even when
    we didn't initiate the process as a single logout
    request started by another SP.
    """
    logger.debug('Logout service started')
    conf = get_config(config_loader_path, request)

    state = StateCache(request.session)
    client = Saml2Client(conf,
                         state_cache=state,
                         identity_cache=IdentityCache(request.session))

    if 'SAMLResponse' in data:  # we started the logout
        logger.debug('Receiving a logout response from the IdP')
        response = client.parse_logout_request_response(
            data['SAMLResponse'], binding)
        state.sync()
        return finish_logout(request, response, next_page=next_page)

    elif 'SAMLRequest' in data:  # logout started by the IdP
        logger.debug('Receiving a logout request from the IdP')
        subject_id = _get_subject_id(request.session)
        if subject_id is None:
            logger.warning(
                'The session does not contain the subject id for user %s. Performing local logout'
                % request.user)
            auth.logout(request)
            return render_to_response(logout_error_template, {},
                                      context_instance=RequestContext(request))
        else:
            http_info = client.handle_logout_request(data['SAMLRequest'],
                                                     subject_id, binding)
            state.sync()
            auth.logout(request)
            return HttpResponseRedirect(get_location(http_info))
    else:
        logger.error('No SAMLResponse or SAMLRequest parameter found')
        raise Http404('No SAMLResponse or SAMLRequest parameter found')
Пример #8
0
def echo_attributes(request,
                    config_loader_path=None,
                    template='djangosaml2/echo_attributes.html'):
    """Example view that echo the SAML attributes of an user"""
    state = StateCache(request.session)
    conf = get_config(config_loader_path, request)

    client = Saml2Client(conf, state_cache=state,
                         identity_cache=IdentityCache(request.session))
    subject_id = _get_subject_id(request.session)
    try:
        identity = client.users.get_identity(subject_id,
                                             check_not_on_or_after=False)
    except AttributeError:
        return HttpResponse("No active SAML identity found. Are you sure you have logged in via SAML?")

    return render(request, template, {'attributes': identity[0]})
Пример #9
0
def logout(request, config_loader_path=None):
    """SAML Logout Request initiator

    This view initiates the SAML2 Logout request
    using the pysaml2 library to create the LogoutRequest.
    """
    logger.debug('Logout process started')
    state = StateCache(request.session)
    conf = get_config(config_loader_path, request)

    client = Saml2Client(conf,
                         state_cache=state,
                         identity_cache=IdentityCache(request.session),
                         logger=logger)
    subject_id = _get_subject_id(request.session)
    session_id, code, head, body = client.global_logout(subject_id)
    headers = dict(head)
    state.sync()
    logger.debug('Redirecting to the IdP to continue the logout process')
    return HttpResponseRedirect(headers['Location'])
Пример #10
0
    def get(self, request):
        state = StateCache(request.session)
        conf = get_config(request=request)

        client = Saml2Client(conf, state_cache=state, identity_cache=IdentityCache(request.session))
        subject_id = _get_subject_id(request.session)
        if subject_id is None:
            return logout_failed(_('You cannot be logged out.'))

        try:
            result = client.global_logout(subject_id)
        except KeyError:
            return logout_failed(_('You are not logged in any IdP/AA.'))

        state.sync()
        if not result:
            return logout_failed(_('You are not logged in any IdP/AA.'))

        # Logout is supported only from 1 IdP
        binding, http_info = result.values()[0]
        return HttpResponseRedirect(get_location(http_info))
Пример #11
0
    def test_valid_form(self):
        url, data, session_id = self._get_sp_authn_request()
        response = self.client.post(url, data, follow=True)
        # csrf_regexp = '<input type="hidden" name="csrfmiddlewaretoken" value="(?P<value>[a-zA-Z0-9+=]*)">'
        # login_data['csrfmiddlewaretoken'] = re.findall(csrf_regexp, response.content.decode())[0]
        self.login_data['password'] = '******'
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)
        # is there a SAML response?
        saml_resp = re.findall(samlresponse_form_regexp,
                               login_response.content.decode())
        assert saml_resp

        # test agreement screens
        self.sp.agreement_screen = 1
        self.sp.save()
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)
        assert 'has requested the following informations' in login_response.content.decode(
        )

        # don't show again
        agr_data = dict(dont_show_again=1, confirm=1)
        agr_url = reverse('uniauth:saml_user_agreement')
        agr_response = self.client.post(agr_url, data=agr_data, follow=True)

        # login again, agreement screen should not be displayed anymore
        # purge persistent_id from storage
        self.user.persistentid_set.all().delete()
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)
        saml_resp = re.findall(samlresponse_form_regexp,
                               login_response.content.decode())
        assert saml_resp

        # transient name_id format, remove persistent_id
        sp_conf = copy.deepcopy(SAML_SP_CONFIG)
        del (sp_conf['service']['sp']['name_id_format'][0])
        self.sp_conf.load(sp_conf)
        url, data, session_id = self._get_sp_authn_request()
        response = self.client.post(url, data, follow=True)
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)

        # test logout
        session = self.client.session
        state = StateCache(session)
        identity_cache = IdentityCache(session)
        oq_cache = OutstandingQueriesCache(session)
        oq_cache.set(session_id, '/')
        outstanding_queries = oq_cache.outstanding_queries()
        client = Saml2Client(self.sp_conf,
                             state_cache=state,
                             identity_cache=IdentityCache(session))
        response = client.parse_authn_request_response(saml_resp[0],
                                                       BINDING_HTTP_POST,
                                                       outstanding_queries)
        # this should take name_id dict
        # result = client.global_logout(session['SAML']['subject_id'])
        logout_result = client.global_logout(response.name_id)

        # is there a SAML response?
        saml_req_logout = re.findall(samlrequest_form_regexp,
                                     logout_result[idp_eid][1]['data'])
        assert saml_req_logout

        logout_response = self.client.post(
            logout_url, data={'SAMLRequest': saml_req_logout}, follow=True)
Пример #12
0
    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)

        attribute_mapping = get_custom_setting(
            'SAML_ATTRIBUTE_MAPPING', {'uid': ('username', )})
        create_unknown_user = get_custom_setting(
            'SAML_CREATE_UNKNOWN_USER', True)

        conf = get_config(request=request)
        client = Saml2Client(conf, identity_cache=IdentityCache(request.session))

        oq_cache = OutstandingQueriesCache(request.session)
        outstanding_queries = oq_cache.outstanding_queries()

        xmlstr = serializer.validated_data['SAMLResponse']

        # process the authentication response
        try:
            response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries)
        except Exception as e:
            if isinstance(e, StatusRequestDenied):
                return login_failed(_('Authentication request has been denied by identity provider. '
                                      'Please check your credentials.'))
            logger.error('SAML response parsing failed %s' % e)
            return login_failed(_('SAML2 response has errors.'))

        if response is None:
            logger.error('SAML response is None')
            return login_failed(_('SAML response has errors. Please check the logs'))

        if response.assertion is None:
            logger.error('SAML response assertion is None')
            return login_failed(_('SAML response has errors. Please check the logs'))

        session_id = response.session_id()
        oq_cache.delete(session_id)

        # authenticate the remote user
        session_info = response.session_info()

        if callable(attribute_mapping):
            attribute_mapping = attribute_mapping()
        if callable(create_unknown_user):
            create_unknown_user = create_unknown_user()

        user = auth.authenticate(
            session_info=session_info,
            attribute_mapping=attribute_mapping,
            create_unknown_user=create_unknown_user,
        )
        if user is None:
            return login_failed(_('SAML2 authentication failed.'))

        registration_method = settings.WALDUR_AUTH_SAML2.get('name', 'saml2')
        if user.registration_method != registration_method:
            user.registration_method = registration_method
            user.save(update_fields=['registration_method'])

        # required for validating SAML2 logout requests
        auth.login(request, user)
        _set_subject_id(request.session, session_info['name_id'])
        logger.debug('User %s authenticated via SSO.', user)

        logger.debug('Sending the post_authenticated signal')
        post_authenticated.send_robust(sender=user, session_info=session_info)
        token = self.refresh_token(user)

        logger.info('Authenticated with SAML token. Returning token for successful login of user %s', user)
        event_logger.saml2_auth.info(
            'User {user_username} with full name {user_full_name} logged in successfully with SAML2.',
            event_type='auth_logged_in_with_saml2', event_context={'user': user}
        )
        return login_completed(token.key, 'saml2')
Пример #13
0
def assertion_consumer_service(request,
                               config_loader_path=None,
                               attribute_mapping=None,
                               create_unknown_user=None):
    """SAML Authorization Response endpoint

    The IdP will send its response to this view, which
    will process it with pysaml2 help and log the user
    in using the custom Authorization backend
    djangosaml2.backends.Saml2Backend that should be
    enabled in the settings.py
    """
    attribute_mapping = attribute_mapping or get_custom_setting(
        'SAML_ATTRIBUTE_MAPPING', {'uid': ('username', )})
    create_unknown_user = create_unknown_user or get_custom_setting(
        'SAML_CREATE_UNKNOWN_USER', True)
    logger.debug('Assertion Consumer Service started')

    conf = get_config(config_loader_path, request)
    if 'SAMLResponse' not in request.POST:
        return HttpResponseBadRequest(
            'Couldn\'t find "SAMLResponse" in POST data.')
    xmlstr = request.POST['SAMLResponse']
    client = Saml2Client(conf, identity_cache=IdentityCache(request.session))

    oq_cache = OutstandingQueriesCache(request.session)
    outstanding_queries = oq_cache.outstanding_queries()

    try:
        response = client.parse_authn_request_response(xmlstr,
                                                       BINDING_HTTP_POST,
                                                       outstanding_queries)
    except StatusError:
        return render(request, 'djangosaml2/login_error.html', status=403)

    except MissingKey:
        logger.error('MissingKey error in ACS')
        return HttpResponseForbidden(
            "The Identity Provider is not configured correctly: "
            "the certificate key is missing")
    if response is None:
        logger.error('SAML response is None')
        return HttpResponseBadRequest(
            "SAML response has errors. Please check the logs")

    session_id = response.session_id()
    oq_cache.delete(session_id)

    # authenticate the remote user
    session_info = response.session_info()

    if callable(attribute_mapping):
        attribute_mapping = attribute_mapping()
    if callable(create_unknown_user):
        create_unknown_user = create_unknown_user()

    logger.debug('Trying to authenticate the user')
    user = auth.authenticate(session_info=session_info,
                             attribute_mapping=attribute_mapping,
                             create_unknown_user=create_unknown_user)
    if user is None:
        logger.error('The user is None')
        return render(request,
                      'djangosaml2/permission_denied.html',
                      status=403)

    auth.login(request, user)
    _set_subject_id(request.session, session_info['name_id'])

    logger.debug('Sending the post_authenticated signal')
    post_authenticated.send_robust(sender=user, session_info=session_info)

    # redirect the user to the view where he came from
    default_relay_state = get_custom_setting('ACS_DEFAULT_REDIRECT_URL',
                                             settings.LOGIN_REDIRECT_URL)
    relay_state = request.POST.get('RelayState', default_relay_state)
    if not relay_state:
        logger.warning('The RelayState parameter exists but is empty')
        relay_state = default_relay_state
    if not is_safe_url(url=relay_state, host=request.get_host()):
        came_from = settings.LOGIN_REDIRECT_URL
    logger.debug('Redirecting to the RelayState: %s', relay_state)
    return HttpResponseRedirect(relay_state)
Пример #14
0
def spid_logout(request, config_loader_path=None, **kwargs):
    """SAML Logout Request initiator

    This view initiates the SAML2 Logout request
    using the pysaml2 library to create the LogoutRequest.
    """
    if not request.user.is_authenticated:
        return HttpResponseRedirect(settings.LOGOUT_REDIRECT_URL)

    state = StateCache(request.saml_session)
    conf = get_config(config_loader_path, request)
    client = Saml2Client(conf,
                         state_cache=state,
                         identity_cache=IdentityCache(request.saml_session))

    # whatever happens, however, the user will be logged out of this sp
    auth.logout(request)
    state.sync()

    subject_id = djangosaml2_views._get_subject_id(request.saml_session)
    if subject_id is None:
        logger.warning(
            f"The session does not contain the subject id for user {request.user}"
        )
        logger.error(
            f"Looks like the user {subject_id} is not logged in any IdP/AA")
        return HttpResponseBadRequest("You are not logged in any IdP/AA")

    slo_req = saml2.samlp.LogoutRequest()

    slo_req.destination = subject_id.name_qualifier
    # spid-testenv2 preleva l'attribute consumer service dalla authnRequest (anche se questo sta già nei metadati...)
    slo_req.attribute_consuming_service_index = "0"

    issuer = saml2.saml.Issuer()
    issuer.name_qualifier = client.config.entityid
    issuer.text = client.config.entityid
    issuer.format = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
    slo_req.issuer = issuer

    # message id
    slo_req.id = saml2.s_utils.sid()
    slo_req.version = saml2.VERSION  # "2.0"
    slo_req.issue_instant = saml2.time_util.instant()

    # oggetto
    slo_req.name_id = subject_id

    try:
        session_info = client.users.get_info_from(slo_req.name_id,
                                                  subject_id.name_qualifier,
                                                  False)
    except KeyError as e:
        logger.error(f"SPID Logout error: {e}")
        return HttpResponseRedirect("/")

    session_indexes = [session_info["session_index"]]

    # aggiungere session index
    if session_indexes:
        sis = []
        for si in session_indexes:
            if isinstance(si, saml2.samlp.SessionIndex):
                sis.append(si)
            else:
                sis.append(saml2.samlp.SessionIndex(text=si))
        slo_req.session_index = sis

    slo_req.protocol_binding = SAML2_DEFAULT_BINDING

    assertion_consumer_service_url = client.config._sp_endpoints[
        "assertion_consumer_service"][0][0]
    slo_req.assertion_consumer_service_url = assertion_consumer_service_url

    slo_req_signed = client.sign(
        slo_req,
        sign_prepare=False,
        sign_alg=settings.SPID_SIG_ALG,
        digest_alg=settings.SPID_DIG_ALG,
    )

    _req_str = slo_req_signed
    logger.debug(
        f"LogoutRequest to {subject_id.name_qualifier}: {repr_saml_request(_req_str)}"
    )

    slo_location = client.metadata.single_logout_service(
        subject_id.name_qualifier, SAML2_DEFAULT_BINDING,
        "idpsso")[0]["location"]

    if not slo_location:
        error_message = f"Unable to know SLO endpoint in {subject_id.name_qualifier}"
        logger.error(error_message)
        return HttpResponse(error_message)

    http_info = client.apply_binding(
        SAML2_DEFAULT_BINDING,
        _req_str,
        slo_location,
        sign=True,
        sigalg=settings.SPID_SIG_ALG,
    )
    state.sync()
    return HttpResponse(http_info["data"])
Пример #15
0
def spid_logout(request, config_loader_path=None, **kwargs):
    """SAML Logout Request initiator

    This view initiates the SAML2 Logout request
    using the pysaml2 library to create the LogoutRequest.
    """
    state = StateCache(request.session)
    conf = get_config(config_loader_path, request)

    client = Saml2Client(conf, state_cache=state,
                         identity_cache=IdentityCache(request.session))
    subject_id = _get_subject_id(request.session)
    if subject_id is None:
        logger.warning(
            'The session does not contain the subject id for user %s',
            request.user)
        logger.error("Looks like the user %s is not logged in any IdP/AA", subject_id)
        return HttpResponseBadRequest("You are not logged in any IdP/AA")

    slo_req = saml2.samlp.LogoutRequest()

    binding = settings.SPID_DEFAULT_BINDING
    location_fixed = subject_id.name_qualifier
    location = location_fixed
    slo_req.destination = location_fixed
    # spid-testenv2 preleva l'attribute consumer service dalla authnRequest (anche se questo sta già nei metadati...)
    slo_req.attribute_consuming_service_index = "0"

    # import pdb; pdb.set_trace()
    issuer = saml2.saml.Issuer()
    issuer.name_qualifier = client.config.entityid
    issuer.text = client.config.entityid
    issuer.format = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
    slo_req.issuer = issuer

    # message id
    slo_req.id = saml2.s_utils.sid()
    slo_req.version = saml2.VERSION # "2.0"
    slo_req.issue_instant = saml2.time_util.instant()

    # oggetto
    slo_req.name_id = subject_id


    session_info = client.users.get_info_from(slo_req.name_id,
                                              subject_id.name_qualifier,
                                              False)
    session_indexes = [session_info['session_index']]

    # aggiungere session index
    if session_indexes:
        sis = []
        for si in session_indexes:
            if isinstance(si, saml2.samlp.SessionIndex):
                sis.append(si)
            else:
                sis.append(saml2.samlp.SessionIndex(text=si))
        slo_req.session_index = sis

    slo_req.protocol_binding = binding

    assertion_consumer_service_url = client.config._sp_endpoints['assertion_consumer_service'][0][0]
    slo_req.assertion_consumer_service_url = assertion_consumer_service_url

    slo_req_signed = client.sign(slo_req, sign_prepare=False,
                                 sign_alg=settings.SPID_ENC_ALG,
                                 digest_alg=settings.SPID_DIG_ALG)
    session_id = slo_req.id


    _req_str = slo_req_signed
    logger.debug('LogoutRequest to {}: {}'.format(subject_id.name_qualifier,
                                                  repr_saml(_req_str)))

    # get slo from metadata
    slo_location = None
    # for k,v in client.metadata.metadata.items():
        # idp_nq = v.entity.get(subject_id.name_qualifier)
        # if idp_nq:
            # slo_location = idp_nq['idpsso_descriptor'][0]['single_logout_service'][0]['location']

    slo_location = client.metadata.single_logout_service(subject_id.name_qualifier,
                                                         binding,
                                                         "idpsso")[0]['location']
    if not slo_location:
        logger.error('Unable to know SLO endpoint in {}'.format(subject_id.name_qualifier))
        return HttpResponse(text_type(e))

    http_info = client.apply_binding(binding,
                                     _req_str,
                                     slo_location,
                                     sign=True,
                                     sigalg=settings.SPID_ENC_ALG)

    state.sync()
    return HttpResponse(http_info['data'])
Пример #16
0
def assertion_consumer_service(request,
                               config_loader_path=None,
                               attribute_mapping=None,
                               create_unknown_user=None):
    """SAML Authorization Response endpoint

    The IdP will send its response to this view, which
    will process it with pysaml2 help and log the user
    in using the custom Authorization backend
    djangosaml2.backends.Saml2Backend that should be
    enabled in the settings.py
    """
    attribute_mapping = attribute_mapping or get_custom_setting(
        'SAML_ATTRIBUTE_MAPPING', {'uid': ('username', )})
    create_unknown_user = create_unknown_user if create_unknown_user is not None else \
                          get_custom_setting('SAML_CREATE_UNKNOWN_USER', True)
    conf = get_config(config_loader_path, request)
    try:
        xmlstr = request.POST['SAMLResponse']
    except KeyError:
        logger.warning('Missing "SAMLResponse" parameter in POST data.')
        raise SuspiciousOperation

    client = Saml2Client(conf, identity_cache=IdentityCache(request.session))

    oq_cache = OutstandingQueriesCache(request.session)
    outstanding_queries = oq_cache.outstanding_queries()

    try:
        response = client.parse_authn_request_response(xmlstr,
                                                       BINDING_HTTP_POST,
                                                       outstanding_queries)
    except (StatusError, ToEarly):
        logger.exception("Error processing SAML Assertion.")
        return fail_acs_response(request)
    except ResponseLifetimeExceed:
        logger.info(
            "SAML Assertion is no longer valid. Possibly caused by network delay or replay attack.",
            exc_info=True)
        return fail_acs_response(request)
    except SignatureError:
        logger.info("Invalid or malformed SAML Assertion.", exc_info=True)
        return fail_acs_response(request)
    except StatusAuthnFailed:
        logger.info("Authentication denied for user by IdP.", exc_info=True)
        return fail_acs_response(request)
    except StatusRequestDenied:
        logger.warning("Authentication interrupted at IdP.", exc_info=True)
        return fail_acs_response(request)
    except MissingKey:
        logger.exception(
            "SAML Identity Provider is not configured correctly: certificate key is missing!"
        )
        return fail_acs_response(request)
    except UnsolicitedResponse:
        logger.exception(
            "Received SAMLResponse when no request has been made.")
        return fail_acs_response(request)

    if response is None:
        logger.warning("Invalid SAML Assertion received (unknown error).")
        return fail_acs_response(request,
                                 status=400,
                                 exc_class=SuspiciousOperation)

    session_id = response.session_id()
    oq_cache.delete(session_id)

    # authenticate the remote user
    session_info = response.session_info()

    if callable(attribute_mapping):
        attribute_mapping = attribute_mapping()
    if callable(create_unknown_user):
        create_unknown_user = create_unknown_user()

    logger.debug('Trying to authenticate the user. Session info: %s',
                 session_info)
    user = auth.authenticate(request=request,
                             session_info=session_info,
                             attribute_mapping=attribute_mapping,
                             create_unknown_user=create_unknown_user)
    if user is None:
        logger.warning(
            "Could not authenticate user received in SAML Assertion. Session info: %s",
            session_info)
        raise PermissionDenied

    auth.login(request, user)
    _set_subject_id(request.session, session_info['name_id'])
    logger.debug("User %s authenticated via SSO.", user)

    logger.debug('Sending the post_authenticated signal')
    post_authenticated.send_robust(sender=user, session_info=session_info)

    # redirect the user to the view where he came from
    default_relay_state = get_custom_setting('ACS_DEFAULT_REDIRECT_URL',
                                             settings.LOGIN_REDIRECT_URL)
    relay_state = request.POST.get('RelayState', default_relay_state)
    if not relay_state:
        logger.warning('The RelayState parameter exists but is empty')
        relay_state = default_relay_state
    if not is_safe_url_compat(url=relay_state,
                              allowed_hosts={request.get_host()}):
        relay_state = settings.LOGIN_REDIRECT_URL
    logger.debug('Redirecting to the RelayState: %s', relay_state)
    return HttpResponseRedirect(relay_state)
Пример #17
0
def logout_service(request, config_loader_path=None, next_page=None,
                   logout_error_template='djangosaml2/logout_error.html'):
    """SAML Logout Response endpoint

    The IdP will send the logout response to this view,
    which will process it with pysaml2 help and log the user
    out.
    Note that the IdP can request a logout even when
    we didn't initiate the process as a single logout
    request started by another SP.
    """
    logger.debug('Logout service started')
    conf = get_config(config_loader_path, request)

    state = StateCache(request.session)
    client = Saml2Client(conf, state_cache=state,
                         identity_cache=IdentityCache(request.session),
                         logger=logger)

    if 'SAMLResponse' in request.GET:  # we started the logout
        logger.debug('Receiving a logout response from the IdP')
        response = client.logout_response(request.GET['SAMLResponse'],
                                          binding=BINDING_HTTP_REDIRECT)
        state.sync()
        if response and response[1] == '200 Ok':
            if next_page is None and hasattr(settings, 'LOGOUT_REDIRECT_URL'):
                next_page = settings.LOGOUT_REDIRECT_URL
            logger.debug('Performing django_logout with a next_page of %s'
                         % next_page)
            return django_logout(request, next_page=next_page)
        else:
            logger.error('Unknown error during the logout')
            return HttpResponse('Error during logout')

    elif 'SAMLRequest' in request.GET:  # logout started by the IdP
        logger.debug('Receiving a logout request from the IdP')
        subject_id = _get_subject_id(request.session)
        if subject_id is None:
            logger.warning(
                'The session does not contain the subject id for user %s. Performing local logout'
                % request.user)
            auth.logout(request)
            return render_to_response(logout_error_template, {},
                                      context_instance=RequestContext(request))
        else:
            response, success = client.logout_request(request.GET, subject_id)
            state.sync()
            if success:
                auth.logout(request)
                assert response[0][0] == 'Location'
                url = response[0][1]
                return HttpResponseRedirect(url)
            elif response is not None:
                assert response[0][0] == 'Location'
                url = response[0][1]
                return HttpResponseRedirect(url)
            else:
                logger.error('Unknown error during the logout')
                return HttpResponse('Error during logout')
    else:
        logger.error('No SAMLResponse or SAMLRequest parameter found')
        raise Http404('No SAMLResponse or SAMLRequest parameter found')
Пример #18
0
def assertion_consumer_service(request,
                               config_loader_path=None,
                               attribute_mapping=None,
                               create_unknown_user=None):
    """SAML Authorization Response endpoint

    The IdP will send its response to this view, which
    will process it with pysaml2 help and log the user
    in using the custom Authorization backend
    djangosaml2.backends.Saml2Backend that should be
    enabled in the settings.py
    """
    attribute_mapping = attribute_mapping or get_custom_setting(
            'SAML_ATTRIBUTE_MAPPING', {'uid': ('username', )})
    create_unknown_user = create_unknown_user or get_custom_setting(
            'SAML_CREATE_UNKNOWN_USER', True)
    logger.debug('Assertion Consumer Service started')

    conf = get_config(config_loader_path, request)
    if 'SAMLResponse' not in request.POST:
        return HttpResponseBadRequest(
            'Couldn\'t find "SAMLResponse" in POST data.')
    post = {'SAMLResponse': request.POST['SAMLResponse']}
    client = Saml2Client(conf, identity_cache=IdentityCache(request.session),
                         logger=logger)

    oq_cache = OutstandingQueriesCache(request.session)
    outstanding_queries = oq_cache.outstanding_queries()

    # process the authentication response
    response = client.response(post, outstanding_queries)
    if response is None:
        logger.error('SAML response is None')
        return HttpResponseBadRequest(
            "SAML response has errors. Please check the logs")

    session_id = response.session_id()
    oq_cache.delete(session_id)

    # authenticate the remote user
    session_info = response.session_info()

    if callable(attribute_mapping):
        attribute_mapping = attribute_mapping()
    if callable(create_unknown_user):
        create_unknown_user = create_unknown_user()

    logger.debug('Trying to authenticate the user')
    user = auth.authenticate(session_info=session_info,
                             attribute_mapping=attribute_mapping,
                             create_unknown_user=create_unknown_user)
    if user is None:
        logger.error('The user is None')
        return HttpResponseForbidden("Permission denied")

    auth.login(request, user)
    _set_subject_id(request.session, session_info['name_id'])

    logger.debug('Sending the post_authenticated signal')
    post_authenticated.send_robust(sender=user, session_info=session_info)

    # redirect the user to the view where he came from
    relay_state = request.POST.get('RelayState', '/')
    if not relay_state:
        logger.warning('The RelayState parameter exists but is empty')
        relay_state = settings.LOGIN_REDIRECT_URL
    logger.debug('Redirecting to the RelayState: ' + relay_state)
    return HttpResponseRedirect(relay_state)