Esempio n. 1
0
    def __init__(self, config_module, config=None):
        """
        :type config_module: str
        :type config: {dict}

        :param config_module: Path to a file containing the SP SAML configuration.
        :param config: SP SAML configuration.
        """
        if config is None:
            config = config_factory('sp', config_module)
        Saml2Client.__init__(self, config)
Esempio n. 2
0
    def handle_discovery_response(self, request):
        """Handle SAML Discovery Service response. This method is basically
        a wrapper around `authenticate` with a little extra logic for getting
        the `entityID` out of the request and the next_url and binding that was
        previously submitted to `authenticate` from the user's session.

        Args:
            request (Request): Flask request object for this HTTP transaction.

        Returns:
            Flask Response object to return to user containing either
                HTTP_REDIRECT or HTTP_POST SAML message.

        Raises:
            AuthException: when unable to locate valid IdP.
            BadRequest: when invalid result returned from SAML client.
        """
        session_id = request.args.get('session_id')
        next_url = "/"

        # Retrieve cache. Get `next_url` from cache.
        outstanding_queries_cache = \
            AuthDictCache(session, '_saml_outstanding_queries')
        if session_id in outstanding_queries_cache.keys():
            next_url = outstanding_queries_cache[session_id]
            del outstanding_queries_cache[session_id]
        outstanding_queries_cache.sync()
        # Get the selected IdP from the Discovery Service response.
        selected_idp = Saml2Client.parse_discovery_service_response(
            query=request.query_string)
        return self.authenticate(next_url=next_url, selected_idp=selected_idp)
Esempio n. 3
0
    def _handle_discovery_request(self):
        """Handle SAML Discovery Service request. This method is called
        internally by the `authenticate` method when multiple acceptable IdPs
        are detected.

        Returns:
            Tuple containing session Id and Flask Response object to return to
                user containing either HTTP_REDIRECT to configured Discovery
                Service end point.

        Raises:
            AuthException: when unable to find discovery response end point.
        """
        session_id = sid()
        try:
            return_url = self._config.getattr(
                'endpoints', 'sp')['discovery_response'][0][0]
        except KeyError:
            raise AuthException(
                "Multiple IdPs configured with no configured Discovery" + \
                " response end point.")
        return_url += "?session_id=%s" % session_id
        disco_url = Saml2Client.create_discovery_service_request(
            self.discovery_service_end_point,
            self._config.entityid, **{'return': return_url})
        LOGGER.debug("Redirect to Discovery Service %s", disco_url)
        return (session_id, make_response('', 302, {'Location': disco_url}))
Esempio n. 4
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)
Esempio n. 5
0
def saml_client_for(idp_name):
    """
    Given the name of an IdP, return a configuation.
    The configuration is a hash for use by saml2.config.Config
    """

    if idp_name not in current_app.config['SAML_IDP_SETTINGS']:
        raise Exception(
            f'Settings for IDP "{idp_name}" not found on SAML_IDP_SETTINGS.')

    acs_url = url_for('auth.saml_sso', idp_name=idp_name, _external=True)

    https_acs_url = url_for('auth.saml_sso',
                            idp_name=idp_name,
                            _external=True,
                            _scheme='https')

    # SAML metadata changes very rarely. On a production system,
    # this data should be cached as approprate for your production system.
    rv = requests.get(
        current_app.config['SAML_IDP_SETTINGS'][idp_name]['metadata_url'])

    current_app.logger.debug('rv.rext: %s', rv.text)

    entityid = current_app.config['SAML_IDP_SETTINGS'][idp_name].get(
        'entityid', acs_url)

    settings = {
        'entityid': entityid,
        'metadata': {
            'inline': [rv.text],
        },
        'service': {
            'sp': {
                'endpoints': {
                    'assertion_consumer_service':
                    [(acs_url, BINDING_HTTP_REDIRECT),
                     (acs_url, BINDING_HTTP_POST),
                     (https_acs_url, BINDING_HTTP_REDIRECT),
                     (https_acs_url, BINDING_HTTP_POST)],
                },
                # Don't verify that the incoming requests originate from us via
                # the built-in cache for authn request ids in pysaml2
                'allow_unsolicited': True,
                # Don't sign authn requests, since signed requests only make
                # sense in a situation where you control both the SP and IdP
                'authn_requests_signed': False,
                'logout_requests_signed': True,
                'want_assertions_signed': True,
                'want_response_signed': False
            }
        }
    }

    current_app.logger.info('settings: %s', settings)

    saml2_config = Saml2Config()
    saml2_config.load(settings)
    saml2_config.allow_unknown_attributes = True

    saml2_client = Saml2Client(config=saml2_config)

    return saml2_client
Esempio n. 6
0
    def setup_class(self):
        self.server = Server("idp_conf")

        conf = config.SPConfig()
        conf.load_file("server_conf")
        self.client = Saml2Client(conf)
Esempio n. 7
0
def test_complete_flow():
    client = ecp_client.Client("user",
                               "password",
                               metadata_file=full_path("idp_all.xml"))

    sp = Saml2Client(config_file=dotname("servera_conf"))

    with closing(Server(config_file=dotname("idp_all_conf"))) as idp:
        IDP_ENTITY_ID = idp.config.entityid
        #SP_ENTITY_ID = sp.config.entityid

        # ------------ @Client -----------------------------

        headers = client.add_paos_headers([])

        assert len(headers) == 2

        # ------------ @SP -----------------------------

        response = DummyResponse(set_list2dict(headers))

        assert sp.can_handle_ecp_response(response)

        sid, message = sp.create_ecp_authn_request(IDP_ENTITY_ID,
                                                   relay_state="XYZ")

        # ------------ @Client -----------------------------

        respdict = client.parse_soap_message(message)

        cargs = client.parse_sp_ecp_response(respdict)

        assert isinstance(respdict["body"], AuthnRequest)
        assert len(respdict["header"]) == 2
        item0 = respdict["header"][0]
        assert isinstance(item0, Request) or isinstance(item0, RelayState)

        destination = respdict["body"].destination

        ht_args = client.apply_binding(BINDING_SOAP, respdict["body"],
                                       destination)

        # Time to send to the IDP
        # ----------- @IDP -------------------------------

        req = idp.parse_authn_request(ht_args["data"], BINDING_SOAP)

        assert isinstance(req.message, AuthnRequest)

        # create Response and return in the SOAP response
        sp_entity_id = req.sender()

        name_id = idp.ident.transient_nameid("id12", sp.config.entityid)
        binding, destination = idp.pick_binding("assertion_consumer_service",
                                                [BINDING_PAOS],
                                                entity_id=sp_entity_id)

        resp = idp.create_ecp_authn_request_response(destination, {
            "eduPersonEntitlement": "Short stop",
            "surName": "Jeter",
            "givenName": "Derek",
            "mail": "*****@*****.**",
            "title": "The man"
        },
                                                     req.message.id,
                                                     destination,
                                                     sp_entity_id,
                                                     name_id=name_id,
                                                     authn=AUTHN)

        # ------------ @Client -----------------------------
        # The client got the response from the IDP repackage and send it to the SP

        respdict = client.parse_soap_message(resp)
        idp_response = respdict["body"]

        assert isinstance(idp_response, Response)
        assert len(respdict["header"]) == 1

        _ecp_response = None
        for item in respdict["header"]:
            if item.c_tag == "Response" and item.c_namespace == ecp_prof.NAMESPACE:
                _ecp_response = item

        #_acs_url = _ecp_response.assertion_consumer_service_url

        # done phase2 at the client

        ht_args = client.use_soap(idp_response, cargs["rc_url"],
                                  [cargs["relay_state"]])

        print(ht_args)

        # ------------ @SP -----------------------------

        respdict = sp.unpack_soap_message(ht_args["data"])

        # verify the relay_state

        for header in respdict["header"]:
            inst = create_class_from_xml_string(RelayState, header)
            if isinstance(inst, RelayState):
                assert inst.text == "XYZ"

        # parse the response

        resp = sp.parse_authn_request_response(respdict["body"], None,
                                               {sid: "/"})

        print(resp.response)

        assert resp.response.destination == "http://lingon.catalogix.se:8087/paos"
        assert resp.response.status.status_code.value == STATUS_SUCCESS
Esempio n. 8
0
    if _args.service_conf_module:
        service_conf = importlib.import_module(_args.service_conf_module)
    else:
        import service_conf

    HOST = service_conf.HOST
    PORT = service_conf.PORT
    # ------- HTTPS -------
    # These should point to relevant files
    SERVER_CERT = service_conf.SERVER_CERT
    SERVER_KEY = service_conf.SERVER_KEY
    # This is of course the certificate chain for the CA that signed
    # your cert and all the way up to the top
    CERT_CHAIN = service_conf.CERT_CHAIN

    SP = Saml2Client(config_file="%s" % CNFBASE)

    POLICY = service_conf.POLICY

    add_urls()
    sign_alg = None
    digest_alg = None
    try:
        sign_alg = service_conf.SIGN_ALG
    except:
        pass
    try:
        digest_alg = service_conf.DIGEST_ALG
    except:
        pass
    ds.DefaultSignature(sign_alg, digest_alg)
Esempio n. 9
0
 def get_saml2_client(self):
     return Saml2Client(config=self.get_saml2_config())
Esempio n. 10
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')
Esempio n. 11
0
from saml2.config import SPConfig
from saml2.response import AuthnResponse
from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST
from saml2.client import Saml2Client

# OutStanding Queries
# outstanding = {'id-R3qGBIK1FKbybkEOo': '/', 'id-vV5JVaBZCuC2LHP9Y': '/', 'id-TH9lfrLJL4KtNuEZJ': '/', 'id-KeYf8iMkonCWaqGrd': '/', 'id-S8lzm7lkEYIwokDVZ': '/', 'id-1naCBqIuGqm31mFnC': '/', 'id-D5bhbXLDxt6nS2QtZ': '/', 'id-UCjbQ7AS1nGG5wSN5': '/', 'id-EdrCM5hBIDix23Bf5': '/', 'id-p3yvaSmx6TJPZ0qK7': '/', 'id-DgwqMaGwOJYRxnzQe': '/'}

outstanding = None
outstanding_certs = None
conv_info = None

conf = SPConfig()

conf.load(copy.deepcopy(SAML_CONFIG))
client = Saml2Client(conf)

# client arguments
selected_idp = None
came_from = '/'
# conf['sp']['authn_requests_signed'] determines if saml2.BINDING_HTTP_POST or saml2.BINDING_HTTP_REDIRECT
binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'  # saml2.BINDING_HTTP_REDIRECT
sign = False
sigalg = None
nsprefix = {
    'ds': 'http://www.w3.org/2000/09/xmldsig#',
    'md': 'urn:oasis:names:tc:SAML:2.0:metadata',
    'samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
    'xenc': 'http://www.w3.org/2001/04/xmlenc#',
    'saml': 'urn:oasis:names:tc:SAML:2.0:assertion'
}
Esempio n. 12
0
    def __init__(self, hs):
        Resource.__init__(self)

        self._saml_client = Saml2Client(hs.config.saml2_sp_config)
        self._sso_auth_handler = SSOAuthHandler(hs)
Esempio n. 13
0
    def authenticate(self, next_url='/', binding=BINDING_HTTP_REDIRECT):
        """Start SAML Authentication login process.

        Args:
            next_url (string): HTTP URL to return user to when authentication
                is complete.
            binding (binding): Saml2 binding method to use for request,
                default BINDING_HTTP_REDIRECT (don't change til HTTP_POST
                support is complete in pysaml2.

        Returns:
            Flask Response object to return to user containing either
                HTTP_REDIRECT or HTTP_POST SAML message.

        Raises:
            AuthException: when unable to locate valid IdP.
            BadRequest: when invalid result returned from SAML client.
        """
        # find configured for IdP for requested binding method
        idp_entityid = ''
        idps = self._config.idps().keys()
        for idp in idps:
            if self._config.single_sign_on_services(idp, binding) != []:
                idp_entityid = idp
                break
        if idp_entityid == '':
            raise AuthException('Unable to locate valid IdP for this request')
        # fail if signing requested but no private key configured
        if self._config.authn_requests_signed == 'true':
            if not self._config.key_file \
                or not os.path.exists(self._config.key_file):
                raise AuthException(
                    'Signature requested for this Saml authentication request,'
                    ' but no private key file configured')

        LOGGER.debug('Connecting to Identity Provider %s' % idp_entityid)
        # retrieve cache
        outstanding_queries_cache = \
            AuthDictCache(session, '_saml_outstanding_queries')

        LOGGER.debug('Outstanding queries cache %s' % (
            outstanding_queries_cache))

        # make pysaml2 call to authenticate
        client = Saml2Client(self._config, logger=LOGGER)
        (session_id, result) = client.authenticate(
            entityid=idp_entityid,
            relay_state=next_url,
            binding=binding)

        # The psaml2 source for this method indicates that BINDING_HTTP_POST
        # should not be used right now to authenticate. Regardless, we'll
        # check for it and act accordingly.

        if binding == BINDING_HTTP_REDIRECT:
            LOGGER.debug('Redirect to Identity Provider %s ( %s )' % (
                idp_entityid, result))
            response = make_response('', 302, dict([result]))
        elif binding == BINDING_HTTP_POST:
            LOGGER.warn('POST binding used to authenticate is not currently'
                ' supported by pysaml2 release version. Fix in place in repo.')
            LOGGER.debug('Post to Identity Provider %s ( %s )' % (
                idp_entityid, result))
            response = make_response('\n'.join(result), 200)
        else:
            raise BadRequest('Invalid result returned from SAML client')

        LOGGER.debug(
            'Saving session_id ( %s ) in outstanding queries' % session_id)
        # cache the outstanding query
        outstanding_queries_cache.update({session_id: next_url})
        outstanding_queries_cache.sync()

        LOGGER.debug('Outstanding queries cache %s' % (
            session['_saml_outstanding_queries']))

        return response
Esempio n. 14
0
    def handle_assertion(self, request):
        """Handle SAML Authentication login assertion (POST).

        Args:
            request (Request): Flask request object for this HTTP transaction.

        Returns:
            User Id (string), User attributes (dict), Redirect Flask response
                object to return user to now that authentication is complete.

        Raises:
            BadRequest: when error with SAML response from Identity Provider.
            AuthException: when unable to locate uid attribute in response.
        """
        if not request.form.get('SAMLResponse'):
            raise BadRequest('SAMLResponse missing from POST')
        # retrieve cache
        outstanding_queries_cache = \
            AuthDictCache(session, '_saml_outstanding_queries')
        identity_cache = IdentityCache(session, '_saml_identity')

        LOGGER.debug('Outstanding queries cache %s' % (
            outstanding_queries_cache))
        LOGGER.debug('Identity cache %s' % identity_cache)

        # use pysaml2 to process the SAML authentication response
        client = Saml2Client(self._config, identity_cache=identity_cache,
            logger=LOGGER)
        saml_response = client.response(
            dict(SAMLResponse=request.form['SAMLResponse']),
            outstanding_queries_cache)
        if saml_response is None:
            raise BadRequest('SAML response is invalid')
        # make sure outstanding query cache is cleared for this session_id
        session_id = saml_response.session_id()
        if session_id in outstanding_queries_cache.keys():
            del outstanding_queries_cache[session_id]
        outstanding_queries_cache.sync()
        # retrieve session_info
        saml_session_info = saml_response.session_info()
        LOGGER.debug('SAML Session Info ( %s )' % saml_session_info)
        # retrieve user data via API
        try:
            if self.attribute_map.get('uid', 'name_id') == 'name_id':
                user_id = saml_session_info.get('name_id')
            else:
                user_id = saml_session_info['ava'] \
                    .get(self.attribute_map.get('uid'))[0]
        except:
            raise AuthException('Unable to find "%s" attribute in response' % (
                self.attribute_map.get('uid', 'name_id')))
        # Future: map attributes to user info
        user_attributes = dict()
        # set subject Id in cache to retrieved name_id
        session['_saml_subject_id'] = saml_session_info.get('name_id')

        LOGGER.debug('Outstanding queries cache %s' % (
            session['_saml_outstanding_queries']))
        LOGGER.debug('Identity cache %s' % session['_saml_identity'])
        LOGGER.debug('Subject Id %s' % session['_saml_subject_id'])

        relay_state = request.form.get('RelayState', '/')
        LOGGER.debug('Returning redirect to %s' % relay_state)
        return user_id, user_attributes, redirect(relay_state)
Esempio n. 15
0
 def _get_client_for_provider(self, base_url=None):
     spConfig = self._get_config_for_provider(base_url)
     saml_client = Saml2Client(config=spConfig)
     return saml_client
from saml2.sigver import RSA_SHA1
from saml2.server import Server
from saml2 import BINDING_HTTP_REDIRECT
from saml2.client import Saml2Client
from saml2.config import SPConfig
from urlparse import parse_qs

from pathutils import dotname

__author__ = 'rolandh'

idp = Server(config_file=dotname("idp_all_conf"))

conf = SPConfig()
conf.load_file(dotname("servera_conf"))
sp = Saml2Client(conf)


def test():
    srvs = sp.metadata.single_sign_on_service(idp.config.entityid,
                                              BINDING_HTTP_REDIRECT)

    destination = srvs[0]["location"]
    req_id, req = sp.create_authn_request(destination, id="id1")

    try:
        key = sp.sec.key
    except AttributeError:
        key = import_rsa_key_from_file(sp.sec.key_file)

    info = http_redirect_message(req, destination, relay_state="RS",
Esempio n. 17
0
def get_saml_client(org):
    """
    Return SAML configuration.

    The configuration is a hash for use by saml2.config.Config
    """

    saml_type = org.get_setting("auth_saml_type")
    entity_id = org.get_setting("auth_saml_entity_id")
    sso_url = org.get_setting("auth_saml_sso_url")
    x509_cert = org.get_setting("auth_saml_x509_cert")
    metadata_url = org.get_setting("auth_saml_metadata_url")
    sp_settings = org.get_setting("auth_saml_sp_settings")

    if settings.SAML_SCHEME_OVERRIDE:
        acs_url = url_for(
            "saml_auth.idp_initiated",
            org_slug=org.slug,
            _external=True,
            _scheme=settings.SAML_SCHEME_OVERRIDE,
        )
    else:
        acs_url = url_for("saml_auth.idp_initiated", org_slug=org.slug, _external=True)

    saml_settings = {
        "metadata": {"remote": [{"url": metadata_url}]},
        "service": {
            "sp": {
                "endpoints": {
                    "assertion_consumer_service": [
                        (acs_url, BINDING_HTTP_REDIRECT),
                        (acs_url, BINDING_HTTP_POST),
                    ]
                },
                # Don't verify that the incoming requests originate from us via
                # the built-in cache for authn request ids in pysaml2
                "allow_unsolicited": True,
                # Don't sign authn requests, since signed requests only make
                # sense in a situation where you control both the SP and IdP
                "authn_requests_signed": False,
                "logout_requests_signed": True,
                "want_assertions_signed": True,
                "want_response_signed": False,
            }
        },
    }

    if settings.SAML_ENCRYPTION_ENABLED:
        encryption_dict = {
            "xmlsec_binary": get_xmlsec_binary(),
            "encryption_keypairs": [
                {
                    "key_file": settings.SAML_ENCRYPTION_PEM_PATH,
                    "cert_file": settings.SAML_ENCRYPTION_CERT_PATH,
                }
            ],
        }
        saml_settings.update(encryption_dict)

    if saml_type is not None and saml_type == "static":
        metadata_inline = mustache_render(
            inline_metadata_template,
            entity_id=entity_id,
            x509_cert=x509_cert,
            sso_url=sso_url,
        )

        saml_settings["metadata"] = {"inline": [metadata_inline]}

    if acs_url is not None and acs_url != "":
        saml_settings["entityid"] = acs_url

    if sp_settings:
        import json
        saml_settings["service"]["sp"].update(json.loads(sp_settings))

    sp_config = Saml2Config()
    sp_config.load(saml_settings)
    sp_config.allow_unknown_attributes = True
    saml_client = Saml2Client(config=sp_config)

    return saml_client
Esempio n. 18
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 a binding to try first
    sign_requests = getattr(conf, '_sp_authn_requests_signed', False)
    binding = BINDING_HTTP_POST if sign_requests else BINDING_HTTP_REDIRECT
    logger.debug('Trying binding %s for IDP %s', binding, selected_idp)

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

    client = Saml2Client(conf)
    http_response = None

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

    # success, so save the session ID and return our response
    logger.debug('Saving the session_id in the OutstandingQueries cache')
    oq_cache = OutstandingQueriesCache(request.session)
    oq_cache.set(session_id, came_from)
    return http_response
Esempio n. 19
0
from saml2 import BINDING_HTTP_REDIRECT
from saml2.client import Saml2Client
from saml2.server import Server
from saml2.saml import AUTHN_PASSWORD

__author__ = 'rolandh'

IDENTITY = {
    "eduPersonAffiliation": ["staff", "member"],
    "surName": ["Jeter"],
    "givenName": ["Derek"],
    "mail": ["*****@*****.**"],
    "title": ["shortstop"]
}

sp = Saml2Client(config_file="servera_conf")
idp = Server(config_file="idp_all_conf")

dest = sp._sso_location(entityid="urn:mace:example.com:saml:roland:idp",
                        binding=BINDING_HTTP_REDIRECT)

authn_request = sp.create_authn_request(destination=dest)
htargs = sp.apply_binding(BINDING_HTTP_REDIRECT, "%s" % authn_request, dest,
                          "abcd")

_dict = parse_qs(htargs["headers"][0][1].split('?')[1])


def main():
    for i in range(1000):
        req = idp.parse_authn_request(_dict["SAMLRequest"][0],
Esempio n. 20
0
 def __init__(self, config_module):
     Saml2Client.__init__(self, config_factory('sp', config_module))
Esempio n. 21
0
File: util.py Progetto: SUNET/SATOSA
 def __init__(self, config):
     """
     :type config: {dict}
     :param config: SP SAML configuration.
     """
     Saml2Client.__init__(self, config)
Esempio n. 22
0
 def setup_class(self):
     conf = config.SPConfig()
     conf.load_file("server_conf")
     vo_name = conf.vorg.keys()[0]
     self.sp = Saml2Client(conf, virtual_organization=vo_name)
     add_derek_info(self.sp)
Esempio n. 23
0
File: sp.py Progetto: rohe/actester
                         dest='debug',
                         action='store_true',
                         help="Print debug information")
    _parser.add_argument('-D',
                         dest='discosrv',
                         help="Which disco server to use")
    _parser.add_argument('-A',
                         dest='authncontext',
                         help="An ini file containing all the Authentication "
                         "Context definitions")
    _parser.add_argument('-s', dest='seed', help="Cookie seed")
    _parser.add_argument("config", help="SAML client config")

    _args = _parser.parse_args()

    SP = Saml2Client(config_file="%s" % _args.config)

    if _args.discosrv:
        ARGS["discosrv"] = _args.discosrv

    if _args.authncontext:
        cnf = ConfigParser.ConfigParser()
        cnf.read(_args.authncontext)
        ARGS["authncontext"] = []
        for section in cnf.sections():
            name = cnf.get(section, "name")
            url = cnf.get(section, "url")
            ARGS["authncontext"].append((name, url))
        ARGS["authncontext"].sort()

    CACHE = Cache()
Esempio n. 24
0
File: util.py Progetto: trsau/SATOSA
 def __init__(self, config):
     """
     :type config: {dict}
     :param config: SP SAML configuration.
     """
     Saml2Client.__init__(self, config)
Esempio n. 25
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', ),
                'eduPersonScopedAffiliation':
                ('_process_saml2_affiliations', ),
            },
        )
        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()

        try:
            user = auth.authenticate(
                request=
                request,  # AxesBackend requires request for authentication
                session_info=session_info,
                attribute_mapping=attribute_mapping,
                create_unknown_user=create_unknown_user,
            )
        except ValidationError as e:
            return login_failed(e.message)
        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')
Esempio n. 26
0
def test_artifact_flow():
    #SP = 'urn:mace:example.com:saml:roland:sp'
    sp = Saml2Client(config_file="servera_conf")
    idp = Server(config_file="idp_all_conf")

    # original request

    binding, destination = sp.pick_binding("single_sign_on_service",
                                           entity_id=idp.config.entityid)
    relay_state = "RS0"
    req = sp.create_authn_request(destination, id="id1")

    artifact = sp.use_artifact(req, 1)

    binding, destination = sp.pick_binding("single_sign_on_service",
                                           [BINDING_HTTP_ARTIFACT],
                                           entity_id=idp.config.entityid)

    hinfo = sp.apply_binding(binding, "%s" % artifact, destination, relay_state)

    # ========== @IDP ============

    artifact2 = get_msg(hinfo, binding)

    assert artifact == artifact2

    # The IDP now wants to replace the artifact with the real request

    destination = idp.artifact2destination(artifact2, "spsso")

    msg = idp.create_artifact_resolve(artifact2, destination, sid())

    hinfo = idp.use_soap(msg, destination, None, False)

    # ======== @SP ==========

    msg = get_msg(hinfo, BINDING_SOAP)

    ar = sp.parse_artifact_resolve(msg)

    assert ar.artifact.text == artifact

    # The SP picks the request out of the repository with the artifact as the key
    oreq = sp.artifact[ar.artifact.text]
    # Should be the same as req above

    # Returns the information over the existing SOAP connection so
    # no transport information needed

    msg = sp.create_artifact_response(ar, ar.artifact.text)
    hinfo = sp.use_soap(msg, destination)

    # ========== @IDP ============

    msg = get_msg(hinfo, BINDING_SOAP)

    # The IDP untangles the request from the artifact resolve response
    spreq = idp.parse_artifact_resolve_response(msg)

    # should be the same as req above

    assert spreq.id == req.id

    # That was one way, the Request from the SP
    # ---------------------------------------------#
    # Now for the other, the response from the IDP

    name_id = idp.ident.transient_nameid(sp.config.entityid, "derek")

    resp_args = idp.response_args(spreq, [BINDING_HTTP_POST])

    response = idp.create_authn_response({"eduPersonEntitlement": "Short stop",
                                          "surName": "Jeter", "givenName": "Derek",
                                          "mail": "*****@*****.**",
                                          "title": "The man"},
                                         name_id=name_id,
                                         authn=AUTHN,
                                         **resp_args)

    print response

    # with the response in hand create an artifact

    artifact = idp.use_artifact(response, 1)

    binding, destination = sp.pick_binding("single_sign_on_service",
                                           [BINDING_HTTP_ARTIFACT],
                                           entity_id=idp.config.entityid)

    hinfo = sp.apply_binding(binding, "%s" % artifact, destination, relay_state,
                             response=True)

    # ========== SP =========

    artifact3 = get_msg(hinfo, binding)

    assert artifact == artifact3

    destination = sp.artifact2destination(artifact3, "idpsso")

    # Got an artifact want to replace it with the real message
    msg = sp.create_artifact_resolve(artifact3, destination, sid())

    print msg

    hinfo = sp.use_soap(msg, destination, None, False)

    # ======== IDP ==========

    msg = get_msg(hinfo, BINDING_SOAP)

    ar = idp.parse_artifact_resolve(msg)

    print ar

    assert ar.artifact.text == artifact3

    # The IDP retrieves the response from the database using the artifact as the key
    #oreq = idp.artifact[ar.artifact.text]

    binding, destination = idp.pick_binding("artifact_resolution_service",
                                            entity_id=sp.config.entityid)

    resp = idp.create_artifact_response(ar, ar.artifact.text)
    hinfo = idp.use_soap(resp, destination)

    # ========== SP ============

    msg = get_msg(hinfo, BINDING_SOAP)
    sp_resp = sp.parse_artifact_resolve_response(msg)

    assert sp_resp.id == response.id
Esempio n. 27
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=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=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['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=400)

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

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

        return JsonResponse(data)
Esempio n. 28
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)

    # 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))

    client = Saml2Client(conf)
    try:
        (session_id, result) = client.prepare_for_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))
Esempio n. 29
0
def logout_service():
    """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')

    state = StateCache(session)
    identity = IdentityCache(session)
    client = Saml2Client(current_app.saml2_config,
                         state_cache=state,
                         identity_cache=identity)

    logout_redirect_url = current_app.config.get('SAML2_LOGOUT_REDIRECT_URL')
    next_page = session.get('next', logout_redirect_url)
    next_page = request.args.get('next', next_page)
    next_page = request.form.get('RelayState', next_page)

    if 'SAMLResponse' in request.form: # we started the logout
        logger.debug('Receiving a logout response from the IdP')
        response = client.parse_logout_request_response(
            request.form['SAMLResponse'],
            BINDING_HTTP_REDIRECT
        )
        state.sync()
        if response and response.status_ok():
            session.clear()
            return redirect(next_page)
        else:
            logger.error('Unknown error during the logout')
            abort(400)

    # logout started by the IdP
    elif 'SAMLRequest' in request.form:
        logger.debug('Receiving a logout request from the IdP')
        subject_id = _get_name_id(session)
        if subject_id is None:
            logger.warning(
                'The session does not contain the subject id for user {0} '
                'Performing local logout'.format(
                    session['eduPersonPrincipalName']
                )
            )
            session.clear()
            return redirect(next_page)
        else:
            http_info = client.handle_logout_request(
                request.form['SAMLRequest'],
                subject_id,
                BINDING_HTTP_REDIRECT,
                relay_state=request.form['RelayState']
            )
            state.sync()
            location = get_location(http_info)
            session.clear()
            return redirect(location)
    logger.error('No SAMLResponse or SAMLRequest parameter found')
    abort(400)
Esempio n. 30
0
def test_flow():
    sp = Saml2Client(config_file="servera_conf")

    with closing(Server(config_file="idp_all_conf")) as idp:
        relay_state = "FOO"
        # -- dummy request ---
        orig_req = AuthnRequest(issuer=sp._issuer(),
                                name_id_policy=NameIDPolicy(
                                    allow_create="true",
                                    format=NAMEID_FORMAT_TRANSIENT))

        # == Create an AuthnRequest response

        name_id = idp.ident.transient_nameid(sp.config.entityid, "id12")
        binding, destination = idp.pick_binding("assertion_consumer_service",
                                                entity_id=sp.config.entityid)
        resp = idp.create_authn_response(
            {
                "eduPersonEntitlement": "Short stop",
                "surName": "Jeter",
                "givenName": "Derek",
                "mail": "*****@*****.**",
                "title": "The man"
            },
            "id-123456789",
            destination,
            sp.config.entityid,
            name_id=name_id,
            authn=AUTHN)

        hinfo = idp.apply_binding(binding, "%s" % resp, destination,
                                  relay_state)

        # ------- @SP ----------

        xmlstr = get_msg(hinfo, binding)
        aresp = sp.parse_authn_request_response(xmlstr, binding,
                                                {resp.in_response_to: "/"})

        binding, destination = sp.pick_binding("authn_query_service",
                                               entity_id=idp.config.entityid)

        authn_context = requested_authn_context(INTERNETPROTOCOLPASSWORD)

        subject = aresp.assertion.subject

        aq_id, aq = sp.create_authn_query(subject, destination, authn_context)

        print(aq)

        assert isinstance(aq, AuthnQuery)
        binding = BINDING_SOAP

        hinfo = sp.apply_binding(binding, "%s" % aq, destination, "state2")

        # -------- @IDP ----------

        xmlstr = get_msg(hinfo, binding)

        pm = idp.parse_authn_query(xmlstr, binding)

        msg = pm.message
        assert msg.id == aq.id

        p_res = idp.create_authn_query_response(msg.subject, msg.session_index,
                                                msg.requested_authn_context)

        print(p_res)

        hinfo = idp.apply_binding(binding,
                                  "%s" % p_res,
                                  "",
                                  "state2",
                                  response=True)

        # ------- @SP ----------

        xmlstr = get_msg(hinfo, binding)

        final = sp.parse_authn_query_response(xmlstr, binding)

        print(final)

        assert final.response.id == p_res.id
Esempio n. 31
0
    def post(self, request):
        """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
        """
        serializer = self.serializer_class(data=request.data)
        if not serializer.is_valid():
            errors = dict(serializer.errors)

            try:
                non_field_errors = errors.pop('non_field_errors')
                errors['detail'] = non_field_errors[0]
            except (KeyError, IndexError):
                pass

            return Response(errors, status=status.HTTP_401_UNAUTHORIZED)

        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, logger=logger)

        post = {'SAMLResponse': serializer.validated_data['saml2response']}

        # process the authentication response
        # noinspection PyBroadException
        try:
            response = client.response(
                post,
                outstanding=None,  # Rely on allow_unsolicited setting
                decode=False,  # The response is already base64 decoded
            )
        except Exception as e:
            logger.error('SAML response parsing failed %s' % e)
            response = None

        if response is None:
            return Response({'saml2response': 'SAML2 response has errors.'},
                            status=status.HTTP_401_UNAUTHORIZED)

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

        user = auth.authenticate(
            session_info=session_info,
            attribute_mapping=attribute_mapping,
            create_unknown_user=create_unknown_user,
        )
        if user is None:
            logger.info(
                'Authentication with SAML token has failed, user not found')
            return Response({'detail': 'SAML2 authentication failed'},
                            status=status.HTTP_401_UNAUTHORIZED)

        post_authenticated.send_robust(sender=user, session_info=session_info)

        token, _ = Token.objects.get_or_create(user=user)
        event_logger.info(
            "User %s with full name %s authenticated successfully with Omani PKI.",
            user.username,
            user.full_name,
            extra={
                'user': user,
                'event_type': 'auth_logged_in_with_pki'
            })
        logger.info(
            'Authenticated with SAML token. Returning token for successful login of user %s',
            user)
        return Response({'token': token.key})
Esempio n. 32
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)
Esempio n. 33
0
def get_saml_client():
    """
    Return SAML configuration.

    The configuration is a hash for use by saml2.config.Config
    """
    if settings.SAML_CALLBACK_SERVER_NAME:
        acs_url = settings.SAML_CALLBACK_SERVER_NAME + url_for(
            "saml_auth.idp_initiated")
    else:
        acs_url = url_for("saml_auth.idp_initiated", _external=True)

    # NOTE:
    #   Ideally, this should fetch the metadata and pass it to
    #   PySAML2 via the "inline" metadata type.
    #   However, this method doesn't seem to work on PySAML2 v2.4.0
    #
    #   SAML metadata changes very rarely. On a production system,
    #   this data should be cached as approprate for your production system.
    if settings.SAML_METADATA_URL != "":
        rv = requests.get(settings.SAML_METADATA_URL)
        import tempfile
        tmp = tempfile.NamedTemporaryFile()
        f = open(tmp.name, 'w')
        f.write(rv.text)
        f.close()
        metadata_path = tmp.name
    else:
        metadata_path = settings.SAML_LOCAL_METADATA_PATH

    saml_settings = {
        'metadata': {
            # 'inline': metadata,
            "local": [metadata_path]
        },
        'service': {
            'sp': {
                'endpoints': {
                    'assertion_consumer_service':
                    [(acs_url, BINDING_HTTP_REDIRECT),
                     (acs_url, BINDING_HTTP_POST)],
                },
                # Don't verify that the incoming requests originate from us via
                # the built-in cache for authn request ids in pysaml2
                'allow_unsolicited': True,
                # Don't sign authn requests, since signed requests only make
                # sense in a situation where you control both the SP and IdP
                'authn_requests_signed': False,
                'logout_requests_signed': True,
                'want_assertions_signed': True,
                'want_response_signed': False,
            },
        },
    }
    if settings.SAML_ENTITY_ID != "":
        saml_settings['entityid'] = settings.SAML_ENTITY_ID

    spConfig = Saml2Config()
    spConfig.load(saml_settings)
    spConfig.allow_unknown_attributes = True
    saml_client = Saml2Client(config=spConfig)
    if settings.SAML_METADATA_URL != "":
        tmp.close()

    return saml_client
Esempio n. 34
0
def test_construct_1():
    sp = Saml2Client(config_file=dotname("servera_conf"))
    url = sp.create_discovery_service_request(
        "http://example.com/saml/disco", "https://example.com/saml/sp.xml")

    assert url == "http://example.com/saml/disco?entityID=https%3A%2F%2Fexample.com%2Fsaml%2Fsp.xml"
Esempio n. 35
0
def test_basic_flow():
    sp = Saml2Client(config_file="servera_conf")
    idp = Server(config_file="idp_all_conf")

    # -------- @IDP -------------

    relay_state = "FOO"
    # -- dummy request ---
    orig_req = AuthnRequest(issuer=sp._issuer(),
                            name_id_policy=NameIDPolicy(
                                allow_create="true",
                                format=NAMEID_FORMAT_TRANSIENT))

    # == Create an AuthnRequest response

    name_id = idp.ident.transient_nameid("id12", sp.config.entityid)

    binding, destination = idp.pick_binding("assertion_consumer_service",
                                            entity_id=sp.config.entityid)
    resp = idp.create_authn_response(
        {
            "eduPersonEntitlement": "Short stop",
            "surName": "Jeter",
            "givenName": "Derek",
            "mail": "*****@*****.**",
            "title": "The man"
        },
        "id-123456789",
        destination,
        sp.config.entityid,
        name_id=name_id,
        authn=AUTHN)

    hinfo = idp.apply_binding(binding, "%s" % resp, destination, relay_state)

    # --------- @SP -------------

    xmlstr = get_msg(hinfo, binding)

    aresp = sp.parse_authn_request_response(xmlstr, binding,
                                            {resp.in_response_to: "/"})

    # == Look for assertion X

    asid = aresp.assertion.id

    binding, destination = sp.pick_binding("assertion_id_request_service",
                                           entity_id=idp.config.entityid)

    hinfo = sp.apply_binding(binding, asid, destination)

    # ---------- @IDP ------------

    aid = get_msg(hinfo, binding, response=False)

    # == construct response

    resp = idp.create_assertion_id_request_response(aid)

    hinfo = idp.apply_binding(binding, "%s" % resp, None, "", response=True)

    # ----------- @SP -------------

    xmlstr = get_msg(hinfo, binding, response=True)

    final = sp.parse_assertion_id_request_response(xmlstr, binding)

    print final.response
    assert isinstance(final.response, Assertion)