1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4
5"""Contains classes and functions that a SAML2.0 Identity provider (IdP)
6or attribute authority (AA) may use to conclude its tasks.
7"""
8import logging
9import os
10
11import importlib
12import dbm
13import shelve
14import six
15import threading
16
17import saml2.cryptography.symmetric
18from saml2 import saml
19from saml2 import element_to_extension_element
20from saml2 import class_name
21from saml2 import BINDING_HTTP_REDIRECT
22from saml2.argtree import add_path, is_set
23
24from saml2.entity import Entity
25from saml2.eptid import Eptid
26from saml2.eptid import EptidShelve
27from saml2.samlp import NameIDMappingResponse
28from saml2.sdb import SessionStorage
29from saml2.schema import soapenv
30
31from saml2.request import AuthnRequest
32from saml2.request import AssertionIDRequest
33from saml2.request import AttributeQuery
34from saml2.request import NameIDMappingRequest
35from saml2.request import AuthzDecisionQuery
36from saml2.request import AuthnQuery
37
38from saml2.s_utils import MissingValue
39from saml2.s_utils import rndstr
40from saml2.s_utils import Unknown
41
42from saml2.sigver import pre_signature_part
43from saml2.sigver import signed_instance_factory
44from saml2.sigver import CertificateError
45
46from saml2.assertion import Assertion
47from saml2.assertion import Policy
48from saml2.assertion import restriction_from_attribute_spec
49from saml2.assertion import filter_attribute_value_assertions
50
51from saml2.ident import IdentDB, decode
52from saml2.profile import ecp
53
54logger = logging.getLogger(__name__)
55
56AUTHN_DICT_MAP = {
57    "decl": "authn_decl",
58    "authn_auth": "authn_auth",
59    "class_ref": "authn_class",
60    "authn_instant": "authn_instant",
61    "subject_locality": "subject_locality"
62}
63
64
65def _shelve_compat(name, *args, **kwargs):
66    try:
67        return shelve.open(name, *args, **kwargs)
68    except dbm.error[0]:
69        # Python 3 whichdb needs to try .db to determine type
70        if name.endswith('.db'):
71            name = name.rsplit('.db', 1)[0]
72            return shelve.open(name, *args, **kwargs)
73        else:
74            raise
75
76
77class Server(Entity):
78    """ A class that does things that IdPs or AAs do """
79
80    def __init__(
81        self,
82        config_file="",
83        config=None,
84        cache=None,
85        stype="idp",
86        symkey="",
87        msg_cb=None,
88    ):
89        Entity.__init__(self, stype, config, config_file, msg_cb=msg_cb)
90        self.eptid = None
91        self.init_config(stype)
92        self.cache = cache
93        self.ticket = {}
94        self.session_db = self.choose_session_storage()
95        if symkey:
96            self.symkey = symkey.encode()
97        else:
98            self.symkey = saml2.cryptography.symmetric.Default.generate_key()
99        self.seed = rndstr()
100        self.lock = threading.Lock()
101
102    def getvalid_certificate_str(self):
103        if self.sec.cert_handler is not None:
104            return self.sec.cert_handler._last_validated_cert
105        return None
106
107    def support_AssertionIDRequest(self):
108        return True
109
110    def support_AuthnQuery(self):
111        return True
112
113    def choose_session_storage(self):
114        _spec = self.config.getattr("session_storage", "idp")
115        if not _spec:
116            return SessionStorage()
117        elif isinstance(_spec, six.string_types):
118            if _spec.lower() == "memory":
119                return SessionStorage()
120        else:  # Should be tuple
121            typ, data = _spec
122            if typ.lower() == "mongodb":
123                from saml2.mongo_store import SessionStorageMDB
124                return SessionStorageMDB(database=data, collection="session")
125
126        raise NotImplementedError("No such storage type implemented")
127
128    def init_config(self, stype="idp"):
129        """ Remaining init of the server configuration
130
131        :param stype: The type of Server ("idp"/"aa")
132        """
133        if stype == "aa":
134            return
135
136        # subject information is stored in a database
137        # default database is in memory which is OK in some setups
138        dbspec = self.config.getattr("subject_data", "idp")
139        idb = None
140        typ = ""
141        if not dbspec:
142            idb = {}
143        elif isinstance(dbspec, six.string_types):
144            idb = _shelve_compat(dbspec, writeback=True, protocol=2)
145        else:  # database spec is a a 2-tuple (type, address)
146            # print(>> sys.stderr, "DBSPEC: %s" % (dbspec,))
147            (typ, addr) = dbspec
148            if typ == "shelve":
149                idb = _shelve_compat(addr, writeback=True, protocol=2)
150            elif typ == "memcached":
151                import memcache
152                idb = memcache.Client(addr)
153            elif typ == "dict":  # in-memory dictionary
154                idb = {}
155            elif typ == "mongodb":
156                from saml2.mongo_store import IdentMDB
157                self.ident = IdentMDB(database=addr, collection="ident")
158            elif typ == "identdb":
159                mod, clas = addr.rsplit('.', 1)
160                mod = importlib.import_module(mod)
161                self.ident = getattr(mod, clas)()
162
163        if typ == "mongodb" or typ == "identdb":
164            pass
165        elif idb is not None:
166            self.ident = IdentDB(idb)
167        elif dbspec:
168            raise Exception("Couldn't open identity database: %s" %
169                            (dbspec,))
170
171        try:
172            _domain = self.config.getattr("domain", "idp")
173            if _domain:
174                self.ident.domain = _domain
175
176            self.ident.name_qualifier = self.config.entityid
177
178            dbspec = self.config.getattr("edu_person_targeted_id", "idp")
179            if not dbspec:
180                pass
181            else:
182                typ = dbspec[0]
183                addr = dbspec[1]
184                secret = dbspec[2]
185                if typ == "shelve":
186                    self.eptid = EptidShelve(secret, addr)
187                elif typ == "mongodb":
188                    from saml2.mongo_store import EptidMDB
189                    self.eptid = EptidMDB(secret, database=addr,
190                                          collection="eptid")
191                else:
192                    self.eptid = Eptid(secret)
193        except Exception:
194            self.ident.close()
195            raise
196
197    def wants(self, sp_entity_id, index=None):
198        """ Returns what attributes the SP requires and which are optional
199        if any such demands are registered in the Metadata.
200
201        :param sp_entity_id: The entity id of the SP
202        :param index: which of the attribute consumer services its all about
203            if index == None then all attribute consumer services are clumped
204            together.
205        :return: 2-tuple, list of required and list of optional attributes
206        """
207        return self.metadata.attribute_requirement(sp_entity_id, index)
208
209    def verify_assertion_consumer_service(self, request):
210        _acs = request.assertion_consumer_service_url
211        _aci = request.assertion_consumer_service_index
212        _binding = request.protocol_binding
213        _eid = request.issuer.text
214        if _acs:
215            # look up acs in for that binding in the metadata given the issuer
216            # Assuming the format is entity
217            for acs in self.metadata.assertion_consumer_service(_eid, _binding):
218                if _acs == acs.text:
219                    return True
220        elif _aci:
221            for acs in self.metadata.assertion_consumer_service(_eid, _binding):
222                if _aci == acs.index:
223                    return True
224
225        return False
226
227    # -------------------------------------------------------------------------
228
229    def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT,
230                            relay_state=None, sigalg=None, signature=None):
231        """Parse a Authentication Request
232
233        :param enc_request: The request in its transport format
234        :param binding: Which binding that was used to transport the message
235        :param relay_state: RelayState, when binding=redirect
236        :param sigalg: Signature Algorithm, when binding=redirect
237        :param signature: Signature, when binding=redirect
238            to this entity.
239        :return: A request instance
240        """
241
242        return self._parse_request(enc_request, AuthnRequest,
243                                   "single_sign_on_service", binding,
244                                   relay_state=relay_state, sigalg=sigalg,
245                                   signature=signature)
246
247    def parse_attribute_query(self, xml_string, binding):
248        """ Parse an attribute query
249
250        :param xml_string: The Attribute Query as an XML string
251        :param binding: Which binding that was used for the request
252        :return: A query instance
253        """
254
255        return self._parse_request(xml_string, AttributeQuery,
256                                   "attribute_service", binding)
257
258    def parse_authz_decision_query(self, xml_string, binding):
259        """ Parse an authorization decision query
260
261        :param xml_string: The Authz decision Query as an XML string
262        :param binding: Which binding that was used when receiving this query
263        :return: Query instance
264        """
265
266        return self._parse_request(xml_string, AuthzDecisionQuery,
267                                   "authz_service", binding)
268
269    def parse_assertion_id_request(self, xml_string, binding):
270        """ Parse an assertion id query
271
272        :param xml_string: The AssertionIDRequest as an XML string
273        :param binding: Which binding that was used when receiving this request
274        :return: Query instance
275        """
276
277        return self._parse_request(xml_string, AssertionIDRequest,
278                                   "assertion_id_request_service", binding)
279
280    def parse_authn_query(self, xml_string, binding):
281        """ Parse an authn query
282
283        :param xml_string: The AuthnQuery as an XML string
284        :param binding: Which binding that was used when receiving this query
285        :return: Query instance
286        """
287
288        return self._parse_request(xml_string, AuthnQuery,
289                                   "authn_query_service", binding)
290
291    def parse_name_id_mapping_request(self, xml_string, binding):
292        """ Parse a nameid mapping request
293
294        :param xml_string: The NameIDMappingRequest as an XML string
295        :param binding: Which binding that was used when receiving this request
296        :return: Query instance
297        """
298
299        return self._parse_request(xml_string, NameIDMappingRequest,
300                                   "name_id_mapping_service", binding)
301
302    @staticmethod
303    def update_farg(in_response_to, consumer_url, farg=None):
304        if not farg:
305            farg = add_path(
306                {},
307                ['assertion', 'subject', 'subject_confirmation', 'method',
308                 saml.SCM_BEARER])
309            add_path(
310                farg['assertion']['subject']['subject_confirmation'],
311                ['subject_confirmation_data', 'in_response_to', in_response_to])
312            add_path(
313                farg['assertion']['subject']['subject_confirmation'],
314                ['subject_confirmation_data', 'recipient', consumer_url])
315        else:
316            if not is_set(farg,
317                          ['assertion', 'subject', 'subject_confirmation',
318                           'method']):
319                add_path(farg,
320                         ['assertion', 'subject', 'subject_confirmation',
321                          'method', saml.SCM_BEARER])
322            if not is_set(farg,
323                          ['assertion', 'subject', 'subject_confirmation',
324                           'subject_confirmation_data', 'in_response_to']):
325                add_path(farg,
326                         ['assertion', 'subject', 'subject_confirmation',
327                          'subject_confirmation_data', 'in_response_to',
328                          in_response_to])
329            if not is_set(farg, ['assertion', 'subject', 'subject_confirmation',
330                                 'subject_confirmation_data', 'recipient']):
331                add_path(farg,
332                         ['assertion', 'subject', 'subject_confirmation',
333                          'subject_confirmation_data', 'recipient',
334                          consumer_url])
335        return farg
336
337    def setup_assertion(
338        self,
339        authn,
340        sp_entity_id,
341        in_response_to,
342        consumer_url,
343        name_id,
344        policy,
345        _issuer,
346        authn_statement,
347        identity,
348        best_effort,
349        sign_response,
350        farg=None,
351        session_not_on_or_after=None,
352        sign_alg=None,
353        digest_alg=None,
354        **kwargs,
355    ):
356        """
357        Construct and return the Assertion
358
359        :param authn: Authentication information
360        :param sp_entity_id:
361        :param in_response_to: The ID of the request this is an answer to
362        :param consumer_url: The recipient of the assertion
363        :param name_id: The NameID of the subject
364        :param policy: Assertion policies
365        :param _issuer: Issuer of the statement
366        :param authn_statement: An AuthnStatement instance
367        :param identity: Identity information about the Subject
368        :param best_effort: Even if not the SPs demands can be met send a
369            response.
370        :param sign_response: Sign the response, only applicable if
371            ErrorResponse
372        :param kwargs: Extra keyword arguments
373        :return: An Assertion instance
374        """
375
376        ast = Assertion(identity)
377        ast.acs = self.config.getattr("attribute_converters")
378        if policy is None:
379            policy = Policy(mds=self.metadata)
380        try:
381            ast.apply_policy(sp_entity_id, policy)
382        except MissingValue as exc:
383            if not best_effort:
384                response = self.create_error_response(
385                    in_response_to,
386                    destination=consumer_url,
387                    info=exc,
388                    sign=sign_response,
389                    sign_alg=sign_alg,
390                    digest_alg=digest_alg,
391                )
392                return str(response).split("\n")
393
394        farg = self.update_farg(in_response_to, consumer_url, farg)
395
396        if authn:  # expected to be a dictionary
397            # Would like to use dict comprehension but ...
398            authn_args = dict(
399                [(AUTHN_DICT_MAP[k], v) for k, v in authn.items() if
400                 k in AUTHN_DICT_MAP])
401            authn_args.update(kwargs)
402
403            assertion = ast.construct(
404                sp_entity_id, self.config.attribute_converters, policy,
405                issuer=_issuer, farg=farg['assertion'], name_id=name_id,
406                session_not_on_or_after=session_not_on_or_after,
407                **authn_args)
408
409        elif authn_statement:  # Got a complete AuthnStatement
410            assertion = ast.construct(
411                sp_entity_id, self.config.attribute_converters, policy,
412                issuer=_issuer, authn_statem=authn_statement,
413                farg=farg['assertion'], name_id=name_id,
414                **kwargs)
415        else:
416            assertion = ast.construct(
417                sp_entity_id, self.config.attribute_converters, policy,
418                issuer=_issuer, farg=farg['assertion'], name_id=name_id,
419                session_not_on_or_after=session_not_on_or_after,
420                **kwargs)
421        return assertion
422
423    # XXX DONE calls pre_signature_part
424    # XXX calls _response
425    def _authn_response(
426        self,
427        in_response_to,
428        consumer_url,
429        sp_entity_id,
430        identity=None,
431        name_id=None,
432        status=None,
433        authn=None,
434        issuer=None,
435        policy=None,
436        sign_assertion=None,
437        sign_response=None,
438        best_effort=False,
439        encrypt_assertion=False,
440        encrypt_cert_advice=None,
441        encrypt_cert_assertion=None,
442        authn_statement=None,
443        encrypt_assertion_self_contained=False,
444        encrypted_advice_attributes=False,
445        pefim=False,
446        sign_alg=None,
447        digest_alg=None,
448        farg=None,
449        session_not_on_or_after=None,
450    ):
451        """ Create a response. A layer of indirection.
452
453        :param in_response_to: The session identifier of the request
454        :param consumer_url: The URL which should receive the response
455        :param sp_entity_id: The entity identifier of the SP
456        :param identity: A dictionary with attributes and values that are
457            expected to be the bases for the assertion in the response.
458        :param name_id: The identifier of the subject
459        :param status: The status of the response
460        :param authn: A dictionary containing information about the
461            authn context.
462        :param issuer: The issuer of the response
463        :param policy:
464        :param sign_assertion: Whether the assertion should be signed or not
465        :param sign_response: Whether the response should be signed or not
466        :param best_effort: Even if not the SPs demands can be met send a
467            response.
468        :param encrypt_assertion: True if assertions should be encrypted.
469        :param encrypt_assertion_self_contained: True if all encrypted
470        assertions should have alla namespaces
471        selfcontained.
472        :param encrypted_advice_attributes: True if assertions in the advice
473        element should be encrypted.
474        :param encrypt_cert_advice: Certificate to be used for encryption of
475        assertions in the advice element.
476        :param encrypt_cert_assertion: Certificate to be used for encryption
477        of assertions.
478        :param authn_statement: Authentication statement.
479        :param pefim: True if a response according to the PEFIM profile
480        should be created.
481        :param farg: Argument to pass on to the assertion constructor
482        :return: A response instance
483        """
484
485        if farg is None:
486            assertion_args = {}
487
488        # if identity:
489        _issuer = self._issuer(issuer)
490
491        # if encrypt_assertion and show_nameid:
492        #    tmp_name_id = name_id
493        #    name_id = None
494        #    name_id = None
495        #    tmp_authn = authn
496        #    authn = None
497        #    tmp_authn_statement = authn_statement
498        #    authn_statement = None
499
500        if pefim:
501            encrypted_advice_attributes = True
502            encrypt_assertion_self_contained = True
503            assertion_attributes = self.setup_assertion(
504                None, sp_entity_id, None, None, None, policy, None, None,
505                identity, best_effort, sign_response, farg=farg)
506            assertion = self.setup_assertion(
507                authn, sp_entity_id, in_response_to, consumer_url, name_id,
508                policy, _issuer, authn_statement, [], True, sign_response,
509                farg=farg, session_not_on_or_after=session_not_on_or_after)
510            assertion.advice = saml.Advice()
511
512            # assertion.advice.assertion_id_ref.append(saml.AssertionIDRef())
513            # assertion.advice.assertion_uri_ref.append(saml.AssertionURIRef())
514            assertion.advice.assertion.append(assertion_attributes)
515        else:
516            assertion = self.setup_assertion(
517                authn, sp_entity_id, in_response_to, consumer_url, name_id,
518                policy, _issuer, authn_statement, identity, True,
519                sign_response, farg=farg,
520                session_not_on_or_after=session_not_on_or_after)
521
522        to_sign = []
523        if not encrypt_assertion:
524            if sign_assertion:
525                # XXX self.signing_algorithm self.digest_algorithm defined by entity
526                # XXX this should be handled through entity.py
527                # XXX sig/digest-allowed should be configurable
528                sign_alg = sign_alg or self.signing_algorithm
529                digest_alg = digest_alg or self.digest_algorithm
530
531                assertion.signature = pre_signature_part(
532                    assertion.id,
533                    self.sec.my_cert,
534                    2,
535                    sign_alg=sign_alg,
536                    digest_alg=digest_alg,
537                )
538                to_sign.append((class_name(assertion), assertion.id))
539
540        if (self.support_AssertionIDRequest() or self.support_AuthnQuery()):
541            self.session_db.store_assertion(assertion, to_sign)
542
543        return self._response(
544            in_response_to,
545            consumer_url,
546            status,
547            issuer,
548            sign_response,
549            to_sign,
550            sp_entity_id=sp_entity_id,
551            encrypt_assertion=encrypt_assertion,
552            encrypt_cert_advice=encrypt_cert_advice,
553            encrypt_cert_assertion=encrypt_cert_assertion,
554            encrypt_assertion_self_contained=encrypt_assertion_self_contained,
555            encrypted_advice_attributes=encrypted_advice_attributes,
556            sign_assertion=sign_assertion,
557            pefim=pefim,
558            sign_alg=sign_alg,
559            digest_alg=digest_alg,
560            assertion=assertion,
561        )
562
563    # ------------------------------------------------------------------------
564
565    # XXX DONE idp create > _response
566    def create_attribute_response(
567        self,
568        identity,
569        in_response_to,
570        destination,
571        sp_entity_id,
572        userid="",
573        name_id=None,
574        status=None,
575        issuer=None,
576        sign_assertion=None,
577        sign_response=None,
578        attributes=None,
579        sign_alg=None,
580        digest_alg=None,
581        farg=None,
582        **kwargs,
583    ):
584        """ Create an attribute assertion response.
585
586        :param identity: A dictionary with attributes and values that are
587            expected to be the bases for the assertion in the response.
588        :param in_response_to: The session identifier of the request
589        :param destination: The URL which should receive the response
590        :param sp_entity_id: The entity identifier of the SP
591        :param userid: A identifier of the user
592        :param name_id: The identifier of the subject
593        :param status: The status of the response
594        :param issuer: The issuer of the response
595        :param sign_assertion: Whether the assertion should be signed or not
596        :param sign_response: Whether the whole response should be signed
597        :param attributes:
598        :param kwargs: To catch extra keyword arguments
599        :return: A response instance
600        """
601
602        policy = self.config.getattr("policy", "aa")
603
604        if not name_id and userid:
605            try:
606                name_id = self.ident.construct_nameid(userid, policy, sp_entity_id)
607                logger.warning("Unspecified NameID format")
608            except Exception:
609                pass
610
611        to_sign = []
612
613        if identity:
614            farg = self.update_farg(in_response_to, sp_entity_id, farg=farg)
615
616            _issuer = self._issuer(issuer)
617            ast = Assertion(identity)
618            if policy:
619                ast.apply_policy(sp_entity_id, policy)
620            else:
621                policy = Policy(mds=self.metadata)
622
623            if attributes:
624                restr = restriction_from_attribute_spec(attributes)
625                ast = filter_attribute_value_assertions(ast)
626
627            assertion = ast.construct(
628                sp_entity_id, self.config.attribute_converters, policy,
629                issuer=_issuer, name_id=name_id,
630                farg=farg['assertion'])
631
632        return self._response(
633            in_response_to,
634            destination,
635            status,
636            issuer,
637            sign_response,
638            to_sign,
639            sign_assertion=sign_assertion,
640            sign_alg=sign_alg,
641            digest_alg=digest_alg,
642            assertion=assertion,
643            sp_entity_id=sp_entity_id,
644            **kwargs,
645        )
646
647    def gather_authn_response_args(
648        self, sp_entity_id, name_id_policy, userid, **kwargs
649    ):
650        kwargs["policy"] = kwargs.get("release_policy")
651
652        # collect args and return them
653        args = {}
654
655        # XXX will be passed to _authn_response
656        param_defaults = {
657            'policy': None,
658            'best_effort': False,
659            'sign_assertion': False,
660            'sign_response': False,
661            'encrypt_assertion': False,
662            'encrypt_assertion_self_contained': True,
663            'encrypted_advice_attributes': False,
664            'encrypt_cert_advice': None,
665            'encrypt_cert_assertion': None,
666            # need to be named sign_alg and digest_alg
667        }
668        for param, val_default in param_defaults.items():
669            val_kw = kwargs.get(param)
670            val_config = self.config.getattr(param, "idp")
671            args[param] = (
672                val_kw
673                if val_kw is not None
674                else val_config
675                if val_config is not None
676                else val_default
677            )
678
679        for arg, attr, eca, pefim in [
680            ('encrypted_advice_attributes', 'verify_encrypt_cert_advice',
681             'encrypt_cert_advice', kwargs["pefim"]),
682            ('encrypt_assertion', 'verify_encrypt_cert_assertion',
683             'encrypt_cert_assertion', False)]:
684
685            if args[arg] or pefim:
686                _enc_cert = self.config.getattr(attr, "idp")
687
688                if _enc_cert is not None:
689                    if kwargs[eca] is None:
690                        raise CertificateError(
691                            "No SPCertEncType certificate for encryption "
692                            "contained in authentication "
693                            "request.")
694                    if not _enc_cert(kwargs[eca]):
695                        raise CertificateError(
696                            "Invalid certificate for encryption!")
697
698        if 'name_id' not in kwargs or not kwargs['name_id']:
699            nid_formats = []
700            for _sp in self.metadata[sp_entity_id]["spsso_descriptor"]:
701                if "name_id_format" in _sp:
702                    nid_formats.extend([n["text"] for n in
703                                        _sp["name_id_format"]])
704            try:
705                snq = name_id_policy.sp_name_qualifier
706            except AttributeError:
707                snq = sp_entity_id
708
709            if not snq:
710                snq = sp_entity_id
711
712            kwa = {"sp_name_qualifier": snq}
713
714            try:
715                kwa["format"] = name_id_policy.format
716            except AttributeError:
717                pass
718
719            _nids = self.ident.find_nameid(userid, **kwa)
720            # either none or one
721            if _nids:
722                args['name_id'] = _nids[0]
723            else:
724                args['name_id'] = self.ident.construct_nameid(
725                    userid, args['policy'], sp_entity_id, name_id_policy)
726                logger.debug("construct_nameid: %s => %s", userid,
727                             args['name_id'])
728        else:
729            args['name_id'] = kwargs['name_id']
730
731        for param in ['status', 'farg']:
732            try:
733                args[param] = kwargs[param]
734            except KeyError:
735                pass
736
737        return args
738
739    # XXX DONE idp create > _authn_response > _response
740    def create_authn_response(
741        self,
742        identity,
743        in_response_to,
744        destination,
745        sp_entity_id,
746        name_id_policy=None,
747        userid=None,
748        name_id=None,
749        authn=None,
750        issuer=None,
751        sign_response=None,
752        sign_assertion=None,
753        encrypt_cert_advice=None,
754        encrypt_cert_assertion=None,
755        encrypt_assertion=None,
756        encrypt_assertion_self_contained=True,
757        encrypted_advice_attributes=False,
758        pefim=False,
759        sign_alg=None,
760        digest_alg=None,
761        session_not_on_or_after=None,
762        **kwargs,
763    ):
764        """ Constructs an AuthenticationResponse
765
766        :param identity: Information about an user
767        :param in_response_to: The identifier of the authentication request
768            this response is an answer to.
769        :param destination: Where the response should be sent
770        :param sp_entity_id: The entity identifier of the Service Provider
771        :param name_id_policy: How the NameID should be constructed
772        :param userid: The subject identifier
773        :param name_id: The identifier of the subject. A saml.NameID instance.
774        :param authn: Dictionary with information about the authentication
775            context
776        :param issuer: Issuer of the response
777        :param sign_assertion: Whether the assertion should be signed or not.
778        :param sign_response: Whether the response should be signed or not.
779        :param encrypt_assertion: True if assertions should be encrypted.
780        :param encrypt_assertion_self_contained: True if all encrypted
781        assertions should have alla namespaces
782        selfcontained.
783        :param encrypted_advice_attributes: True if assertions in the advice
784        element should be encrypted.
785        :param encrypt_cert_advice: Certificate to be used for encryption of
786        assertions in the advice element.
787        :param encrypt_cert_assertion: Certificate to be used for encryption
788        of assertions.
789        :param pefim: True if a response according to the PEFIM profile
790        should be created.
791        :return: A response instance
792        """
793
794        try:
795            args = self.gather_authn_response_args(
796                sp_entity_id,
797                name_id_policy=name_id_policy,
798                userid=userid,
799                name_id=name_id,
800                sign_response=sign_response,
801                sign_assertion=sign_assertion,
802                encrypt_cert_advice=encrypt_cert_advice,
803                encrypt_cert_assertion=encrypt_cert_assertion,
804                encrypt_assertion=encrypt_assertion,
805                encrypt_assertion_self_contained=encrypt_assertion_self_contained,
806                encrypted_advice_attributes=encrypted_advice_attributes,
807                pefim=pefim,
808                **kwargs,
809            )
810        except IOError as exc:
811            response = self.create_error_response(
812                in_response_to,
813                destination=destination,
814                info=exc,
815                sign=sign_response,
816                sign_alg=sign_alg,
817                digest_alg=digest_alg,
818            )
819            return str(response).split("\n")
820
821        try:
822            _authn = authn
823            return self._authn_response(
824                in_response_to,
825                destination,
826                sp_entity_id,
827                identity,
828                authn=_authn,
829                issuer=issuer,
830                pefim=pefim,
831                sign_alg=sign_alg,
832                digest_alg=digest_alg,
833                session_not_on_or_after=session_not_on_or_after,
834                **args,
835            )
836        except MissingValue as exc:
837            return self.create_error_response(
838                in_response_to,
839                destination=destination,
840                info=exc,
841                sign=sign_response,
842                sign_alg=sign_alg,
843                digest_alg=digest_alg,
844            )
845
846    # XXX DONE idp create > create_authn_response > _authn_response > _response
847    def create_authn_request_response(
848        self,
849        identity,
850        in_response_to,
851        destination,
852        sp_entity_id,
853        name_id_policy=None,
854        userid=None,
855        name_id=None,
856        authn=None,
857        authn_decl=None,
858        issuer=None,
859        sign_response=None,
860        sign_assertion=None,
861        session_not_on_or_after=None,
862        sign_alg=None,
863        digest_alg=None,
864        **kwargs,
865    ):
866        return self.create_authn_response(
867            identity,
868            in_response_to,
869            destination,
870            sp_entity_id,
871            name_id_policy,
872            userid,
873            name_id,
874            authn,
875            issuer,
876            sign_response,
877            sign_assertion,
878            authn_decl=authn_decl,
879            session_not_on_or_after=session_not_on_or_after,
880            sign_alg=sign_alg,
881            digest_alg=digest_alg,
882        )
883
884    # XXX DONE calls pre_signature_part
885    # XXX DONE idp create > [...]
886    def create_assertion_id_request_response(
887        self, assertion_id, sign=None, sign_alg=None, digest_alg=None, **kwargs
888    ):
889        try:
890            (assertion, to_sign) = self.session_db.get_assertion(assertion_id)
891        except KeyError:
892            raise Unknown
893
894        if to_sign:
895            if assertion.signature is None:
896                # XXX self.signing_algorithm self.digest_algorithm defined by entity
897                # XXX this should be handled through entity.py
898                # XXX sig/digest-allowed should be configurable
899                sign_alg = sign_alg or self.signing_algorithm
900                digest_alg = digest_alg or self.digest_algorithm
901
902                assertion.signature = pre_signature_part(
903                    assertion.id,
904                    self.sec.my_cert,
905                    1,
906                    sign_alg=sign_alg,
907                    digest_alg=digest_alg,
908                )
909            return signed_instance_factory(assertion, self.sec, to_sign)
910        else:
911            return assertion
912
913    # XXX calls self.sign without ensuring sign
914    # XXX calls self.sign => should it call _message (which calls self.sign)?
915    # XXX idp create > NameIDMappingResponse & sign?
916    def create_name_id_mapping_response(
917        self,
918        name_id=None,
919        encrypted_id=None,
920        in_response_to=None,
921        issuer=None,
922        sign_response=None,
923        status=None,
924        sign_alg=None,
925        digest_alg=None,
926        **kwargs,
927    ):
928        """
929        protocol for mapping a principal's name identifier into a
930        different name identifier for the same principal.
931        Done over soap.
932
933        :param name_id:
934        :param encrypted_id:
935        :param in_response_to:
936        :param issuer:
937        :param sign_response:
938        :param status:
939        :return:
940        """
941        # Done over SOAP
942
943        ms_args = self.message_args()
944
945        _resp = NameIDMappingResponse(
946            name_id, encrypted_id, in_response_to=in_response_to, **ms_args
947        )
948
949        if sign_response:
950            return self.sign(_resp, sign_alg=sign_alg, digest_alg=digest_alg)
951        else:
952            logger.info("Message: %s", _resp)
953            return _resp
954
955    # XXX DONE idp create > _response
956    def create_authn_query_response(
957        self,
958        subject,
959        session_index=None,
960        requested_context=None,
961        in_response_to=None,
962        issuer=None,
963        sign_response=None,
964        status=None,
965        sign_alg=None,
966        digest_alg=None,
967        **kwargs,
968    ):
969        """
970        A successful <Response> will contain one or more assertions containing
971        authentication statements.
972
973        :return:
974        """
975
976        margs = self.message_args()
977        asserts = [
978            saml.Assertion(authn_statement=statement, subject=subject, **margs)
979            for statement in self.session_db.get_authn_statements(
980                subject.name_id, session_index, requested_context
981            )
982        ]
983
984        if asserts:
985            args = {"assertion": asserts}
986        else:
987            args = {}
988
989        return self._response(
990            in_response_to,
991            "",
992            status,
993            issuer,
994            sign_response,
995            to_sign=[],
996            sign_alg=sign_alg,
997            digest_alg=digest_alg,
998            **args,
999        )
1000
1001    # ---------
1002
1003    def parse_ecp_authn_request(self):
1004        pass
1005
1006    # XXX DONE idp create > create_authn_response > _authn_response > _response
1007    def create_ecp_authn_request_response(
1008        self,
1009        acs_url,
1010        identity,
1011        in_response_to,
1012        destination,
1013        sp_entity_id,
1014        name_id_policy=None,
1015        userid=None,
1016        name_id=None,
1017        authn=None,
1018        issuer=None,
1019        sign_response=None,
1020        sign_assertion=None,
1021        sign_alg=None,
1022        digest_alg=None,
1023        **kwargs,
1024    ):
1025
1026        # ----------------------------------------
1027        # <ecp:Response
1028        # ----------------------------------------
1029
1030        ecp_response = ecp.Response(assertion_consumer_service_url=acs_url)
1031        header = soapenv.Header()
1032        header.extension_elements = [element_to_extension_element(ecp_response)]
1033
1034        # ----------------------------------------
1035        # <samlp:Response
1036        # ----------------------------------------
1037
1038        response = self.create_authn_response(
1039            identity,
1040            in_response_to,
1041            destination,
1042            sp_entity_id,
1043            name_id_policy,
1044            userid,
1045            name_id,
1046            authn,
1047            issuer,
1048            sign_response,
1049            sign_assertion,
1050            sign_alg=sign_alg,
1051            digest_alg=digest_alg
1052        )
1053        body = soapenv.Body()
1054        body.extension_elements = [element_to_extension_element(response)]
1055
1056        soap_envelope = soapenv.Envelope(header=header, body=body)
1057
1058        return str(soap_envelope)
1059
1060    def close(self):
1061        self.ident.close()
1062
1063    def clean_out_user(self, name_id):
1064        """
1065        Remove all authentication statements that belongs to a user identified
1066        by a NameID instance
1067
1068        :param name_id: NameID instance
1069        :return: The local identifier for the user
1070        """
1071
1072        lid = self.ident.find_local_id(name_id)
1073        logger.info("Clean out %s", lid)
1074
1075        # remove the authentications
1076        try:
1077            for _nid in [decode(x) for x in self.ident.db[lid].split(" ")]:
1078                try:
1079                    self.session_db.remove_authn_statements(_nid)
1080                except KeyError:
1081                    pass
1082        except KeyError:
1083            pass
1084
1085        return lid
1086