Ejemplo n.º 1
0
    def test_get_config(self):
        saml_config = get_config()
        self.assertEqual(saml_config.entityid, 'http://localhost:8000/spid/metadata')

        request = self.factory.get('')
        saml_config = get_config(request=request)
        self.assertEqual(saml_config.entityid, 'http://localhost:8000/spid/metadata')

        request = self.factory.get('/spid/metadata')
        saml_config = get_config(request=request)
        self.assertEqual(saml_config.entityid, 'http://testserver/spid/metadata/')
Ejemplo n.º 2
0
    def test_get_config(self):
        saml_config = get_config()
        self.assertIsNone(saml_config.entityid)

        # SPConfig for a SPID request
        request = self.factory.get("/spid/metadata")
        saml_config = get_config(request=request)
        if base_dir.name != "example":
            self.assertEqual(saml_config.entityid,
                             "http://testserver/spid/metadata/")
        else:
            self.assertEqual(saml_config.entityid,
                             "https://localhost:8000/spid/metadata/")
Ejemplo n.º 3
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
Ejemplo n.º 4
0
def metadata(request, config_loader_path=None, valid_for=None):
    """Returns an XML with the SAML 2.0 metadata for this
    SP as configured in the settings.py file.
    """
    conf = get_config(config_loader_path, request)
    metadata = entity_descriptor(conf)
    return HttpResponse(content=str(metadata), content_type="text/xml; charset=utf8")
Ejemplo n.º 5
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')
Ejemplo n.º 6
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)
Ejemplo n.º 7
0
def logout(request, config_loader_path=None, logout_error_template='djangosaml2/logout_error.html'):
    """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),
                         )
    subject_id = _get_subject_id(request.session)
    if subject_id is None:
        logger.warning(
            'The session does not contains the subject id for user %s'
            % request.user)
        auth.logout(request)
        return render_to_response(logout_error_template, {},
                                      context_instance=RequestContext(request))
    resp = client.global_logout(subject_id)
    entity_ids = client.users.issuers_of_info(subject_id)
    state.sync()
    logger.debug('Redirecting to the IdP to continue the logout process')
    return HttpResponseRedirect(resp[entity_ids[0]][1]['headers'][0][1])
Ejemplo n.º 8
0
def login(request,
          config_loader_path=None,
          wayf_template='djangosaml2/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 Redirect protocol binding.
    """
    logger.debug('Login process started')

    came_from = request.GET.get('next', 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_to_response(authorization_error_template, {
                    'came_from': came_from,
                    }, context_instance=RequestContext(request))

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

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

    client = Saml2Client(conf, logger=logger)
    try:
        (session_id, result) = client.authenticate(
            entityid=selected_idp, relay_state=came_from,
            binding=BINDING_HTTP_REDIRECT,
            )
    except TypeError, e:
        logger.error('Unable to know which IdP to use')
        return HttpResponse(unicode(e))
Ejemplo n.º 9
0
def logout_ls_saml2_view(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')
    """Turn off saml2 """
    _set_saml2_auth_used(request.session, False)
    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 logout_view(request)
        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 HttpResponse('Error during logout')
        else:
            response, success = client.logout_request(request.GET, subject_id)
            state.sync()
            if success:
                logout_view(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')
Ejemplo n.º 10
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')
Ejemplo n.º 11
0
def metadata_spid(request, config_loader_path=None, valid_for=None):
    """Returns an XML with the SAML 2.0 metadata for this
    SP as configured in the settings.py file.
    """
    conf = get_config(config_loader_path, request)
    xmldoc = spid_sp_metadata(conf)
    return HttpResponse(content=str(xmldoc).encode('utf-8'),
                        content_type="text/xml; charset=utf8")
Ejemplo n.º 12
0
def metadata(request, config_loader_path=None, valid_for=None):
    """Returns an XML with the SAML 2.0 metadata for this
    SP as configured in the settings.py file.
    """
    conf = get_config(config_loader_path, request)
    metadata = entity_descriptor(conf)
    return HttpResponse(content=str(metadata),
                        content_type="text/xml; charset=utf8")
Ejemplo n.º 13
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")
Ejemplo n.º 14
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), logger=logger)
    subject_id = _get_subject_id(request.session)
    identity = client.users.get_identity(subject_id, check_not_on_or_after=False)
    return render_to_response(template, {"attributes": identity[0]}, context_instance=RequestContext(request))
Ejemplo n.º 15
0
 def test_spid_public_cert(self):
     request = self.factory.get('/spid/metadata')
     saml_config = get_config(request=request)
     self.assertEqual(saml_config.key_file, 'tests/certificates/private.key')
     self.assertEqual(saml_config.cert_file, 'example/certificates/public.cert')
     self.assertListEqual(saml_config.encryption_keypairs, [{
         'key_file': 'tests/certificates/private.key',
         'cert_file': 'example/certificates/public.cert'
     }])
Ejemplo n.º 16
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')
Ejemplo n.º 17
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':
            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)
        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')
Ejemplo n.º 18
0
    def test_assertion_consumer_service(self):
        # Get initial number of users
        initial_user_count = User.objects.count()
        settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com',
                                                idp_hosts=['idp.example.com'])

        config = get_config()
        # session_id should start with a letter since it is a NCName
        session_id = "a0123456789abcdef0123456789abcdef"
        came_from = '/another-view/'
        saml_response = auth_response({'uid': 'student'}, session_id, config)
        self.init_cookies()
        self.add_outstanding_query(session_id, came_from)

        # this will create a user
        response = self.client.post(
            '/acs/', {
                'SAMLResponse': base64.b64encode(str(saml_response)),
                'RelayState': came_from,
            })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'testserver')
        self.assertEquals(url.path, came_from)

        self.assertEquals(User.objects.count(), initial_user_count + 1)
        user_id = self.client.session[SESSION_KEY]
        user = User.objects.get(id=user_id)
        self.assertEquals(user.username, 'student')

        # let's create another user and log in with that one
        new_user = User.objects.create(username='******', password='******')

        session_id = "a1111111111111111111111111111111"
        came_from = ''  # bad, let's see if we can deal with this
        saml_response = auth_response({'uid': 'teacher'}, session_id, config)
        self.add_outstanding_query(session_id, '/')
        response = self.client.post(
            '/acs/', {
                'SAMLResponse': base64.b64encode(str(saml_response)),
                'RelayState': came_from,
            })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'testserver')
        # as the RelayState is empty we have redirect to LOGIN_REDIRECT_URL
        self.assertEquals(url.path, '/accounts/profile/')
        self.assertEquals(new_user.id, self.client.session[SESSION_KEY])
Ejemplo n.º 19
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]})
Ejemplo n.º 20
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]})
Ejemplo n.º 21
0
def login(request, config_loader_path=None,
          authorization_error_template='djangosaml2/auth_error.html'):
    """ Mock SAML2 Authorization form. """
    if request.method!="POST":
        came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL)
        return TemplateResponse(request, 'mockdjangosaml2/login.html',
                                {'form': MockAuthForm(request),
                                 'next': came_from})
    logger.debug('Mock login process started')

    came_from = request.POST.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

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

    logger.debug('Check credentials.')
    form = MockAuthForm(data=request.POST)
    if not form.is_valid():
        return TemplateResponse(request, 'mockdjangosaml2/login.html',
                                {'form': form})
    # authenticate the remote user
    session_info = \
        MOCK_SAML2_USERS[request.POST.get('username')]['session_info']
    request.session['mock_session_info'] = session_info
    request.session['mock_came_from'] = came_from
    request.session.modified = True

    if config_loader_path:
        conf = get_config(config_loader_path, request)
        acs_location = conf
    else:
        acs_location = reverse('saml2_acs')
    return HttpResponseRedirect(acs_location)
Ejemplo n.º 22
0
 def test_spid_public_cert(self):
     request = self.factory.get("/spid/metadata")
     saml_config = get_config(request=request)
     self.assertEqual(saml_config.key_file,
                      "tests/certificates/private.key")
     self.assertEqual(saml_config.cert_file,
                      "example/certificates/public.cert")
     self.assertListEqual(
         saml_config.encryption_keypairs,
         [{
             "key_file": "tests/certificates/private.key",
             "cert_file": "example/certificates/public.cert",
         }],
     )
Ejemplo n.º 23
0
def login(request,
          config_loader_path=None,
          authorization_error_template='djangosaml2/auth_error.html'):
    """ Mock SAML2 Authorization form. """
    if request.method != "POST":
        came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL)
        return TemplateResponse(request, 'mockdjangosaml2/login.html', {
            'form': MockAuthForm(request),
            'next': came_from
        })
    logger.debug('Mock login process started')

    came_from = request.POST.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

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

    logger.debug('Check credentials.')
    form = MockAuthForm(data=request.POST)
    if not form.is_valid():
        return TemplateResponse(request, 'mockdjangosaml2/login.html',
                                {'form': form})
    # authenticate the remote user
    session_info = \
        MOCK_SAML2_USERS[request.POST.get('username')]['session_info']
    request.session['mock_session_info'] = session_info
    request.session['mock_came_from'] = came_from
    request.session.modified = True

    if config_loader_path:
        conf = get_config(config_loader_path, request)
        acs_location = conf
    else:
        acs_location = reverse('saml2_acs')
    return HttpResponseRedirect(acs_location)
Ejemplo n.º 24
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')
Ejemplo n.º 25
0
    def test_assertion_consumer_service(self):
        # Get initial number of users
        initial_user_count = User.objects.count()
        settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com',
                                                idp_hosts=['idp.example.com'])

        config = get_config()
        # session_id should start with a letter since it is a NCName
        session_id = "a0123456789abcdef0123456789abcdef"
        came_from = '/another-view/'
        saml_response = auth_response({'uid': 'student'}, session_id, config)
        self.init_cookies()
        self.add_outstanding_query(session_id, came_from)

        # this will create a user
        response = self.client.post('/acs/', {
                'SAMLResponse': base64.b64encode(str(saml_response)),
                'RelayState': came_from,
                })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'testserver')
        self.assertEquals(url.path, came_from)

        self.assertEquals(User.objects.count(), initial_user_count + 1)
        user_id = self.client.session[SESSION_KEY]
        user = User.objects.get(id=user_id)
        self.assertEquals(user.username, 'student')

        # let's create another user and log in with that one
        new_user = User.objects.create(username='******', password='******')

        session_id = "a1111111111111111111111111111111"
        came_from = ''  # bad, let's see if we can deal with this
        saml_response = auth_response({'uid': 'teacher'}, session_id, config)
        self.add_outstanding_query(session_id, '/')
        response = self.client.post('/acs/', {
                'SAMLResponse': base64.b64encode(str(saml_response)),
                'RelayState': came_from,
                })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'testserver')
        # as the RelayState is empty we have redirect to LOGIN_REDIRECT_URL
        self.assertEquals(url.path, '/accounts/profile/')
        self.assertEquals(new_user.id, self.client.session[SESSION_KEY])
Ejemplo n.º 26
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')
Ejemplo n.º 27
0
    def do_login(self):
        """Auxiliary method used in several tests (mainly logout tests)"""
        config = get_config()
        session_id = "a0123456789abcdef0123456789abcdef"
        came_from = '/another-view/'
        saml_response = auth_response({'uid': 'student'}, session_id, config)
        self.init_cookies()
        self.add_outstanding_query(session_id, came_from)

        # this will create a user
        response = self.client.post('/acs/', {
                'SAMLResponse': base64.b64encode(str(saml_response)),
                'RelayState': came_from,
                })
        self.assertEquals(response.status_code, 302)
Ejemplo n.º 28
0
    def do_login(self):
        """Auxiliary method used in several tests (mainly logout tests)"""
        config = get_config()
        session_id = "a0123456789abcdef0123456789abcdef"
        came_from = '/another-view/'
        saml_response = auth_response({'uid': 'student'}, session_id, config)
        self.init_cookies()
        self.add_outstanding_query(session_id, came_from)

        # this will create a user
        response = self.client.post('/acs/', {
                'SAMLResponse': base64.b64encode(str(saml_response)),
                'RelayState': came_from,
                })
        self.assertEquals(response.status_code, 302)
Ejemplo n.º 29
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))
    subject_id = _get_subject_id(request.session)
    if subject_id is None:
        logger.warning(
            'The session does not contains 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')
Ejemplo n.º 30
0
    def test_default_spid_saml_config(self):
        request = self.factory.get('/spid/metadata')
        saml_config = get_config(request=request)
        self.assertEqual(saml_config.entityid, 'http://testserver/spid/metadata/')

        self.assertEqual(saml_config.name_qualifier, '')  # ???

        self.assertEqual(saml_config._sp_name, 'http://testserver/spid/metadata/')
        self.assertEqual(saml_config._sp_name_id_format, [NAMEID_FORMAT_TRANSIENT])
        self.assertEqual(saml_config._sp_digest_algorithm, DIGEST_SHA256)
        self.assertEqual(saml_config._sp_signing_algorithm, SIG_RSA_SHA256)

        self.assertEqual(saml_config._sp_endpoints, {
            'assertion_consumer_service': [
                ('http://testserver/spid/acs/',
                 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST')
            ],
            'single_logout_service': [
                ('http://testserver/spid/ls/post/',
                 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST')]
        })

        metadata_files = list(saml_config.metadata.metadata)

        if base_dir.name == 'example':
            self.assertEqual(saml_config.cert_file,
                             str(base_dir.joinpath('certificates/public.cert')))
            self.assertEqual(saml_config.key_file,
                             str(base_dir.joinpath('certificates/private.key')))

            self.assertIn(str(base_dir.joinpath('spid_config/metadata/satosa-spid.xml')),
                          metadata_files),
            self.assertIn(str(base_dir.joinpath('spid_config/metadata/spid-saml-check.xml')),
                          metadata_files)
        else:
            self.assertEqual(saml_config.cert_file, 'tests/certificates/public.cert')
            self.assertEqual(saml_config.key_file, 'tests/certificates/private.key')
            self.assertIn('tests/metadata/satosa-spid.xml', metadata_files)
            self.assertIn('tests/metadata/spid-saml-check.xml', metadata_files)
            self.assertIn('tests/metadata/spid-sp-test.xml', metadata_files)

        self.assertEqual(
            saml_config.organization,
            {'name': [('Example', 'it'), ('Example', 'en')],
             'display_name': [('Example', 'it'), ('Example', 'en')],
             'url': [('http://www.example.it', 'it'), ('http://www.example.it', 'en')]}
        )
Ejemplo n.º 31
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')
        try:
            response = client.parse_logout_request_response(data['SAMLResponse'], binding)
            state.sync()
        except:
            traceback_info = '\n'.join(traceback.format_exception(*(sys.exc_info())))
            logger.warning('Encountered the following error when calling Saml2Client.global_logout(): %s' % traceback_info)            
            auth.logout(request)
            return HttpResponseRedirect(settings.LOGOUT_REDIRECT_URL)            
        else:
            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 HttpResponseRedirect(settings.LOGOUT_REDIRECT_URL)                        
        else:
            http_info = client.handle_logout_request(data['SAMLRequest'], subject_id, binding)
            state.sync()
            auth.logout(request)
            redirect_location = settings.LOGOUT_REDIRECT_URL
            try:
                redirect_location = get_location(http_info)
            except: 
                traceback_info = '\n'.join(traceback.format_exception(*(sys.exc_info())))               
                logger.error('Encountered the following error calling get_location(http_info) with an http_info value of %s: %s' % (http_info, traceback_info))
            return HttpResponseRedirect(redirect_location)
    else:
        logger.error('No SAMLResponse or SAMLRequest parameter found')
        return failure_redirect("No SAMLResponse or SAMLRequest parameter found.")
Ejemplo n.º 32
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]})
Ejemplo n.º 33
0
def metadata(request, config_loader_path=None, valid_for=None):
    """Returns an XML with the SAML 2.0 metadata for this
    SP as configured in the settings.py file.
    """
    conf = get_config(config_loader_path, request)
    valid_for = valid_for or get_custom_setting("SAML_VALID_FOR", 24)
    metadata = entity_descriptor(conf, valid_for)

    saml_token = request.GET.get("saml_token")
    if saml_token:
        # inject the saml token to the sp urls
        for descriptor in metadata.spsso_descriptor.assertion_consumer_service:
            descriptor.location += "?saml_token=%s" % saml_token
        for descriptor in metadata.spsso_descriptor.single_logout_service:
            descriptor.location += "?saml_token=%s" % saml_token

    return HttpResponse(content=str(metadata), content_type="text/xml; charset=utf8")
Ejemplo n.º 34
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]})
Ejemplo n.º 35
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')
Ejemplo n.º 36
0
def get_idp_sso_supported_bindings(idp_entity_id=None, config=None):
    """Returns the list of bindings supported by an IDP
    This is not clear in the pysaml2 code, so wrapping it in a util"""
    if config is None:
        # avoid circular import
        from djangosaml2.conf import get_config
        config = get_config()
    # load metadata store from config
    meta = getattr(config, 'metadata', {})
    # if idp is None, assume only one exists so just use that
    if idp_entity_id is None:
        # .keys() returns dict_keys in python3.5+
        idp_entity_id = list(available_idps(config).keys()).pop()
    try:
        return meta.service(idp_entity_id, 'idpsso_descriptor',
                            'single_sign_on_service').keys()
    except UnknownSystemEntity:
        return []
Ejemplo n.º 37
0
    def custom_validation(self, response):
        conf = get_config(None, self.request)

        # Spid and SAML2 additional tests
        accepted_time_diff = conf.accepted_time_diff
        recipient = conf._sp_endpoints['assertion_consumer_service'][0][0]
        authn_context_classref = settings.SPID_AUTH_CONTEXT
        issuer = response.response.issuer
        # in_response_to = todo
        validator = Saml2ResponseValidator(
            authn_response=response.xmlstr,
            recipient=recipient,
            # in_response_to = in_response_to,
            # requester = requester,
            accepted_time_diff=accepted_time_diff,
            authn_context_class_ref=authn_context_classref,
            return_addrs=response.return_addrs)
        validator.run()
Ejemplo n.º 38
0
def login(request,
          config_loader_path=None,
          wayf_template='djangosaml2/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 Redirect protocol binding.
    """
    logger.debug('Login process started')

    came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL)

    if not request.user.is_anonymous():
        logger.debug('User is already logged in')
        return render_to_response(authorization_error_template, {
            'came_from': came_from,
        },
                                  context_instance=RequestContext(request))

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

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

    client = Saml2Client(conf, logger=logger)
    try:
        (session_id, result) = client.authenticate(
            entityid=selected_idp,
            relay_state=came_from,
            binding=BINDING_HTTP_REDIRECT,
        )
    except TypeError, e:
        logger.error('Unable to know which IdP to use')
        return HttpResponse(unicode(e))
Ejemplo n.º 39
0
def metadata_spid(request, config_loader_path=None, valid_for=None):
    """Returns an XML with the SAML 2.0 metadata for this
    SP as configured in the settings.py file.
    """
    conf = get_config(config_loader_path, request)
    metadata = entity_descriptor(conf)

    # this will renumber acs starting from 0 and set index=0 as is_default
    cnt = 0
    for attribute_consuming_service in metadata.spsso_descriptor.attribute_consuming_service:
        attribute_consuming_service.index = str(cnt)
        cnt += 1

    cnt = 0
    for assertion_consumer_service in metadata.spsso_descriptor.assertion_consumer_service:
        assertion_consumer_service.is_default = 'true' if not cnt else ''
        assertion_consumer_service.index = str(cnt)
        cnt += 1

    # nameformat patch... tutto questo non rispecchia gli standard OASIS
    for reqattr in metadata.spsso_descriptor.attribute_consuming_service[0].requested_attribute:
        reqattr.name_format = None #"urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
        # reqattr.is_required = None
        reqattr.friendly_name = None

    # remove unecessary encryption and digest algs
    supported_algs = ['http://www.w3.org/2009/xmldsig11#dsa-sha256',
                      'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256']
    new_list = []
    for alg in metadata.extensions.extension_elements:
        # if alg.namespace != 'urn:oasis:names:tc:SAML:metadata:algsupport': continue
        if alg.attributes.get('Algorithm') in supported_algs:
            new_list.append(alg)
    metadata.extensions.extension_elements = new_list
    # ... Piuttosto non devo specificare gli algoritmi di firma/criptazione...
    metadata.extensions = None

    # attribute consuming service service name patch
    service_name = metadata.spsso_descriptor.attribute_consuming_service[0].service_name[0]
    service_name.lang = 'it'
    service_name.text = conf._sp_name

    return HttpResponse(content=text_type(metadata).encode('utf-8'),
                        content_type="text/xml; charset=utf8")
Ejemplo n.º 40
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'])
Ejemplo n.º 41
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)
    if subject_id is None:
        logger.warning("The session does not contains the subject id for user %s" % request.user)
    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"])
Ejemplo n.º 42
0
def metadata(request, config_loader_path=None, valid_for=None):
    """Returns an XML with the SAML 2.0 metadata for this
    SP as configured in the settings.py file.
    """
    conf = get_config(config_loader_path, request)
    metadata = entity_descriptor(conf)
    if conf.extensions:
        if metadata.extensions is None:
            metadata.extensions = md.Extensions()

        for key, val in conf.extensions.items():
            _ext = do_extensions(key, val)
            if _ext:
                for _e in _ext:
                    metadata.extensions.add_extension_element(_e)

    return HttpResponse(
        content=text_type(metadata).encode('utf-8'),
        content_type="text/xml; charset=utf8",
    )
Ejemplo n.º 43
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'])
Ejemplo n.º 44
0
def login(request,
          config_loader_path=None,
          wayf_template='djangosaml2/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 Redirect protocol binding.
    """
    logger.debug('Login process started')

    came_from = request.GET.get('next', settings.LOGIN_REDIRECT_URL)

    if not request.user.is_anonymous():
        logger.debug('User is already logged in')
        return render_to_response(authorization_error_template, {
                'came_from': came_from,
                }, context_instance=RequestContext(request))

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

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

    client = Saml2Client(conf, logger=logger)
    try:
        (session_id, result) = client.authenticate(
            entityid=selected_idp, relay_state=came_from,
            binding=BINDING_HTTP_REDIRECT,
            )
    except TypeError, e:
        logger.error('Unable to know which IdP to use')
        return HttpResponse(unicode(e))
Ejemplo n.º 45
0
    def custom_validation(self, response):
        conf = get_config(None, self.request)

        # Spid and SAML2 additional tests
        accepted_time_diff = conf.accepted_time_diff
        recipient = conf._sp_endpoints["assertion_consumer_service"][0][0]
        authn_context_classref = settings.SPID_AUTH_CONTEXT

        oq_cache = OutstandingQueriesCache(self.request.saml_session)
        in_response_to = oq_cache.outstanding_queries()
        logger.debug("in_response_to=%r", in_response_to)

        validator = Saml2ResponseValidator(
            authn_response=response.xmlstr,
            recipient=recipient,
            accepted_time_diff=accepted_time_diff,
            in_response_to=in_response_to,
            authn_context_class_ref=authn_context_classref,
            return_addrs=response.return_addrs,
        )
        validator.run()
Ejemplo n.º 46
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))
Ejemplo n.º 47
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')
Ejemplo n.º 48
0
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_compat(url=came_from, allowed_hosts={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 callable_bool(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)
    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 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')
            sigalg = sig_alg_option_map[sig_alg_option] if sign_requests else None
            nsprefix = get_namespace_prefixes()
            session_id, result = client.prepare_for_authenticate(
                entityid=selected_idp, relay_state=came_from,
                binding=binding, sign=False, sigalg=sigalg,
                nsprefix=nsprefix)
        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:
        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(text_type(e))
            session_id, request_xml = client.create_authn_request(
                location,
                binding=binding)
            try:
                if PY3:
                    saml_request = base64.b64encode(binary_type(request_xml, 'UTF-8'))
                else:
                    saml_request = base64.b64encode(binary_type(request_xml))

                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(text_type(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
Ejemplo n.º 49
0
def login(request,
          config_loader_path=None,
          wayf_template='djangosaml2/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 Redirect protocol binding.
    """
    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

    # 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_to_response(authorization_error_template, {
                'came_from': came_from,
                }, context_instance=RequestContext(request))

    selected_idp = request.GET.get('idp', None)
    conf = get_config(config_loader_path, request)
    client = Saml2Client(conf)
    try:
        sid, http_args = client.prepare_for_authenticate(
            entityid=selected_idp, relay_state=came_from,
            binding=BINDING_HTTP_REDIRECT,
            )
    except TypeError as e:
        logger.error('Unable to know which IdP to use')
        raise e

    assert isinstance(sid, str)
    assert len(http_args) == 4
    assert http_args["headers"][0][0] == "Location"
    assert http_args["data"] == []
    location = http_args["headers"][0][1]

    # fix up the redirect url for endpoints that have ? in the link
    split_location = location.split('?SAMLRequest=')
    if split_location and '?' in split_location[0]:
        logger.debug(
            'Redirect URL already has query string, ' +
            'transforming ?SAMLRequest=')
        location = location.replace('?SAMLRequest=', '&SAMLRequest=')

    split_location = location.split('?RelayState=')
    if split_location and '?' in split_location[0]:
        logger.debug(
            'Redirect URL already has query string, ' +
            'transforming ?RelayState=')
        location = location.replace('?RelayState=', '&RelayState=')

    logger.debug('Saving the session_id in the OutstandingQueries cache')
    oq_cache = OutstandingQueriesCache(request.session)
    oq_cache.set(sid, came_from)

    logger.debug('Redirecting the user to the IdP')
    logger.debug('Redirecting to %s' % location)
    return HttpResponseRedirect(location)
Ejemplo n.º 50
0
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 binding (REDIRECT vs. POST).
    # When authn_requests_signed is turned on, HTTP Redirect binding cannot be
    # used the same way as without signatures; proper usage in this case involves
    # stripping out the signature from SAML XML message and creating a new
    # signature, following precise steps defined in the SAML2.0 standard.
    #
    # It is not feasible to implement this since we wouldn't be able to use an
    # external (xmlsec1) library to handle the signatures - more (higher level)
    # context is needed in order to create such signature (like the value of
    # RelayState parameter).
    #
    # Therefore it is much easier to use the HTTP POST binding in this case, as
    # it can relay the whole signed SAML message as is, without the need to
    # manipulate the signature or the XML message itself.
    #
    # Read more in the official SAML2 specs (3.4.4.1):
    # http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
    binding = BINDING_HTTP_POST if getattr(conf, '_sp_authn_requests_signed',
                                           False) else BINDING_HTTP_REDIRECT

    client = Saml2Client(conf)
    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(unicode(e))

    logger.debug('Saving the session_id in the OutstandingQueries cache')
    oq_cache = OutstandingQueriesCache(request.session)
    oq_cache.set(session_id, came_from)

    logger.debug('Redirecting user to the IdP via %s binding.',
                 binding.split(':')[-1])
    if binding == BINDING_HTTP_REDIRECT:
        return HttpResponseRedirect(get_location(result))
    elif binding == BINDING_HTTP_POST:
        if not post_binding_form_template:
            return HttpResponse(result['data'])
        try:
            params = get_hidden_form_inputs(result['data'][3])
            return render(request, post_binding_form_template, {
                'target_url': result['url'],
                'params': params,
            })
        except (DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden):
            raise PermissionDenied
        except TemplateDoesNotExist:
            return HttpResponse(result['data'])
    else:
        raise NotImplementedError('Unsupported binding: %s', binding)
Ejemplo n.º 51
0
    def test_custom_conf_loader(self):
        config_loader_path = 'djangosaml2.tests.test_config_loader'
        request = RequestFactory().get('/bar/foo')
        conf = get_config(config_loader_path, request)

        self.assertEquals(conf.entityid, 'testentity')
Ejemplo n.º 52
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)
Ejemplo n.º 53
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()

    # process the authentication response
    response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_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
    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
    logger.debug('Redirecting to the RelayState: %s', relay_state)
    return HttpResponseRedirect(relay_state)
Ejemplo n.º 54
0
def assertion_consumer_service_view(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
    """
    logger.debug('Assertion Consumer Service started')

    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

    try:
        response = client.response(post, outstanding_queries)
    except Exception as e:
        logger.error('Error while authenticating. %s' % e)
        return HttpResponseRedirect('/saml2/login_error')
    if response is None:
        logger.error('SAML response is None')
        return HttpResponse("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')
    try:
        user = auth.authenticate(session_info=session_info,
                             attribute_mapping=attribute_mapping,
                             create_unknown_user=create_unknown_user)
    except Exception as e:
        logger.error('Error while authenticating. %s' % e)
        return HttpResponseRedirect('/saml2/login_error')
    if user is None:
        logger.error('The user is None')
        return HttpResponseRedirect('/saml2/login_error')
        #return HttpResponse("There were problems trying to authenticate the user")

    auth.login(request, user)

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

    _set_saml2_auth_used(request.session, True)
    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', '/login')
    relay_state = '/login'
    logger.debug('Redirecting to the RelayState: ' + relay_state)
    return HttpResponseRedirect(relay_state)
Ejemplo n.º 55
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 StatusNoAuthnContext:
        logger.warning("Missing Authentication Context from 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)
Ejemplo n.º 56
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)
                        )

    if 'SAMLResponse' in request.GET:  # we started the logout
        logger.debug('Receiving a logout response from the IdP')
        response = client.parse_logout_request_response(request.GET["SAMLResponse"], binding=BINDING_HTTP_REDIRECT)
        action = client.handle_logout_response(response)

        state.sync()
        if action and action[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 = client.handle_logout_request(request.GET["SAMLRequest"], subject_id, binding=BINDING_HTTP_REDIRECT)

            state.sync()
            if response['headers']:
                headers = response['headers']
                auth.logout(request)
                assert headers[0][0] == 'Location'
                url = headers[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')
Ejemplo n.º 57
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')
    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)
    # Handle SP that should initiate a global logout        
    if not request.user.is_authenticated():
        logger.debug('User is not authenticated. Redirecting to settings.LOGOUT_REDIRECT_URL')
        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 contains the subject id for user %s' % request.user)

    try:
        result = client.global_logout(subject_id)
    except Exception as e:
        traceback_info = '\n'.join(traceback.format_exception(*(sys.exc_info())))
        logger.warning('Encountered the following error when calling Saml2Client.global_logout(): %s' % traceback_info)
        return failure_redirect('Encountered the following error when trying to do a global logout: %s.' % e)
        
    state.sync()

    if not result:
        logger.error("Looks like the user %s is not logged in any IdP/AA" % subject_id)
        return failure_redirect("You are not logged into 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'])
                logger.debug("Body = %s" % body)                
                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 failure_redirect("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 failure_redirect("Logout Binding not supported")
Ejemplo n.º 58
0
    def post(self, request):
        if not self.request.user.is_anonymous:
            error_message = _('This endpoint is for anonymous users only.')
            return JsonResponse({'error_message': error_message}, status_code=400)

        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        idp = serializer.validated_data.get('idp')

        conf = get_config(request=request)

        # ensure our selected binding is supported by the IDP
        supported_bindings = utils.get_idp_sso_supported_bindings(idp, config=conf)
        default_binding = settings.WALDUR_AUTH_SAML2.get('DEFAULT_BINDING')

        if default_binding in supported_bindings:
            binding = default_binding
        elif BINDING_HTTP_POST in supported_bindings:
            binding = BINDING_HTTP_POST
        elif BINDING_HTTP_REDIRECT in supported_bindings:
            binding = BINDING_HTTP_REDIRECT
        else:
            error_message = _('Identity provider does not support available bindings.')
            return JsonResponse({'error_message': error_message}, status_code=400)

        client = Saml2Client(conf)

        kwargs = {}
        sign_requests = getattr(conf, '_sp_authn_requests_signed', False)
        if sign_requests:
            signature_algorithm = settings.WALDUR_AUTH_SAML2.get('signature_algorithm') or SIG_RSA_SHA1
            digest_algorithm = settings.WALDUR_AUTH_SAML2.get('digest_algorithm') or DIGEST_SHA1

            kwargs['sign'] = True
            kwargs['sigalg'] = signature_algorithm
            kwargs['sign_alg'] = signature_algorithm
            kwargs['digest_alg'] = digest_algorithm

        nameid_format = settings.WALDUR_AUTH_SAML2.get('nameid_format')
        if nameid_format or nameid_format == "":  # "" is a valid setting in pysaml2
            kwargs['nameid_format'] = nameid_format

        if binding == BINDING_HTTP_REDIRECT:
            session_id, result = client.prepare_for_authenticate(
                entityid=idp, binding=binding, **kwargs)

            data = {
                'binding': 'redirect',
                'url': get_location(result),
            }
        elif binding == BINDING_HTTP_POST:
            try:
                location = client.sso_location(idp, binding)
            except TypeError:
                error_message = _('Invalid identity provider specified.')
                return JsonResponse({'error_message': error_message}, status_code=400)

            session_id, request_xml = client.create_authn_request(location, binding=binding, **kwargs)
            data = {
                'binding': 'post',
                'url': location,
                'request': base64.b64encode(request_xml.encode('UTF-8')),
            }

        # save session_id
        oq_cache = OutstandingQueriesCache(request.session)
        oq_cache.set(session_id, '')

        return JsonResponse(data)
Ejemplo n.º 59
0
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

    # 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_to_response(authorization_error_template, {
                    'came_from': came_from,
                    }, context_instance=RequestContext(request))

    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_to_response(wayf_template, {
                'available_idps': idps.items(),
                'came_from': came_from,
                }, context_instance=RequestContext(request))

    # Choose binding (REDIRECT vs. POST).
    # When authn_requests_signed is turned on, HTTP Redirect binding cannot be
    # used the same way as without signatures; proper usage in this case involves
    # stripping out the signature from SAML XML message and creating a new
    # signature, following precise steps defined in the SAML2.0 standard.
    #
    # It is not feasible to implement this since we wouldn't be able to use an
    # external (xmlsec1) library to handle the signatures - more (higher level)
    # context is needed in order to create such signature (like the value of
    # RelayState parameter).
    #
    # Therefore it is much easier to use the HTTP POST binding in this case, as
    # it can relay the whole signed SAML message as is, without the need to
    # manipulate the signature or the XML message itself.
    #
    # Read more in the official SAML2 specs (3.4.4.1):
    # http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
    binding = BINDING_HTTP_POST if getattr(conf, '_sp_authn_requests_signed', False) else BINDING_HTTP_REDIRECT

    client = Saml2Client(conf)
    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(unicode(e))

    logger.debug('Saving the session_id in the OutstandingQueries cache')
    oq_cache = OutstandingQueriesCache(request.session)
    oq_cache.set(session_id, came_from)

    logger.debug('Redirecting user to the IdP via %s binding.', binding.split(':')[-1])
    if binding == BINDING_HTTP_REDIRECT:
        return HttpResponseRedirect(get_location(result))
    elif binding == BINDING_HTTP_POST:
        if not post_binding_form_template:
            return HttpResponse(result['data'])
        try:
            params = get_hidden_form_inputs(result['data'][3])
            return render_to_response(post_binding_form_template, {
                    'target_url': result['url'],
                    'params': params,
                    }, context_instance=RequestContext(request))
        except TemplateDoesNotExist:
            return HttpResponse(result['data'])
    else:
        raise NotImplementedError('Unsupported binding: %s', binding)