コード例 #1
0
    def test_1(self):
        server = Server("idp_slo_redirect_conf")
        req_id, request = _logout_request("sp_slo_redirect_conf")
        print(request)
        bindings = [BINDING_HTTP_REDIRECT]
        response = server.create_logout_response(request, bindings)
        binding, destination = server.pick_binding("single_logout_service",
                                                   bindings, "spsso",
                                                   request)

        http_args = server.apply_binding(binding, "%s" % response, destination,
                                         "relay_state", response=True)

        assert len(http_args) == 4
        assert http_args["headers"][0][0] == "Location"
        assert http_args["data"] == []
コード例 #2
0
    def test_1(self):
        server = Server("idp_slo_redirect_conf")
        req_id, request = _logout_request("sp_slo_redirect_conf")
        print(request)
        bindings = [BINDING_HTTP_REDIRECT]
        response = server.create_logout_response(request, bindings)
        binding, destination = server.pick_binding("single_logout_service",
                                                   bindings, "spsso", request)

        http_args = server.apply_binding(binding,
                                         "%s" % response,
                                         destination,
                                         "relay_state",
                                         response=True)

        assert len(http_args) == 4
        assert http_args["headers"][0][0] == "Location"
        assert http_args["data"] == []
コード例 #3
0
def test_flow():
    sp = Saml2Client(config_file="servera_conf")
    idp = Server(config_file="idp_all_conf")

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

    nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar")
    newid = NewID(text="Barfoo")

    mid, mreq = sp.create_manage_name_id_request(destination,
                                                 name_id=nameid,
                                                 new_id=newid)

    print(mreq)
    rargs = sp.apply_binding(binding, "%s" % mreq, destination, "")

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

    _req = idp.parse_manage_name_id_request(rargs["data"], binding)

    print((_req.message))

    mnir = idp.create_manage_name_id_response(_req.message, None)

    if binding != BINDING_SOAP:
        binding, destination = idp.pick_binding("manage_name_id_service",
                                                entity_id=sp.config.entityid)
    else:
        destination = ""

    respargs = idp.apply_binding(binding, "%s" % mnir, destination, "")

    print(respargs)

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

    _response = sp.parse_manage_name_id_request_response(
        respargs["data"], binding)

    print((_response.response))

    assert _response.response.id == mnir.id
コード例 #4
0
def test_flow():
    sp = Saml2Client(config_file="servera_conf")
    idp = Server(config_file="idp_all_conf")

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

    nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar")
    newid = NewID(text="Barfoo")

    mid, midq = sp.create_manage_name_id_request(destination, name_id=nameid,
                                                 new_id=newid)

    print midq
    rargs = sp.apply_binding(binding, "%s" % midq, destination, "")

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

    _req = idp.parse_manage_name_id_request(rargs["data"], binding)

    print _req.message

    mnir = idp.create_manage_name_id_response(_req.message, [binding])

    if binding != BINDING_SOAP:
        binding, destination = idp.pick_binding("manage_name_id_service",
                                                entity_id=sp.config.entityid)
    else:
        destination = ""

    respargs = idp.apply_binding(binding, "%s" % mnir, destination, "")

    print respargs

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

    _response = sp.parse_manage_name_id_request_response(respargs["data"],
                                                         binding)

    print _response.response

    assert _response.response.id == mnir.id
コード例 #5
0
def test_handle_logout_soap():
    sp = Saml2Client(config_file="servera_conf")
    idp = Server(config_file="idp_all_conf")

    policy = NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT,
                          sp_name_qualifier=sp.config.entityid,
                          allow_create="true")

    name_id = idp.ident.construct_nameid("subject", name_id_policy=policy)

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

    rid, request = idp.create_logout_request(destination, idp.config.entityid,
                                             name_id=name_id)
    args = idp.apply_binding(BINDING_SOAP, "%s" % request, destination)

    # register the user
    session_info = {
        "name_id": name_id, "issuer": idp.config.entityid,
        "not_on_or_after": in_a_while(minutes=15),
        "ava": {
            "givenName": "Anders",
            "surName": "Andersson",
            "mail": "*****@*****.**"
        }
    }
    sp.users.add_information_about_person(session_info)

    reply_args = sp.handle_logout_request(args["data"], name_id, binding,
                                          sign=False)

    print(reply_args)
    assert reply_args["method"] == "POST"
    assert reply_args["headers"] == [('content-type', 'application/soap+xml')]


#if __name__ == "__main__":
#    test_handle_logout_soap()
コード例 #6
0
ファイル: front.py プロジェクト: ibrsp/s2sproxy
class SamlIDP(service.Service):
    def __init__(self, environ, start_response, conf, cache, incoming):
        """
        Constructor for the class.
        :param environ: WSGI environ
        :param start_response: WSGI start response function
        :param conf: The SAML configuration
        :param cache: Cache with active sessions
        """
        service.Service.__init__(self, environ, start_response)
        self.response_bindings = None
        self.idp = Server(config=conf, cache=cache)
        self.incoming = incoming

    def verify_request(self, query, binding):
        """ Parses and verifies the SAML Authentication Request

        :param query: The SAML authn request, transport encoded
        :param binding: Which binding the query came in over
        :returns: dictionary
        """

        if not query:
            logger.info("Missing QUERY")
            resp = Unauthorized('Unknown user')
            return {"response": resp(self.environ, self.start_response)}

        req_info = self.idp.parse_authn_request(query, binding)

        logger.info("parsed OK")
        _authn_req = req_info.message
        logger.debug("%s" % _authn_req)

        # Check that I know where to send the reply to.
        try:
            binding_out, destination = self.idp.pick_binding(
                "assertion_consumer_service",
                bindings=self.response_bindings,
                entity_id=_authn_req.issuer.text, request=_authn_req)
        except Exception as err:
            logger.error("Couldn't find receiver endpoint: %s" % err)
            raise

        logger.debug("Binding: %s, destination: %s" % (binding_out,
                                                       destination))

        resp_args = {}
        try:
            resp_args = self.idp.response_args(_authn_req)
            _resp = None
        except UnknownPrincipal as excp:
            _resp = self.idp.create_error_response(_authn_req.id,
                                                   destination, excp)
        except UnsupportedBinding as excp:
            _resp = self.idp.create_error_response(_authn_req.id,
                                                   destination, excp)

        req_args = {}
        for key in ["subject", "name_id_policy", "conditions",
                    "requested_authn_context", "scoping", "force_authn",
                    "is_passive"]:
            try:
                val = getattr(_authn_req, key)
            except AttributeError:
                pass
            else:
                if val is not None:
                    req_args[key] = val

        return {"resp_args": resp_args, "response": _resp,
                "authn_req": _authn_req, "req_args": req_args}

    def handle_authn_request(self, binding_in):
        """
        Deal with an authentication request

        :param binding_in: Which binding was used when receiving the query
        :return: A response if an error occurred or session information in a
            dictionary
        """

        _request = self.unpack(binding_in)
        _binding_in = service.INV_BINDING_MAP[binding_in]

        try:
            _dict = self.verify_request(_request["SAMLRequest"], _binding_in)
        except UnknownPrincipal as excp:
            logger.error("UnknownPrincipal: %s" % (excp,))
            resp = ServiceError("UnknownPrincipal: %s" % (excp,))
            return resp(self.environ, self.start_response)
        except UnsupportedBinding as excp:
            logger.error("UnsupportedBinding: %s" % (excp,))
            resp = ServiceError("UnsupportedBinding: %s" % (excp,))
            return resp(self.environ, self.start_response)

        _binding = _dict["resp_args"]["binding"]
        if _dict["response"]:  # An error response.
            http_args = self.idp.apply_binding(
                _binding, "%s" % _dict["response"],
                _dict["resp_args"]["destination"],
                _request["RelayState"], response=True)

            logger.debug("HTTPargs: %s" % http_args)
            return self.response(_binding, http_args)
        else:
            return self.incoming(_dict, self.environ, self.start_response,
                                 _request["RelayState"])

    def construct_authn_response(self, identity, name_id, authn, resp_args,
                                 relay_state, sign_response=True):
        """

        :param identity:
        :param name_id:
        :param authn:
        :param resp_args:
        :param relay_state:
        :param sign_response:
        :return:
        """

        _resp = self.idp.create_authn_response(identity, name_id=name_id,
                                               authn=authn,
                                               sign_response=sign_response,
                                               **resp_args)

        http_args = self.idp.apply_binding(
            resp_args["binding"], "%s" % _resp, resp_args["destination"],
            relay_state, response=True)

        logger.debug("HTTPargs: %s" % http_args)

        resp = None
        if http_args["data"]:
            resp = Response(http_args["data"], headers=http_args["headers"])
        else:
            for header in http_args["headers"]:
                if header[0] == "Location":
                    resp = Redirect(header[1])

        if not resp:
            resp = ServiceError("Don't know how to return response")

        return resp(self.environ, self.start_response)

    def register_endpoints(self):
        """
        Given the configuration, return a set of URL to function mappings.
        """

        url_map = []
        idp_endpoints = self.idp.config.getattr("endpoints", "idp")
        for endp, binding in idp_endpoints["single_sign_on_service"]:
            p = urlparse(endp)
            url_map.append(("^%s/(.*)$" % p.path[1:],
                            ("IDP", "handle_authn_request",
                             service.BINDING_MAP[binding])))
            url_map.append(("^%s$" % p.path[1:],
                            ("IDP", "handle_authn_request",
                             service.BINDING_MAP[binding])))

        return url_map
コード例 #7
0
ファイル: test_63_ecp.py プロジェクト: 5monkeys/pysaml2
def test_complete_flow():
    client = ecp_client.Client("user", "password",
                               metadata_file=full_path("idp_all.xml"))

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

    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
コード例 #8
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)
コード例 #9
0
class IdpServer(object):

    ticket = {}
    responses = {}
    challenges = {}
    _binding_mapping = {
        'http-redirect': BINDING_HTTP_REDIRECT,
        'http-post': BINDING_HTTP_POST
    }
    _endpoint_types = ['single_sign_on_service', 'single_logout_service']
    _spid_levels = [
        'https://www.spid.gov.it/SpidL1', 'https://www.spid.gov.it/SpidL2',
        'https://www.spid.gov.it/SpidL3'
    ]
    _spid_attributes = {
        'primary': {
            'spidCode': 'xs:string',
            'name': 'xs:string',
            'familyName': 'xs:string',
            'placeOfBirth': 'xs:string',
            'countryOfBirth': 'xs:string',
            'dateOfBirth': 'xs:date',
            'gender': 'xs:string',
            'companyName': 'xs:string',
            'registeredOffice': 'xs:string',
            'fiscalNumber': 'xs:string',
            'ivaCode': 'xs:string',
            'idCard': 'xs:string',
        },
        'secondary': {
            'mobilePhone': 'xs:string',
            'email': 'xs:string',
            'address': 'xs:string',
            'expirationDate': 'xs:date',
            'digitalAddress': 'xs:string'  # PEC
        }
    }
    CHALLENGES_TIMEOUT = 30  # seconds

    def __init__(self, app, config, *args, **kwargs):
        """
        :param app: Flask instance
        :param config: dictionary containing the configuration
        :param args:
        :param kwargs:
        """
        # bind Flask app
        self.app = app
        self.user_manager = JsonUserManager()
        # setup
        self._config = config
        self.app.secret_key = 'sosecret'
        handler = RotatingFileHandler('spid.log',
                                      maxBytes=500000,
                                      backupCount=1)
        self.app.logger.addHandler(handler)
        self._prepare_server()

    @property
    def _mode(self):
        return 'https' if self._config.get('https', False) else 'http'

    def _idp_config(self):
        """
        Process pysaml2 configuration
        """
        key_file_path = self._config.get('key_file')
        cert_file_path = self._config.get('cert_file')
        metadata = self._config.get('metadata')
        metadata = metadata if metadata else []
        existing_key = os.path.isfile(key_file_path) if key_file_path else None
        existing_cert = os.path.isfile(
            cert_file_path) if cert_file_path else None
        if not existing_key:
            raise BadConfiguration(
                'Chiave privata dell\'IdP di test non trovata: {} non trovato'.
                format(key_file_path))
        if not existing_cert:
            raise BadConfiguration(
                'Certificato dell\'IdP di test non trovato: {} non trovato'.
                format(cert_file_path))
        self.entity_id = self._config.get('hostname')
        if not self.entity_id:
            self.entity_id = self._config.get('host')
        self.entity_id = '{}://{}'.format(self._mode, self.entity_id)
        port = self._config.get('port')
        if port:
            self.entity_id = '{}:{}'.format(self.entity_id, port)
        idp_conf = {
            "entityid":
            self.entity_id,
            "description":
            "Spid Test IdP",
            "service": {
                "idp": {
                    "name": "Spid Testenv",
                    "endpoints": {
                        "single_sign_on_service": [],
                        "single_logout_service": [],
                    },
                    "policy": {
                        "default": {
                            "name_form": NAME_FORMAT_BASIC,
                        },
                    },
                    "name_id_format": [
                        NAMEID_FORMAT_TRANSIENT,
                    ]
                },
            },
            "debug":
            1,
            "key_file":
            self._config.get('key_file'),
            "cert_file":
            self._config.get('cert_file'),
            "metadata":
            metadata,
            "organization": {
                "display_name": "Spid testenv",
                "name": "Spid testenv",
                "url": "http://www.example.com",
            },
            "contact_person": [
                {
                    "contact_type": "technical",
                    "given_name": "support",
                    "sur_name": "support",
                    "email_address": "*****@*****.**"
                },
            ],
            "logger": {
                "rotating": {
                    "filename": "idp.log",
                    "maxBytes": 500000,
                    "backupCount": 1,
                },
                "loglevel": "debug",
            }
        }
        # setup services url
        for _service_type in self._endpoint_types:
            endpoint = self._config['endpoints'][_service_type]
            idp_conf['service']['idp']['endpoints'][_service_type].append(
                ('{}{}'.format(self.entity_id,
                               endpoint), BINDING_HTTP_REDIRECT))
            idp_conf['service']['idp']['endpoints'][_service_type].append(
                ('{}{}'.format(self.entity_id, endpoint), BINDING_HTTP_POST))
        return idp_conf

    def _setup_app_routes(self):
        """
        Setup Flask routes
        """
        # Setup SSO and SLO endpoints
        endpoints = self._config.get('endpoints')
        if endpoints:
            for ep_type in self._endpoint_types:
                _url = endpoints.get(ep_type)
                if _url:
                    if not _url.startswith('/'):
                        raise BadConfiguration(
                            'Errore nella configurazione delle url, i path devono essere relativi ed iniziare con "/" (slash) - url {}'
                            .format(_url))
                    for _binding in self._binding_mapping.keys():
                        self.app.add_url_rule(_url,
                                              '{}_{}'.format(
                                                  ep_type, _binding),
                                              getattr(self, ep_type),
                                              methods=[
                                                  'GET',
                                              ])
        self.app.add_url_rule('/login',
                              'login',
                              self.login,
                              methods=[
                                  'POST',
                                  'GET',
                              ])
        # Endpoint for user add action
        self.app.add_url_rule('/add-user',
                              'add_user',
                              self.add_user,
                              methods=[
                                  'GET',
                                  'POST',
                              ])
        self.app.add_url_rule('/continue-response',
                              'continue_response',
                              self.continue_response,
                              methods=[
                                  'POST',
                              ])
        self.app.add_url_rule('/metadata',
                              'metadata',
                              self.metadata,
                              methods=['POST', 'GET'])

    def _prepare_server(self):
        """
        Setup server
        """
        self.idp_config = Saml2Config()
        self.BASE = '{}://{}:{}'.format(self._mode, self._config.get('host'),
                                        self._config.get('port'))
        if 'entityid' not in self._config:
            # as fallback for entityid use host:port string
            self._config['entityid'] = self.BASE
        self.idp_config.load(cnf=self._idp_config())
        self.server = Server(config=self.idp_config)
        self._setup_app_routes()
        # setup custom methods in order to
        # prepare the login form and verify the challenge (optional)
        # for every spid level (1-2-3)
        self.authn_broker = AuthnBroker()
        for index, _level in enumerate(self._spid_levels):
            self.authn_broker.add(
                authn_context_class_ref(_level),
                getattr(self, '_verify_spid_{}'.format(index + 1)))

    def _verify_spid_1(self, verify=False, **kwargs):
        self.app.logger.debug('spid level 1 - verifica ({})'.format(verify))
        return self._verify_spid(1, verify, **kwargs)

    def _verify_spid_2(self, verify=False, **kwargs):
        self.app.logger.debug('spid level 2 - verifica ({})'.format(verify))
        return self._verify_spid(2, verify, **kwargs)

    def _verify_spid_3(self, verify=False, **kwargs):
        self.app.logger.debug('spid level 3 - verifica ({})'.format(verify))
        return self._verify_spid(3, verify, **kwargs)

    def _verify_spid(self, level=1, verify=False, **kwargs):
        """
        :param level: integer, SPID level
        :param verify: boolean, if True verify spid extra challenge (otp etc.), if False prepare the challenge
        :param kwargs: dictionary, extra arguments
        """
        if verify:
            # Verify the challenge
            if level == 2:
                # spid level 2
                otp = kwargs.get('data').get('otp')
                key = kwargs.get('key')
                if key and key not in self.challenges or not otp:
                    return False
                total_seconds = (datetime.now() -
                                 self.challenges[key][1]).total_seconds()
                # Check that opt value is equal and not expired
                if self.challenges[key][
                        0] != otp or total_seconds > self.CHALLENGES_TIMEOUT:
                    del self.challenges[key]
                    return False
            return True
        else:
            # Prepare the challenge
            if level == 2:
                # spid level 2
                # very simple otp implementation, while opt is a random 6 digits string
                # with a lifetime setup in the server instance
                key = kwargs.get('key')
                otp = ''.join(random.choice(string.digits) for _ in range(6))
                self.challenges[key] = [otp, datetime.now()]
                extra_challenge = '<span>Otp ({})</span><input type="text" name="otp" />'.format(
                    otp)
            else:
                extra_challenge = ''
            return extra_challenge

    def unpack_args(self, elems):
        """
        Unpack arguments from request
        """
        return dict([(k, v) for k, v in elems.items()])

    def _raise_error(self, msg, extra=None):
        """
        Raise some error using 'abort' function from Flask

        :param msg: string for error type
        :param extra: optional string for error details
        """
        abort(Response(error_table.format(msg, extra), 200))

    def _check_saml_message_restrictions(self, obj):
        # TODO: Implement here or somewhere (e.g. mixin on pysaml2 subclasses)
        # the logic to validate spid rules on saml entities
        raise NotImplementedError

    def _store_request(self, authnreq):
        """
        Store authnrequest in a dictionary

        :param authnreq: authentication request string
        """
        self.app.logger.debug('store_request: {}'.format(authnreq))
        key = sha1(authnreq.xmlstr).hexdigest()
        # store the AuthnRequest
        self.ticket[key] = authnreq
        return key

    def single_sign_on_service(self):
        """
        Process Http-Redirect or Http-POST request

        :param request: Flask request object
        """
        self.app.logger.info("Http-Redirect")
        # Unpack parameters
        saml_msg = self.unpack_args(request.args)
        try:
            _key = session['request_key']
            req_info = self.ticket[_key]
        except KeyError as e:
            try:
                binding = self._get_binding('single_sign_on_service', request)
                # Parse AuthnRequest
                req_info = self.server.parse_authn_request(
                    saml_msg["SAMLRequest"], binding)
                authn_req = req_info.message
            except KeyError as err:
                self.app.logger.debug(str(err))
                self._raise_error('Parametro SAMLRequest assente.')

            if not req_info:
                self._raise_error('Processo di parsing del messaggio fallito.')

            self.app.logger.debug('AuthnRequest: {}'.format(authn_req))
            # Check if it is signed
            if "SigAlg" in saml_msg and "Signature" in saml_msg:
                # Signed request
                self.app.logger.debug('Messaggio SAML firmato.')
                issuer_name = authn_req.issuer.text
                _certs = self.server.metadata.certs(issuer_name, "any",
                                                    "signing")
                verified_ok = False
                for cert in _certs:
                    self.app.logger.debug('security backend: {}'.format(
                        self.server.sec.sec_backend.__class__.__name__))
                    # Check signature
                    if verify_redirect_signature(saml_msg,
                                                 self.server.sec.sec_backend,
                                                 cert):
                        verified_ok = True
                        break
                if not verified_ok:
                    self._raise_error(
                        'Verifica della firma del messaggio fallita.')
            # Perform login
            key = self._store_request(req_info)
            relay_state = saml_msg.get('RelayState', '')
            session['request_key'] = key
            session['relay_state'] = relay_state
        return redirect(url_for('login'))

    def _get_binding(self, endpoint_type, request):
        try:
            endpoint = request.endpoint
            binding = endpoint.split('{}_'.format(endpoint_type))[1]
            return self._binding_mapping.get(binding)
        except IndexError:
            pass

    @property
    def _spid_main_fields(self):
        """
        Returns a list of spid main attributes
        """
        return self._spid_attributes['primary'].keys()

    @property
    def _spid_secondary_fields(self):
        """
        Returns a list of spid secondary attributes
        """
        return self._spid_attributes['secondary'].keys()

    def add_user(self):
        """
        Add user endpoint
        """
        spid_main_fields = self._spid_main_fields
        spid_secondary_fields = self._spid_secondary_fields
        _fields = '<br><b>{}</b><br>'.format('Primary attributes')
        for _field_name in spid_main_fields:
            _fields = '{}<span>{}</span> <input type="text" name={} /><br>'.format(
                _fields, _field_name, _field_name)
        _fields = '{}<br><b>{}</b><br>'.format(_fields, 'Secondary attributes')
        for _field_name in spid_secondary_fields:
            _fields = '{}<span>{}</span> <input type="text" name={} /><br>'.format(
                _fields, _field_name, _field_name)
        if request.method == 'GET':
            return FORM_ADD_USER.format('/add-user', _fields), 200
        elif request.method == 'POST':
            username = request.form.get('username')
            password = request.form.get('password')
            sp = request.form.get('service_provider')
            if not username or not password or not sp:
                abort(400)
            extra = {}
            for spid_field in spid_main_fields:
                spid_value = request.form.get(spid_field)
                if spid_value:
                    extra[spid_field] = spid_value
            for spid_field in spid_secondary_fields:
                spid_value = request.form.get(spid_field)
                if spid_value:
                    extra[spid_field] = spid_value
            self.user_manager.add(username, password, sp, extra)
        return 'Added a new user', 200

    def login(self):
        """
        Login endpoint (verify user credentials)
        """
        key = session['request_key'] if 'request_key' in session else None
        relay_state = session['relay_state'] if 'relay_state' in session else ''
        self.app.logger.debug('Request key: {}'.format(key))
        if key and key in self.ticket:
            authn_request = self.ticket[key]
            sp_id = authn_request.message.issuer.text
            destination = authn_request.message.assertion_consumer_service_url
            spid_level = authn_request.message.requested_authn_context.authn_context_class_ref[
                0].text
            authn_info = self.authn_broker.pick(
                authn_request.message.requested_authn_context)
            callback, reference = authn_info[0]
            if request.method == 'GET':
                # inject extra data in form login based on spid level
                extra_challenge = callback(**{'key': key})
                return FORM_LOGIN.format(url_for('login'), key, relay_state,
                                         extra_challenge), 200
            # verify optional challenge based on spid level
            verified = callback(verify=True,
                                **{
                                    'key': key,
                                    'data': request.form
                                })
            if verified:
                # verify user credentials
                user_id, user = self.user_manager.get(request.form['username'],
                                                      request.form['password'],
                                                      sp_id)
                if user_id is not None:
                    # setup response
                    attribute_statement_on_response = self._config.get(
                        'attribute_statement_on_response')
                    identity = user['attrs']
                    AUTHN = {"class_ref": spid_level, "authn_auth": spid_level}
                    _data = dict(identity=identity,
                                 userid=user_id,
                                 in_response_to=authn_request.message.id,
                                 destination=destination,
                                 sp_entity_id=sp_id,
                                 authn=AUTHN,
                                 issuer=self.server.config.entityid,
                                 sign_alg=SIGN_ALG,
                                 digest_alg=DIGEST_ALG,
                                 sign_assertion=True)
                    response = self.server.create_authn_response(**_data)
                    http_args = self.server.apply_binding(
                        BINDING_HTTP_POST,
                        response,
                        destination,
                        response=True,
                        sign=True,
                        relay_state=relay_state)
                    # Setup confirmation page data
                    ast = Assertion(identity)
                    policy = self.server.config.getattr("policy", "idp")
                    ast.acs = self.server.config.getattr(
                        "attribute_converters", "idp")
                    res = ast.apply_policy(sp_id, policy, self.server.metadata)
                    attrs = res.keys()
                    attrs_list = ''
                    for _attr in attrs:
                        attrs_list = '{}<tr><td>{}</td></tr>'.format(
                            attrs_list, _attr)
                    self.responses[key] = http_args['data']
                    return CONFIRM_PAGE.format(attrs_list,
                                               '/continue-response', key), 200
        abort(403)

    def continue_response(self):
        key = request.form['request_key']
        if key and key in self.ticket and key in self.responses:
            return self.responses[key], 200
        abort(403)

    def single_logout_service(self):
        """
        SLO endpoint

        :param binding: 'redirect' is http-redirect, 'post' is http-post binding
        """

        self.app.logger.debug("req: '%s'", request)
        saml_msg = self.unpack_args(request.args)
        _binding = self._get_binding('single_logout_service', request)
        req_info = self.server.parse_logout_request(saml_msg['SAMLRequest'],
                                                    _binding)
        msg = req_info.message
        response = self.server.create_logout_response(
            msg, [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT],
            sign_alg=SIGN_ALG,
            digest_alg=DIGEST_ALG,
            sign=True)
        binding, destination = self.server.pick_binding(
            "single_logout_service",
            [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT], "spsso", req_info)
        http_args = self.server.apply_binding(binding,
                                              "%s" % response,
                                              destination,
                                              response=True,
                                              sign=True)
        return http_args['data'], 200

    def metadata(self):
        metadata = create_metadata_string(
            __file__,
            self.server.config,
        )
        return Response(metadata, mimetype='text/xml')

    @property
    def _wsgiconf(self):
        _cnf = {
            'host': self._config.get('host', '0.0.0.0'),
            'port': self._config.get('port', '8000'),
            'debug': self._config.get('debug', True),
        }
        if self._config.get('https', False):
            key = self._config.get('https_key_file')
            cert = self._config.get('https_cert_file')
            if not key or not cert:
                raise KeyError(
                    'Errore modalità https: Chiave e/o certificato assenti!')
            _cnf['ssl_context'] = (
                cert,
                key,
            )
        return _cnf

    def start(self):
        """
        Start the server instance
        """
        self.app.run(**self._wsgiconf)
コード例 #10
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"))
    idp = Server(config_file=dotname("idp_all_conf"))

    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
コード例 #11
0
ファイル: test_65_authn_query.py プロジェクト: Ratler/pysaml2
def test_flow():
    sp = Saml2Client(config_file="servera_conf")
    idp = Server(config_file="idp_all_conf")

    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 = 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
コード例 #12
0
ファイル: front.py プロジェクト: rhoerbe/pefim-proxy
class SamlIDP(service.Service):
    def __init__(self, environ, start_response, conf, cache, incomming, tid1_to_tid2, tid2_to_tid1, 
                 encmsg_to_iv, tid_handler, force_persistant_nameid, force_no_userid_subject_cacheing, idp=None):
        """
        Constructor for the class.
        :param environ: WSGI environ
        :param start_response: WSGI start response function
        :param conf: The SAML configuration
        :param cache: Cache with active sessions
        """
        service.Service.__init__(self, environ, start_response)
        self.response_bindings = None
        if idp is None:
            self.idp = Server(config=conf, cache=cache)
        else:
            self.idp = idp
        self.incomming = incomming
        self.tid1_to_tid2 = tid1_to_tid2
        self.tid2_to_tid1 = tid2_to_tid1
        self.encmsg_to_iv = encmsg_to_iv
        self.tid_handler = tid_handler
        self.force_persistant_nameid = force_persistant_nameid
        self.force_no_userid_subject_cacheing = force_no_userid_subject_cacheing

    def verify_request(self, query, binding):
        """ Parses and verifies the SAML Authentication Request

        :param query: The SAML authn request, transport encoded
        :param binding: Which binding the query came in over
        :returns: dictionary
        """

        if not query:
            logger.info("Missing QUERY")
            resp = Unauthorized('Unknown user')
            return {"response": resp}

        req_info = self.idp.parse_authn_request(query, binding)
        encrypt_cert = encrypt_cert_from_item(req_info.message)

        logger.info("parsed OK")
        _authn_req = req_info.message
        logger.debug("%s" % _authn_req)

        # Check that I know where to send the reply to
        try:
            binding_out, destination = self.idp.pick_binding(
                "assertion_consumer_service",
                bindings=self.response_bindings,
                entity_id=_authn_req.issuer.text, request=_authn_req)
        except Exception as err:
            logger.error("Couldn't find receiver endpoint: %s" % err)
            raise

        logger.debug("Binding: %s, destination: %s" % (binding_out,
                                                       destination))

        resp_args = {}
        try:
            resp_args = self.idp.response_args(_authn_req)
            _resp = None
        except UnknownPrincipal as excp:
            _resp = self.idp.create_error_response(_authn_req.id,
                                                   destination, excp)
        except UnsupportedBinding as excp:
            _resp = self.idp.create_error_response(_authn_req.id,
                                                   destination, excp)

        req_args = {}
        for key in ["subject", "name_id_policy", "conditions",
                    "requested_authn_context", "scoping", "force_authn",
                    "is_passive"]:
            try:
                val = getattr(_authn_req, key)
            except AttributeError:
                pass
            else:
                if val is not None:
                    req_args[key] = val

        return {"resp_args": resp_args, "response": _resp,
                "authn_req": _authn_req, "req_args": req_args, "encrypt_cert": encrypt_cert}

    def handle_authn_request(self, binding_in):
        """
        Deal with an authentication request

        :param binding_in: Which binding was used when receiving the query
        :return: A response if an error occurred or session information in a
            dictionary
        """

        _request = self.unpack(binding_in)
        _binding_in = service.INV_BINDING_MAP[binding_in]

        try:
            _dict = self.verify_request(_request["SAMLRequest"], _binding_in)
        except UnknownPrincipal as excp:
            logger.error("UnknownPrincipal: %s" % (excp,))
            resp = ServiceError("UnknownPrincipal: %s" % (excp,))
            return resp
        except UnsupportedBinding as excp:
            logger.error("UnsupportedBinding: %s" % (excp,))
            resp = ServiceError("UnsupportedBinding: %s" % (excp,))
            return resp

        _binding = _dict["resp_args"]["binding"]
        if _dict["response"]:  # An error response
            http_args = self.idp.apply_binding(
                _binding, "%s" % _dict["response"],
                _dict["resp_args"]["destination"],
                _request["RelayState"], response=True)

            logger.debug("HTTPargs: %s" % http_args)
            return self.response(_binding, http_args)
        else:
            return self.incomming(_dict, self, self.environ,
                                  self.start_response, _request["RelayState"])

    def get_tid1_resp(self, org_resp):
        tid1 = org_resp.assertion.subject.name_id.text
        return tid1

    def get_sp_entityid(self, resp_args):
        sp_entityid = resp_args["destination"]
        return sp_entityid

    def get_tid2_enc(self, tid1, sp_entityid):
        iv = None
        if self.encmsg_to_iv is not None:
            iv = self.tid_handler.get_new_iv()
        tid2_enc = self.tid_handler.tid2_encrypt(tid1, sp_entityid, iv=iv)
        if self.encmsg_to_iv is not None:
            self.encmsg_to_iv[tid2_enc] = iv
        return tid2_enc

    def get_tid2_hash(self, tid1, sp_entityid):
        tid2_hash = self.tid_handler.tid2_hash(tid1, sp_entityid)
        return tid2_hash

    def handle_tid(self, tid1, tid2):
        if self.tid1_to_tid2 is not None:
            self.tid1_to_tid2[tid1] = tid2
        if self.tid2_to_tid1 is not None:
            self.tid2_to_tid1[tid2] = tid1

    def name_id_exists(self, userid, name_id_policy, sp_entity_id):
        try:
            snq = name_id_policy.sp_name_qualifier
        except AttributeError:
            snq = sp_entity_id

        if not snq:
            snq = sp_entity_id

        kwa = {"sp_name_qualifier": snq}

        try:
            kwa["format"] = name_id_policy.format
        except AttributeError:
            pass
        return self.idp.ident.find_nameid(userid, **kwa)

    def construct_authn_response(self, identity, userid, authn, resp_args,
                                 relay_state, name_id=None, sign_response=True, org_resp=None, org_xml_response=None):
        """

        :param identity:
        :param name_id:
        :param authn:
        :param resp_args:
        :param relay_state:
        :param sign_response:
        :return:
        """
        if self.force_persistant_nameid:
            if "name_id_policy" in resp_args:
                resp_args["name_id_policy"].format = NAMEID_FORMAT_PERSISTENT

        sp_entityid = self.get_sp_entityid(resp_args)
        tid1 = self.get_tid1_resp(org_resp)
        userid = self.tid_handler.uid_hash(tid1)

        if self.force_no_userid_subject_cacheing:
            self.idp.ident = IdentDB({})

        name_id_exist = False
        if self.name_id_exists(userid, resp_args["name_id_policy"], resp_args["sp_entity_id"]):
            name_id_exist = True

        if not name_id_exist:
            if identity is not None:
                identity["uid"] = userid
            if self.tid2_to_tid1 is None:
                tid2 = self.get_tid2_enc(tid1, sp_entityid)
            else:
                tid2 = self.get_tid2_hash(tid1, sp_entityid)
        else:
            tid2 = None

        _resp = self.idp.create_authn_response(identity, userid=userid, name_id=name_id,
                                               authn=authn,
                                               sign_response=False,
                                               **resp_args)

        if not name_id_exist:
            #Fix for name_id so sid2 is used instead.
            _resp.assertion.subject.name_id.text = tid2
            self.idp.ident.remove_local(userid)
            self.idp.ident.store(userid, _resp.assertion.subject.name_id)

        tid2 = _resp.assertion.subject.name_id.text
        if self.tid2_to_tid1 is not None:
            self.tid2_to_tid1[tid2] = tid1
            if self.tid1_to_tid2 is not None:
                self.tid1_to_tid2[tid1] = tid2

        advice = None
        for tmp_assertion in org_resp.response.assertion:
            if tmp_assertion.advice is not None:
                advice = tmp_assertion.advice
                break
        if advice is not None:
            _resp.assertion.advice = advice
        #_resp.assertion = []

        if sign_response:
            _class_sign = class_name(_resp)
            _resp.signature = pre_signature_part(_resp.id, self.idp.sec.my_cert, 1)
            _resp = self.idp.sec.sign_statement(_resp, _class_sign, node_id=_resp.id)
        http_args = self.idp.apply_binding(
            resp_args["binding"], "%s" % _resp, resp_args["destination"],
            relay_state, response=True)

        logger.debug("HTTPargs: %s" % http_args)

        resp = None
        if http_args["data"]:
            resp = Response(http_args["data"], headers=http_args["headers"])
        else:
            for header in http_args["headers"]:
                if header[0] == "Location":
                    resp = Redirect(header[1])

        if not resp:
            resp = ServiceError("Don't know how to return response")

        return resp

    def register_endpoints(self):
        """
        Given the configuration, return a set of URL to function mappings.
        """
        url_map = []
        for endp, binding in self.idp.config.getattr("endpoints", "idp")[
                "single_sign_on_service"]:
            p = urlparse(endp)
            url_map.append(("^%s/(.*)$" % p.path[1:],
                            ("IDP", "handle_authn_request",
                             service.BINDING_MAP[binding])))
            url_map.append(("^%s$" % p.path[1:],
                            ("IDP", "handle_authn_request",
                             service.BINDING_MAP[binding])))

        return url_map
コード例 #13
0
def test_flow():
    sp = Saml2Client(config_file="servera_conf")
    idp = Server(config_file="idp_all_conf")

    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
コード例 #14
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)
コード例 #15
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
コード例 #16
0
ファイル: views.py プロジェクト: saundersmatt/djangosaml2idp
def login_process(request):
    """ View which processes the actual SAML request and returns a self-submitting form with the SAML response.
        The login_required decorator ensures the user authenticates first on the IdP using 'normal' ways.
    """
    # Construct server with config from settings dict
    conf = IdPConfig()
    conf.load(copy.deepcopy(settings.SAML_IDP_CONFIG))
    IDP = Server(config=conf)
    # Parse incoming request
    try:
        req_info = IDP.parse_authn_request(request.session['SAMLRequest'],
                                           BINDING_HTTP_POST)
    except Exception as excp:
        return HttpResponseBadRequest(excp)
    # TODO this is taken from example, but no idea how this works or whats it does. Check SAML2 specification?
    # Signed request for HTTP-REDIRECT
    if "SigAlg" in request.session and "Signature" in request.session:
        _certs = IDP.metadata.certs(req_info.message.issuer.text, "any",
                                    "signing")
        verified_ok = False
        for cert in _certs:
            # TODO implement
            #if verify_redirect_signature(_info, IDP.sec.sec_backend, cert):
            #    verified_ok = True
            #    break
            pass
        if not verified_ok:
            return HttpResponseBadRequest(
                "Message signature verification failure")

    binding_out, destination = IDP.pick_binding(
        service="assertion_consumer_service",
        entity_id=req_info.message.issuer.text)

    # Gather response arguments
    try:
        resp_args = IDP.response_args(req_info.message)
    except (UnknownPrincipal, UnsupportedBinding) as excp:
        return HttpResponseServerError(excp)

    try:
        sp_config = settings.SAML_IDP_SPCONFIG[resp_args['sp_entity_id']]
    except Exception:
        raise ImproperlyConfigured(
            "No config for SP %s defined in SAML_IDP_SPCONFIG" %
            resp_args['sp_entity_id'])

    # Create user-specified processor or fallback to all-access base processor
    processor_string = sp_config.get('processor', None)
    if processor_string is None:
        processor = BaseProcessor
    else:
        processor_class = import_string(processor_string)
        processor = processor_class()

    # Check if user has access to the service of this SP
    if not processor.has_access(request.user):
        raise PermissionDenied("You do not have access to this resource")

    # Create Identity dict (SP-specific)
    sp_mapping = sp_config.get('attribute_mapping', {'username': '******'})
    identity = processor.create_identity(request.user, sp_mapping)

    # TODO investigate how this works, because I don't get it. Specification?
    req_authn_context = req_info.message.requested_authn_context or PASSWORD
    AUTHN_BROKER = AuthnBroker()
    AUTHN_BROKER.add(authn_context_class_ref(req_authn_context), "")

    # Construct SamlResponse message
    try:
        authn_resp = IDP.create_authn_response(
            identity=identity,
            userid=request.user.username,
            name_id=NameID(format=resp_args['name_id_policy'].format,
                           sp_name_qualifier=destination,
                           text=request.user.username),
            authn=AUTHN_BROKER.get_authn_by_accr(req_authn_context),
            sign_response=IDP.config.getattr("sign_response", "idp") or False,
            sign_assertion=IDP.config.getattr("sign_assertion", "idp")
            or False,
            **resp_args)
    except Exception as excp:
        return HttpResponseServerError(excp)

    # Return as html with self-submitting form.
    http_args = IDP.apply_binding(binding=binding_out,
                                  msg_str="%s" % authn_resp,
                                  destination=destination,
                                  relay_state=request.session['RelayState'],
                                  response=True)

    logger.debug('http args are: %s' % http_args)

    if processor.enable_multifactor(request.user):
        # Store http_args in session for after multi factor is complete
        request.session['saml_data'] = http_args['data']
        logger.debug("Redirecting to process_multi_factor")
        return HttpResponseRedirect(reverse('saml_multi_factor'))
    else:
        logger.debug("Performing SAML redirect")
        return HttpResponse(http_args['data'])
コード例 #17
0
ファイル: front.py プロジェクト: simudream/s2sproxy
class SamlIDP(service.Service):
    def __init__(self, environ, start_response, conf, cache, incomming):
        """
        Constructor for the class.
        :param environ: WSGI environ
        :param start_response: WSGI start response function
        :param conf: The SAML configuration
        :param cache: Cache with active sessions
        """
        service.Service.__init__(self, environ, start_response)
        self.response_bindings = None
        self.idp = Server(config=conf, cache=cache)
        self.incomming = incomming

    def verify_request(self, query, binding):
        """ Parses and verifies the SAML Authentication Request

        :param query: The SAML authn request, transport encoded
        :param binding: Which binding the query came in over
        :returns: dictionary
        """

        if not query:
            logger.info("Missing QUERY")
            resp = Unauthorized('Unknown user')
            return {"response": resp(self.environ, self.start_response)}

        req_info = self.idp.parse_authn_request(query, binding)

        logger.info("parsed OK")
        _authn_req = req_info.message
        logger.debug("%s" % _authn_req)

        # Check that I know where to send the reply to
        try:
            binding_out, destination = self.idp.pick_binding(
                "assertion_consumer_service",
                bindings=self.response_bindings,
                entity_id=_authn_req.issuer.text,
                request=_authn_req)
        except Exception as err:
            logger.error("Couldn't find receiver endpoint: %s" % err)
            raise

        logger.debug("Binding: %s, destination: %s" %
                     (binding_out, destination))

        resp_args = {}
        try:
            resp_args = self.idp.response_args(_authn_req)
            _resp = None
        except UnknownPrincipal as excp:
            _resp = self.idp.create_error_response(_authn_req.id, destination,
                                                   excp)
        except UnsupportedBinding as excp:
            _resp = self.idp.create_error_response(_authn_req.id, destination,
                                                   excp)

        req_args = {}
        for key in [
                "subject", "name_id_policy", "conditions",
                "requested_authn_context", "scoping", "force_authn",
                "is_passive"
        ]:
            try:
                val = getattr(_authn_req, key)
            except AttributeError:
                pass
            else:
                req_args[key] = val

        return {
            "resp_args": resp_args,
            "response": _resp,
            "authn_req": _authn_req,
            "req_args": req_args
        }

    def handle_authn_request(self, binding_in):
        """
        Deal with an authentication request

        :param binding_in: Which binding was used when receiving the query
        :return: A response if an error occurred or session information in a
            dictionary
        """

        _request = self.unpack(binding_in)
        _binding_in = service.INV_BINDING_MAP[binding_in]

        try:
            _dict = self.verify_request(_request["SAMLRequest"], _binding_in)
        except UnknownPrincipal as excp:
            logger.error("UnknownPrincipal: %s" % (excp, ))
            resp = ServiceError("UnknownPrincipal: %s" % (excp, ))
            return resp(self.environ, self.start_response)
        except UnsupportedBinding as excp:
            logger.error("UnsupportedBinding: %s" % (excp, ))
            resp = ServiceError("UnsupportedBinding: %s" % (excp, ))
            return resp(self.environ, self.start_response)

        _binding = _dict["resp_args"]["binding"]
        if _dict["response"]:  # An error response
            http_args = self.idp.apply_binding(
                _binding,
                "%s" % _dict["response"],
                _dict["resp_args"]["destination"],
                _request["RelayState"],
                response=True)

            logger.debug("HTTPargs: %s" % http_args)
            return self.response(_binding, http_args)
        else:
            return self.incomming(_dict, self, self.environ,
                                  self.start_response, _request["RelayState"])

    def construct_authn_response(self,
                                 identity,
                                 name_id,
                                 authn,
                                 resp_args,
                                 relay_state,
                                 sign_response=True):
        """

        :param identity:
        :param name_id:
        :param authn:
        :param resp_args:
        :param relay_state:
        :param sign_response:
        :return:
        """

        _resp = self.idp.create_authn_response(identity,
                                               name_id=name_id,
                                               authn=authn,
                                               sign_response=sign_response,
                                               **resp_args)

        http_args = self.idp.apply_binding(resp_args["binding"],
                                           "%s" % _resp,
                                           resp_args["destination"],
                                           relay_state,
                                           response=True)

        logger.debug("HTTPargs: %s" % http_args)

        resp = None
        if http_args["data"]:
            resp = Response(http_args["data"], headers=http_args["headers"])
        else:
            for header in http_args["headers"]:
                if header[0] == "Location":
                    resp = Redirect(header[1])

        if not resp:
            resp = ServiceError("Don't know how to return response")

        return resp(self.environ, self.start_response)

    def register_endpoints(self):
        """
        Given the configuration, return a set of URL to function mappings.
        """

        url_map = []
        for endp, binding in self.idp.config.getattr(
                "endpoints", "idp")["single_sign_on_service"]:
            p = urlparse(endp)
            url_map.append(
                ("^%s/(.*)$" % p.path[1:], ("IDP", "handle_authn_request",
                                            service.BINDING_MAP[binding])))
            url_map.append(
                ("^%s$" % p.path[1:], ("IDP", "handle_authn_request",
                                       service.BINDING_MAP[binding])))

        return url_map