Example #1
0
def get_saml2_config(module_path):

    module = imp.load_source('saml2_settings', module_path)

    conf = SPConfig()
    conf.load(module.SAML_CONFIG)
    return conf
Example #2
0
def get_saml2_config(module_path):

    module = imp.load_source('saml2_settings', module_path)

    conf = SPConfig()
    conf.load(module.SAML_CONFIG)
    return conf
Example #3
0
def test_config_loader_with_real_conf(request):
    config = SPConfig()
    config.load(
        conf.create_conf(sp_host='sp.example.com',
                         idp_hosts=['idp.example.com'],
                         metadata_file='remote_metadata_one_idp.xml'))
    return config
Example #4
0
def config_settings_loader(request=None):
    """Utility function to load the pysaml2 configuration.

    This is also the default config loader.
    """
    conf = SPConfig()
    conf.load(copy.deepcopy(settings.SAML_CONFIG))
    return conf
Example #5
0
def config_settings_loader(request: Optional[HttpRequest] = None) -> SPConfig:
    """ Utility function to load the pysaml2 configuration.
        The configuration can be modified based on the request being passed.
        This is the default config loader, which just loads the config from the settings.
    """
    conf = SPConfig()
    conf.load(copy.deepcopy(settings.SAML_CONFIG))
    return conf
Example #6
0
def config_settings_loader(request=None):
    """Utility function to load the pysaml2 configuration.

    This is also the default config loader.
    """
    conf = SPConfig()
    conf.load(copy.deepcopy(settings.SAML_CONFIG))
    return conf
Example #7
0
def test_ecp():
    cnf = SPConfig()
    cnf.load(ECP_SP)
    assert cnf.endpoint("assertion_consumer_service") == ["http://lingon.catalogix.se:8087/"]
    eid = cnf.ecp_endpoint("130.239.16.3")
    assert eid == "http://example.com/idp"
    eid = cnf.ecp_endpoint("130.238.20.20")
    assert eid is None
Example #8
0
def test_ecp():
    cnf = SPConfig()
    cnf.load(ECP_SP)
    assert cnf.endpoint("assertion_consumer_service") == \
           ["http://lingon.catalogix.se:8087/"]
    eid = cnf.ecp_endpoint("130.239.16.3")
    assert eid == "http://example.com/idp"
    eid = cnf.ecp_endpoint("130.238.20.20")
    assert eid is None
Example #9
0
def create_logout_request(subject_id, destination, issuer_entity_id,
        req_entity_id, sign=True):
    config = SPConfig()
    config.load(sp_config)
    sp_client = Saml2Client(config=config)
    # construct a request
    logout_request = samlp.LogoutRequest(
        id='a123456',
        version=VERSION,
        destination=destination,
        issuer=saml.Issuer(text=req_entity_id,
            format=saml.NAMEID_FORMAT_ENTITY),
        name_id=saml.NameID(text=subject_id))
    return logout_request
Example #10
0
def get_saml_login_request(binding=BINDING_HTTP_REDIRECT):
    conf = SPConfig()
    conf.load(copy.deepcopy(sp_conf_dict))
    client = Saml2Client(conf)
    if binding == BINDING_HTTP_REDIRECT:
        session_id, result = client.prepare_for_authenticate(
            entityid="test_generic_idp",
            relay_state="",
            binding=binding,
        )
        return parse.parse_qs(parse.urlparse(
            result['headers'][0][1]).query)['SAMLRequest'][0]
    elif binding == BINDING_HTTP_POST:
        session_id, request_xml = client.create_authn_request(
            "http://localhost:9000/idp/sso/post", binding=binding)
    return base64.b64encode(bytes(request_xml, 'UTF-8'))
Example #11
0
 def _saml2_config(self):
     if self._v_config is None:
         sp_config = self._saml2_config_template()
         sp_config['metadata']['local'] = [self.saml2_idp_configfile]
         sp_config['entityid'] = self.saml2_sp_entityid
         sp_config['service']['sp']['name'] = self.saml2_sp_entityid
         sp_config['service']['sp']['url'] = self.saml2_sp_url
         sp_config['service']['sp']['endpoints']['assertion_consumer_service'] = [self.saml2_sp_url,]
         sp_config['service']['sp']['endpoints']['single_logout_service'] = ['%s/logout' % self.saml2_sp_url, BINDING_HTTP_REDIRECT]
         sp_config['service']['sp']['url'] = self.saml2_sp_url
         sp_config['xmlsec_binary'] = self.saml2_xmlsec
         config = SPConfig()
         conf=sp_config.copy()
         config.load(conf)
         self._v_config = config
     return self._v_config
Example #12
0
def create_logout_request(subject_id,
                          destination,
                          issuer_entity_id,
                          req_entity_id,
                          sign=True):
    config = SPConfig()
    config.load(sp_config)
    sp_client = Saml2Client(config=config)
    # construct a request
    logout_request = samlp.LogoutRequest(id='a123456',
                                         version=VERSION,
                                         destination=destination,
                                         issuer=saml.Issuer(
                                             text=req_entity_id,
                                             format=saml.NAMEID_FORMAT_ENTITY),
                                         name_id=saml.NameID(text=subject_id))
    return logout_request
Example #13
0
 def _saml2_config(self):
     if self._v_config is None:
         sp_config = self._saml2_config_template()
         sp_config["metadata"]["local"] = [self.saml2_idp_configfile]
         sp_config["entityid"] = self.saml2_sp_entityid
         sp_config["service"]["sp"]["name"] = self.saml2_sp_entityid
         sp_config["service"]["sp"]["url"] = self.saml2_sp_url
         sp_config["service"]["sp"]["endpoints"]["assertion_consumer_service"] = [self.saml2_sp_url]
         sp_config["service"]["sp"]["endpoints"]["single_logout_service"] = [
             "%s/logout" % self.saml2_sp_url,
             BINDING_HTTP_REDIRECT,
         ]
         sp_config["service"]["sp"]["url"] = self.saml2_sp_url
         sp_config["xmlsec_binary"] = self.saml2_xmlsec
         config = SPConfig()
         conf = sp_config.copy()
         config.load(conf)
         self._v_config = config
     return self._v_config
Example #14
0
 def _factory(binding: str = BINDING_HTTP_REDIRECT) -> str:
     conf = SPConfig()
     conf.load(sp_conf_dict)
     client = Saml2Client(conf)
     if binding == BINDING_HTTP_REDIRECT:
         session_id, result = client.prepare_for_authenticate(
             entityid="test_generic_idp",
             relay_state="",
             binding=binding,
         )
         return parse.parse_qs(
             parse.urlparse(
                 result['headers'][0][1]).query)['SAMLRequest'][0]
     elif binding == BINDING_HTTP_POST:
         session_id, request_xml = client.create_authn_request(
             "http://localhost:9000/idp/sso/post", binding=binding)
         return base64.b64encode(bytes(request_xml, 'UTF-8'))
     else:
         raise Exception(f"Invalid binding: {binding}")
Example #15
0
File: app.py Project: wlin20/arkid
 def gen_xml(self, filename, entity_id, acs, sls):  # pylint: disable=no-self-use
     '''将SAMLAPP配置写入指定路径xml文件
     '''
     conf = SPConfig()
     endpointconfig = {
         "entityid": entity_id,
         'entity_category': [COC],
         "description": "extra SP setup",
         "service": {
             "sp": {
                 "want_response_signed": False,
                 "authn_requests_signed": True,
                 "logout_requests_signed": True,
                 "endpoints": {
                     "assertion_consumer_service":
                     [(acs, BINDING_HTTP_POST)],
                     "single_logout_service": [
                         (sls, BINDING_HTTP_REDIRECT),
                         (sls.replace('redirect',
                                      'post'), BINDING_HTTP_POST),
                     ],
                 }
             },
         },
         "key_file": BASEDIR +
         "/djangosaml2idp/certificates/mykey.pem",  # 随便放一个私钥,并不知道SP私钥
         "cert_file":
         BASEDIR + '/djangosaml2idp/saml2_config/sp_cert/%s.pem' % filename,
         "xmlsec_binary": xmlsec_path,
         "metadata": {
             "local":
             [BASEDIR + '/djangosaml2idp/saml2_config/idp_metadata.xml']
         },
         "name_form": NAME_FORMAT_URI,
     }
     conf.load(copy.deepcopy(endpointconfig))
     meta_data = entity_descriptor(conf)
     content = text_type(meta_data).encode('utf-8')
     with open(BASEDIR + '/djangosaml2idp/saml2_config/%s.xml' % filename,
               'wb+') as f:
         f.write(content)
Example #16
0
 def _saml2_config(self):
     if self._v_config is None:
         sp_config = self._saml2_config_template()
         sp_config['metadata']['local'] = [self.saml2_idp_configfile]
         sp_config['entityid'] = self.saml2_sp_entityid
         sp_config['service']['sp']['name'] = self.saml2_sp_entityid
         sp_config['service']['sp']['url'] = self.saml2_sp_url
         sp_config['service']['sp']['endpoints'][
             'assertion_consumer_service'] = [
                 self.saml2_sp_url,
             ]
         sp_config['service']['sp']['endpoints'][
             'single_logout_service'] = [
                 '%s/logout' % self.saml2_sp_url, BINDING_HTTP_REDIRECT
             ]
         sp_config['service']['sp']['url'] = self.saml2_sp_url
         sp_config['xmlsec_binary'] = self.saml2_xmlsec
         config = SPConfig()
         conf = sp_config.copy()
         config.load(conf)
         self._v_config = config
     return self._v_config
Example #17
0
def test_config_loader_with_real_conf(request):
    config = SPConfig()
    config.load(conf.create_conf(sp_host='sp.example.com',
                                 idp_hosts=['idp.example.com'],
                                 metadata_file='remote_metadata_one_idp.xml'))
    return config
def saml_acs(request, idp_name, ms):
    '''SAML ACS'''

    xmlstr = request.POST.get("SAMLResponse")

    # Create setting before call pysaml2 method for current IDP
    # Refer to: https://pythonhosted.org/pysaml2/howto/config.html
    setting = {
        "allow_unknown_attributes": True,
        # full path to the xmlsec1 binary programm
        'xmlsec_binary': xmlsec_path,
        # your entity id, usually your subdomain plus the url to the metadata view
        'entityid': 'PCG:PepperPD:Entity:ID',
        # directory with attribute mapping
        'attribute_map_dir': path.join(SSO_DIR, 'attribute-maps'),
        # this block states what services we provide
        'service': {
            # we are just a lonely SP
            'sp': {
                "allow_unsolicited": True,
                'name': 'Federated Django sample SP',
                'name_id_format': saml.NAMEID_FORMAT_PERSISTENT,
                'endpoints': {
                    # url and binding to the assetion consumer service view
                    # do not change the binding or service name
                    'assertion_consumer_service': [
                        ('https://59.45.37.54/genericsso/', saml2.BINDING_HTTP_POST),
                        ],
                    # url and binding to the single logout service view
                    # do not change the binding or service name
                    'single_logout_service': [
                        ('https://59.45.37.54/saml2/ls/', saml2.BINDING_HTTP_REDIRECT),
                        ('https://59.45.37.54/saml2/ls/post', saml2.BINDING_HTTP_POST),
                      ]
                    },
                # attributes that this project need to identify a user
                'required_attributes': ['uid'],
                # attributes that may be useful to have but not required
                'optional_attributes': ['eduPersonAffiliation'],
                # in this section the list of IdPs we talk to are defined
                'idp': {
                    # we do not need a WAYF service since there is
                    # only an IdP defined here. This IdP should be
                    # present in our metadata
                    # the keys of this dictionary are entity ids
                    # 'https://idp.example.com/simplesaml/saml2/idp/metadata.php': {
                    #     'single_sign_on_service': {
                    #         saml2.BINDING_HTTP_REDIRECT: 'https://idp.example.com/simplesaml/saml2/idp/SSOService.php',
                    #         },
                    #     'single_logout_service': {
                    #         saml2.BINDING_HTTP_REDIRECT: 'https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php',
                    #         },
                    #     },
                    },
                },
            },
        # where the remote metadata is stored
        'metadata': {
            'local': [
                path.join(BASEDIR, idp_name, 'FederationMetadata.xml')
                ],
            },
        # set to 1 to output debugging information
        'debug': 1,
        # ===  CERTIFICATE ===
        # cert_file must be a PEM formatted certificate chain file.
        # example:
        # 'key_file': path.join(BASEDIR, 'sso/' + idp_name + 'mycert.key'),  # private part
        # 'cert_file': path.join(BASEDIR, 'sso/' + idp_name + 'mycert.pem'),  # public part
        # 'key_file': path.join(BASEDIR, 'sso/' + idp_name + 'mycert.key'),  # private part
        # 'cert_file': path.join(BASEDIR, 'sso/' + idp_name + 'customappsso.base64.cer'),  # public part        
        # === OWN METADATA SETTINGS ===
        # 'contact_person': [
        #     {'given_name': 'Lorenzo',
        #      'sur_name': 'Gil',
        #      'company': 'Yaco Sistemas',
        #      'email_address': '*****@*****.**',
        #      'contact_type': 'technical'},
        #     {'given_name': 'Angel',
        #      'sur_name': 'Fernandez',
        #      'company': 'Yaco Sistemas',
        #      'email_address': '*****@*****.**',
        #      'contact_type': 'administrative'},
        #     ],
        # === YOU CAN SET MULTILANGUAGE INFORMATION HERE ===
        # 'organization': {
        #     'name': [('Yaco Sistemas', 'es'), ('Yaco Systems', 'en')],
        #     'display_name': [('Yaco', 'es'), ('Yaco', 'en')],
        #     'url': [('http://www.yaco.es', 'es'), ('http://www.yaco.com', 'en')],
        #     },
        'valid_for': 24,  # how long is our metadata valid
    }

    #** load IDP config and parse the saml response
    conf = SPConfig()
    conf.load(copy.deepcopy(setting))

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

    response = client.parse_authn_request_response(xmlstr, BINDING_HTTP_POST, outstanding_queries)
    session_info = response.session_info()

    # print session_info['issuer']

    # Parse ava (received attributes) as dict
    data = {}
    for k, v in session_info['ava'].items():
        data[k] = v[0]
    
    return post_acs(request,  ms, data)
Example #19
0
def test_config_loader(request):
    config = SPConfig()
    config.load({'entityid': 'testentity'})
    return config
Example #20
0
def asgard_sp_config(request=None):
    host = "localhost"
    if request != None:
        host = request.get_host().replace(":","-")
    x= {
        # your entity id, usually your subdomain plus the url to the metadata view
        'entityid': 'https://keybucket.app.nordu.net/saml2/sp/metadata',
        # directory with attribute mapping
        "attribute_map_dir" : "%s/saml2/attributemaps" % settings.BASE_DIR,
        # this block states what services we provide
        'service': {
            # we are just a lonely SP
            'sp' : {
                'name': 'KeyBucket',
                'endpoints': {
                    # url and binding to the assertion consumer service view
                    # do not change the binding osettingsr service name
                    'assertion_consumer_service': [
                        ('https://keybucket.app.nordu.net/saml2/sp/acs/',
                         BINDING_HTTP_POST),
                    ],
                    # url and binding to the single logout service view
                    # do not change the binding or service name
                    'single_logout_service': [
                        ('https://keybucket.app.nordu.net/saml2/sp/ls/',
                         BINDING_HTTP_REDIRECT),
                    ],
                    },
                # attributes that this project need to identify a user
                'required_attributes': ['eduPersonPrincipalName','displayName'],
                }
        },

        # where the remote metadata is stored
        #'metadata': { 'remote': [{'url':'http://md.swamid.se/md/swamid-idp.xml',
        #                          'cert':'%s/saml2/credentials/md-signer.crt' % settings.BASE_DIR}] },
        'metadata': {'local': [settings.SAML_METADATA_FILE]},

        # set to 1 to output debugging information
        'debug': 1,

        # certificate
        "key_file" : "%s/%s.key" % (settings.SSL_KEY_DIR,host),
        "cert_file" : "%s/%s.crt" % (settings.SSL_CRT_DIR,host),
        # own metadata settings
        'contact_person': [
                {'given_name': 'Leif',
                 'sur_name': 'Johansson',
                 'company': 'NORDUnet',
                 'email_address': '*****@*****.**',
                 'contact_type': 'technical'},
                {'given_name': 'Johan',
                 'sur_name': 'Berggren',
                 'company': 'NORDUnet',
                 'email_address': '*****@*****.**',
                 'contact_type': 'technical'},
        ],
        # you can set multilanguage information here
        'organization': {
            'name': [('NORDUNet', 'en')],
            'display_name': [('NORDUnet A/S', 'en')],
            'url': [('http://www.nordu.net', 'en')],
            }
    }
    c = SPConfig()
    c.load(copy.deepcopy(x))
    return c
Example #21
0
class BaseTestRP(TestCase):
    def setUp(self):
        # put idp metadata in sp metadata store
        # self.IDP = IdPConfig()
        # self.IDP.load(copy.deepcopy(SAML_IDP_CONFIG))
        # idp_metadata = entity_descriptor(self.IDP)
        cleanup_metadata()
        idp_md_url = reverse('uniauth_saml2_idp:saml2_idp_metadata')
        client = Client()
        idp_metadata = client.get(idp_md_url)

        # idp metadata into sp md store
        with open(mds[0], 'wb') as fd:
            fd.write(idp_metadata.content)

        # create a pysaml SP
        self.sp_conf = SPConfig()
        self.sp_conf.load(copy.deepcopy(SAML_SP_CONFIG))

        self.sp_client = Saml2Client(self.sp_conf)
        logger.info('{} SP: {}'.format(self.__class__.__name__, self.client))

    def _get_superuser_user(self):
        data = dict(username='******',
                    email='*****@*****.**',
                    is_superuser=1,
                    is_staff=1)
        user = get_user_model().objects.get_or_create(**data)[0]
        user.set_password('admin')
        user.save()
        return user

    def _superuser_login(self):
        user = self._get_superuser_user()
        self.client.force_login(user)

    def _add_sp_md(self):
        self._superuser_login()
        # put md store through admin UI
        create_url = reverse('admin:uniauth_saml2_idp_metadatastore_add')
        data = dict(name='sptest',
                    type='local',
                    url=idp_md_path,
                    kwargs='{}',
                    is_active=1)
        response = self.client.post(create_url, data, follow=True)
        assert 'was added successfully' in response.content.decode()

        # put sp metadata into IDP md store
        sp_metadata = entity_descriptor(self.sp_conf)
        with open(IDP_SP_METADATA_PATH + '/sp.xml', 'wb') as fd:
            fd.write(sp_metadata.to_string())

    def _add_sp(self):
        self._superuser_login()
        create_url = reverse('admin:uniauth_saml2_idp_serviceprovider_add')
        data = dict(entity_id=SAML_SP_CONFIG['entityid'],
                    display_name='That SP display name',
                    signing_algorithm=
                    "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
                    digest_algorithm="http://www.w3.org/2001/04/xmlenc#sha256",
                    disable_encrypted_assertions=1,
                    is_active=1)
        response = self.client.post(create_url, data, follow=True)
        assert 'was added successfully' in response.content.decode()

    def _get_sp_authn_request(self):
        session_id, result = self.sp_client.prepare_for_authenticate(
            entityid=idp_eid, relay_state='/', binding=BINDING_HTTP_POST)
        url, data = extract_saml_authn_data(result)
        return url, data, session_id

    def _run_ldapd(self):
        self.ldapd = subprocess.Popen(["python3", "tests/ldapd.py"],
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
Example #22
0
def test_config_loader_callable(request):
    config = SPConfig()
    config.load({'entityid': 'testentity_callable'})
    return config
Example #23
0
def config_settings_loader(request: Optional[HttpRequest] = None) -> SPConfig:
    conf = SPConfig()
    if request is None or not request.path.lstrip('/').startswith(
            settings.SPID_URLS_PREFIX):
        # Not a SPID request: load SAML_CONFIG unchanged
        conf.load(copy.deepcopy(settings.SAML_CONFIG))
        return conf

    # Build a SAML_CONFIG for SPID
    metadata_url = request.build_absolute_uri(
        reverse('djangosaml2_spid:spid_metadata'))

    saml_config = {
        'entityid':
        metadata_url,
        'attribute_map_dir':
        os.path.join(djangosaml2_spid_config.path, 'attribute_maps/'),
        'service': {
            'sp': {
                'name':
                metadata_url,
                'name_qualifier':
                request.build_absolute_uri('/'),
                'name_id_format': [settings.SPID_NAMEID_FORMAT],
                'endpoints': {
                    'assertion_consumer_service': [
                        (request.build_absolute_uri(
                            reverse('djangosaml2_spid:saml2_acs')),
                         saml2.BINDING_HTTP_POST),
                    ],
                    'single_logout_service': [
                        (request.build_absolute_uri(
                            reverse('djangosaml2_spid:saml2_ls_post')),
                         saml2.BINDING_HTTP_POST),
                    ],
                },

                # Mandates that the IdP MUST authenticate the presenter directly
                # rather than rely on a previous security context.
                'force_authn':
                False,  # SPID
                'name_id_format_allow_create':
                False,

                # attributes that this project need to identify a user
                'required_attributes':
                ['spidCode', 'name', 'familyName', 'fiscalNumber', 'email'],
                'requested_attribute_name_format':
                saml2.saml.NAME_FORMAT_BASIC,
                'name_format':
                saml2.saml.NAME_FORMAT_BASIC,

                # attributes that may be useful to have but not required
                'optional_attributes': [
                    'gender', 'companyName', 'registeredOffice', 'ivaCode',
                    'idCard', 'digitalAddress', 'placeOfBirth',
                    'countyOfBirth', 'dateOfBirth', 'address', 'mobilePhone',
                    'expirationDate'
                ],
                'signing_algorithm':
                settings.SPID_SIG_ALG,
                'digest_algorithm':
                settings.SPID_DIG_ALG,
                'authn_requests_signed':
                True,
                'logout_requests_signed':
                True,

                # Indicates that Authentication Responses to this SP must
                # be signed. If set to True, the SP will not consume
                # any SAML Responses that are not signed.
                'want_assertions_signed':
                True,

                # When set to true, the SP will consume unsolicited SAML
                # Responses, i.e. SAML Responses for which it has not sent
                # a respective SAML Authentication Request.
                'allow_unsolicited':
                False,

                # Permits to have attributes not configured in attribute-mappings
                # otherwise...without OID will be rejected
                'allow_unknown_attributes':
                True,
            },
        },
        'metadata': {
            'local': [settings.SPID_IDENTITY_PROVIDERS_METADATA_DIR],
            'remote': []
        },

        # Signing
        'key_file':
        settings.SPID_PRIVATE_KEY,
        'cert_file':
        settings.SPID_PUBLIC_CERT,

        # Encryption
        'encryption_keypairs': [{
            'key_file': settings.SPID_PRIVATE_KEY,
            'cert_file': settings.SPID_PUBLIC_CERT,
        }],
        'organization':
        copy.deepcopy(settings.SAML_CONFIG['organization'])
    }

    if settings.SAML_CONFIG.get('debug'):
        saml_config['debug'] = True

    if 'xmlsec_binary' in settings.SAML_CONFIG:
        saml_config['xmlsec_binary'] = copy.deepcopy(
            settings.SAML_CONFIG['xmlsec_binary'])
    else:
        saml_config['xmlsec_binary'] = get_xmlsec_binary(
            ['/opt/local/bin', '/usr/bin/xmlsec1'])

    if settings.SPID_SAML_CHECK_REMOTE_METADATA_ACTIVE:
        saml_config['metadata']['remote'].append(
            {'url': settings.SPID_SAML_CHECK_METADATA_URL})

    if settings.SPID_TESTENV2_REMOTE_METADATA_ACTIVE:
        saml_config['metadata']['remote'].append(
            {'url': settings.SPID_TESTENV2_METADATA_URL})

    logger.debug(f'SAML_CONFIG: {saml_config}')
    conf.load(saml_config)
    return conf
Example #24
0
from saml2.config import SPConfig
from saml2.response import AuthnResponse
from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST
from saml2.client import Saml2Client

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

outstanding = None
outstanding_certs = None
conv_info = None

conf = SPConfig()

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

# client arguments
selected_idp = None
came_from = '/'
# conf['sp']['authn_requests_signed'] determines if saml2.BINDING_HTTP_POST or saml2.BINDING_HTTP_REDIRECT
binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'  # saml2.BINDING_HTTP_REDIRECT
sign = False
sigalg = None
nsprefix = {
    'ds': 'http://www.w3.org/2000/09/xmldsig#',
    'md': 'urn:oasis:names:tc:SAML:2.0:metadata',
    'samlp': 'urn:oasis:names:tc:SAML:2.0:protocol',
    'xenc': 'http://www.w3.org/2001/04/xmlenc#',
    'saml': 'urn:oasis:names:tc:SAML:2.0:assertion'
Example #25
0
def test_config_loader_with_real_conf(request):
    config = SPConfig()
    config.load(
        conf.create_conf(sp_host='sp.example.com',
                         idp_hosts=['idp.example.com']))
    return config
Example #26
0
class TestEnabledRP(BaseTestRP):
    def setUp(self):
        super().setUp()
        self._add_sp_md()
        settings.SAML_DISALLOW_UNDEFINED_SP = True
        self._add_sp()
        self.sp = ServiceProvider.objects.first()
        self.login_data = dict(username='******', password='******')
        # add LDAP in settings
        settings.INSTALLED_APPS.append('multildap')
        settings.LDAP_CONNECTIONS = LDAP_CONNECTIONS
        settings.AUTHENTICATION_BACKENDS.append(
            'uniauth_saml2_idp.auth.multildap.LdapUnicalMultiAcademiaAuthBackend'
        )

        # disable agreement screen
        self.sp.agreement_screen = 0

        # configure sp processors
        self.sp.attribute_processor = 'uniauth_saml2_idp.processors.ldap.LdapUnicalMultiAcademiaProcessor'
        self.sp.attribute_mapping = json.dumps({
            # refeds + edugain Entities
            "cn":
            "cn",
            "eduPersonEntitlement":
            "eduPersonEntitlement",
            "eduPersonPrincipalName":
            "eduPersonPrincipalName",
            "schacHomeOrganization":
            "schacHomeOrganization",
            "eduPersonHomeOrganization":
            "eduPersonHomeOrganization",
            "eduPersonAffiliation":
            "eduPersonAffiliation",
            "eduPersonScopedAffiliation":
            "eduPersonScopedAffiliation",
            "eduPersonTargetedID":
            "eduPersonTargetedID",
            "mail": ["mail", "email"],
            "email": ["mail", "email"],
            "schacPersonalUniqueCode":
            "schacPersonalUniqueCode",
            "schacPersonalUniqueID":
            "schacPersonalUniqueID",
            "sn":
            "sn",
            "givenName": ["givenName", "another_possible_occourrence"],
            "displayName":
            "displayName",

            # custom attributes
            "codice_fiscale":
            "codice_fiscale",
            "matricola_studente":
            "matricola_studente",
            "matricola_dipendente":
            "matricola_dipendente"
        })
        self.sp.save()

        # run ldapd
        self._run_ldapd()

    def test_valid_form(self):
        url, data, session_id = self._get_sp_authn_request()
        response = self.client.post(url, data, follow=True)
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)
        # is there a SAML response?
        saml_resp = re.findall(samlresponse_form_regexp,
                               login_response.content.decode())
        assert saml_resp

        # login again to update existing user on db
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)

        # test a disabled user
        #user = get_user_model().objects.last()
        #user.is_active = 0
        #user.save()
        #login_response = self.client.post(login_url,
        #data=self.login_data, follow=True)

    def test_invalid_form(self):
        url, data, session_id = self._get_sp_authn_request()
        response = self.client.post(url, data, follow=True)
        login_data = {'username': '******', 'password': '******'}
        login_response = self.client.post(login_url,
                                          data=login_data,
                                          follow=True)
        assert 'is invalid' in login_response.content.decode()

        login_data = {'username': '******', 'password': '******'}
        login_response = self.client.post(login_url,
                                          data=login_data,
                                          follow=True)
        assert 'is invalid' in login_response.content.decode()

    def test_sp_attr_policy(self):
        # create a pysaml SP
        self.sp_conf = SPConfig()
        _sp_conf = copy.deepcopy(SAML_SP_CONFIG)
        _sp_conf['service']['sp']['required_attributes'] = [
            'email', 'givenName', 'eduPersonPrincipalName', 'sn', 'displayName'
        ]
        self.sp_conf.load(_sp_conf)
        # put sp metadata into IDP md store
        sp_metadata = entity_descriptor(self.sp_conf)
        with open(IDP_SP_METADATA_PATH + '/sp.xml', 'wb') as fd:
            fd.write(sp_metadata.to_string())

        sp_client = Saml2Client(self.sp_conf)
        session_id, result = sp_client.prepare_for_authenticate(
            entityid=idp_eid, relay_state='/', binding=BINDING_HTTP_POST)
        url, data = extract_saml_authn_data(result)
        response = self.client.post(url, data, follow=True)
        # login again to update existing user on db
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)
        # is there a SAML response?
        saml_resp = re.findall(samlresponse_form_regexp,
                               login_response.content.decode())
        assert saml_resp
        saml_assrt = base64.b64decode(saml_resp[0]).decode()
        assert 'sn' in saml_assrt

    def test_sp_attr_policy2(self):
        # create a pysaml SP
        self.sp_conf = SPConfig()
        _sp_conf = copy.deepcopy(SAML_SP_CONFIG)
        _sp_conf['service']['sp']['required_attributes'] = [
            'email', 'givenName', 'eduPersonPrincipalName', 'sn', 'telexNumber'
        ]
        self.sp_conf.load(_sp_conf)
        # put sp metadata into IDP md store
        sp_metadata = entity_descriptor(self.sp_conf)
        with open(IDP_SP_METADATA_PATH + '/sp.xml', 'wb') as fd:
            fd.write(sp_metadata.to_string())

        sp_client = Saml2Client(self.sp_conf)
        session_id, result = sp_client.prepare_for_authenticate(
            entityid=idp_eid, relay_state='/', binding=BINDING_HTTP_POST)
        url, data = extract_saml_authn_data(result)
        response = self.client.post(url, data, follow=True)
        # login again to update existing user on db
        login_response = self.client.post(login_url,
                                          data=self.login_data,
                                          follow=True)
        # is there a SAML response?
        saml_resp = re.findall(samlresponse_form_regexp,
                               login_response.content.decode())
        # assert saml_resp
        # saml_assrt = base64.b64decode(saml_resp[0]).decode()
        # assert 'telexNumber' not in saml_assrt

    def tearDown(self):
        """Kill ldapd test server
        """
        self.ldapd.kill()
Example #27
0
class Saml(object):

    """
    SAML Wrapper around pysaml2.

    Implements SAML2 Service Provider functionality for Flask.
    """

    def __init__(self, config, attribute_map=None):
        """Initialize SAML Service Provider.

        Args:
            config (dict): Service Provider config info in dict form
            attribute_map (dict): Mapping of attribute keys to user data
        """
        self._config = SPConfig()
        self._config.load(config)
        if config['metadata'].get('config'):
            # Hacked in a way to get the IdP metadata from a python dict
            # rather than having to resort to loading XML from file or http.
            idp_config = IdPConfig()
            idp_config.load(config['metadata']['config'][0])
            idp_entityid = config['metadata']['config'][0]['entityid']
            idp_metadata_str = str(entity_descriptor(idp_config, 24))
            LOGGER.debug('IdP XML Metadata for %s: %s' % (
                idp_entityid, idp_metadata_str))
            self._config.metadata.import_metadata(
                idp_metadata_str, idp_entityid)
        self.attribute_map = {}
        if attribute_map is not None:
            self.attribute_map = attribute_map

    def authenticate(self, next_url='/', binding=BINDING_HTTP_REDIRECT):
        """Start SAML Authentication login process.

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

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

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

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

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

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

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

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

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

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

        return response

    def handle_assertion(self, request):
        """Handle SAML Authentication login assertion (POST).

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

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

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

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

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

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

        relay_state = request.form.get('RelayState', '/')
        LOGGER.debug('Returning redirect to %s' % relay_state)
        return user_id, user_attributes, redirect(relay_state)

    def logout(self, next_url='/'):
        """Start SAML Authentication logout process.

        Args:
            next_url (string): HTTP URL to return user to when logout is
                complete.

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

        Raises:
            AuthException: when unable to resolve Identity Provider single logout end-point.
        """
        # retrieve cache
        state_cache = AuthDictCache(session, '_saml_state')
        identity_cache = IdentityCache(session, '_saml_identity')
        subject_id = session.get('_saml_subject_id')
        # don't logout if not logged in
        if subject_id is None:
            raise AuthException('Unable to retrieve subject id for logout')
        # fail if signing requested but no private key configured
        if self._config.logout_requests_signed == 'true':
            if not self._config.key_file \
                or not os.path.exists(self._config.key_file):
                raise AuthException(
                    'Signature requested for this Saml logout request,'
                    ' but no private key file configured')

        LOGGER.debug('State cache %s' % state_cache)
        LOGGER.debug('Identity cache %s' % identity_cache)
        LOGGER.debug('Subject Id %s' % subject_id)

        # use pysaml2 to initiate the SAML logout request
        client = Saml2Client(self._config, state_cache=state_cache,
            identity_cache=identity_cache, logger=LOGGER)
        saml_response = client.global_logout(subject_id,
            return_to=next_url)

        # sync the state to cache
        state_cache.sync()

        LOGGER.debug('State cache %s' % session['_saml_state'])
        LOGGER.debug('Identity cache %s' % session['_saml_identity'])

        if saml_response[1] == "": # used SOAP BINDING successfully
            return redirect(next_url)

        LOGGER.debug('Returning Response from SAML for continuation of the'
            ' logout process')
        return make_response('\n'.join(saml_response[3]),
            saml_response[1], saml_response[2]) # body, status, headers

    def _handle_logout_request(self, client, request, subject_id, binding):
        """Handle SAML Authentication logout request (GET).

        Args:
            client (Saml2Client): instance of SAML client class.
            request (Request): Flask request object for this HTTP transaction.
            subject_id (string): Id of the subject we are processing the
                logout for.
            binding (string): the SAML binding method being used for this
                request.

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

        Raises:
            BadRequest: when SAML request data is missing.
            AuthException: when SAML request indicates logout failed.
        """
        LOGGER.debug('Received a logout request from Identity Provider')

        # pysaml2 logout_request currently only returns for
        # BINDING_HTTP_REDIRECT. We will have it fail for anything
        # other than the header 'Location'

        try:
            headers, _success = client.logout_request(
                request.values, subject_id, binding=binding)
        except TypeError:
            raise BadRequest('SAML request is invalid')
        try:
            assert headers is not None
            assert headers[0][0] == 'Location'
            return redirect(headers[0][1])
        except:
            raise AuthException('An error occurred during logout')

    def _handle_logout_response(self, client, request, binding, next_url):
        """Handle SAML Authentication logout response (GET or POST).

        Args:
            client (Saml2Client): instance of SAML client class.
            request (Request): Flask request object for this HTTP transaction.
            binding (string): the SAML binding method being used for this
                request.
            next_url (string): URL to get redirected to if all is successful.

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

        Raises:
            BadRequest: when SAML response data is missing.
            AuthException: when SAML response indicates logout failed.
        """
        LOGGER.debug('Received a logout response from Identity Provider')
        try:
            saml_response = client.logout_response(
                request.values['SAMLResponse'], binding=binding)
        except TypeError:
            raise BadRequest('SAML response is invalid')
        LOGGER.debug(saml_response)
        if saml_response:
            if saml_response[1] == '': # used SOAP BINDING successfully
                response = redirect(next_url)
            else:
                # body, status, headers
                response = make_response('\n'.join(saml_response[3]),
                    saml_response[1], saml_response[2])
                # pysaml2 returns an empty 200 in some cases,
                # we'll redirect instead
                if response.status_code == 200 and not response.data:
                    response = redirect(next_url)
        else:
            raise AuthException('An error occurred during logout')
        return response

    def handle_logout(self, request, next_url='/'):
        """Handle SAML Authentication logout request/response.

        Args:
            request (Request): Flask request object for this HTTP transaction.
            next_url (string): URL to get redirected to if all is successful.

        Returns:
            (boolean) Success, Flask Response object to return to user
                containing HTTP_REDIRECT SAML message.

        Raises:
            BadRequest: when SAML request/response data is missing.
        """
        # retrieve cache
        state_cache = AuthDictCache(session, '_saml_state')
        identity_cache = IdentityCache(session, '_saml_identity')
        subject_id = session.get('_saml_subject_id')

        LOGGER.debug('State cache %s' % state_cache)
        LOGGER.debug('Identity cache %s' % identity_cache)
        LOGGER.debug('Subject Id %s' % subject_id)

        # use pysaml2 to complete the SAML logout request
        client = Saml2Client(self._config, state_cache=state_cache,
            identity_cache=identity_cache, logger=LOGGER)
        # let's try to figure out what binding is being used and what type of
        # logout call we are handling
        if request.args:
            binding = BINDING_HTTP_REDIRECT
        elif request.form:
            binding = BINDING_HTTP_POST
        else:
            # The SOAP binding is only valid on logout requests which currently
            # pysaml2 doesn't support.
            raise BadRequest('Unable to find supported binding')

        if 'SAMLRequest' in request.values:
            response = self._handle_logout_request(
                client, request, subject_id, binding)
        elif 'SAMLResponse' in request.values:
            response = self._handle_logout_response(
                client, request, binding, next_url)
        else:
            raise BadRequest('Unable to find SAMLRequest or SAMLResponse')

        # cache the state and remove subject if logout was successful
        success = identity_cache.get_identity(subject_id) == ({}, [])
        if success:
            session.pop('_saml_subject_id')
        state_cache.sync()

        LOGGER.debug('State cache %s' % session['_saml_state'])
        LOGGER.debug('Identity cache %s' % session['_saml_identity'])

        LOGGER.debug(
            'Returning redirect to complete/continue the logout process')
        return success, response

    def get_metadata(self):
        """Returns SAML Service Provider Metadata"""
        edesc = entity_descriptor(self._config, 24)
        if self._config.key_file:
            edesc = sign_entity_descriptor(edesc, 24, None, security_context(self._config))
        response = make_response(str(edesc))
        response.headers['Content-type'] = 'text/xml; charset=utf-8'
        return response
Example #28
0
def test_config_loader(request):
    config = SPConfig()
    config.load({'entityid': 'testentity'})
    return config
Example #29
0
def test_config_loader_with_real_conf(request):
    config = SPConfig()
    config.load(conf.create_conf(sp_host='sp.example.com',
                                 idp_hosts=['idp.example.com']))
    return config
Example #30
0
def config_settings_loader(request: Optional[HttpRequest] = None) -> SPConfig:
    conf = SPConfig()
    if request is None:
        # Not a SPID request: load SAML_CONFIG unchanged
        conf.load(copy.deepcopy(settings.SAML_CONFIG))
        return conf

    # Build a SAML_CONFIG for SPID
    base_url = settings.SPID_BASE_URL or request.build_absolute_uri("/")
    metadata_url = urljoin(base_url, settings.SPID_METADATA_URL_PATH)

    if settings.SPID_METADATA_URL_PATH in request.get_full_path():
        _REQUIRED_ATTRIBUTES = settings.SPID_REQUIRED_ATTRIBUTES
        _OPTIONAL_ATTRIBUTES = settings.SPID_OPTIONAL_ATTRIBUTES
    else:
        _REQUIRED_ATTRIBUTES = settings.CIE_REQUIRED_ATTRIBUTES
        _OPTIONAL_ATTRIBUTES = []

    saml_config = {
        "entityid":
        getattr(settings, 'SAML2_ENTITY_ID', metadata_url),
        "attribute_map_dir":
        settings.SPID_ATTR_MAP_DIR,
        "service": {
            "sp": {
                "name":
                metadata_url,
                "name_qualifier":
                base_url,
                "name_id_format": [settings.SPID_NAMEID_FORMAT],
                "endpoints": {
                    "assertion_consumer_service": [
                        (
                            urljoin(base_url,
                                    reverse("djangosaml2_spid:saml2_acs")),
                            saml2.BINDING_HTTP_POST,
                        ),
                    ],
                    "single_logout_service": [
                        (
                            urljoin(base_url,
                                    reverse("djangosaml2_spid:saml2_ls_post")),
                            saml2.BINDING_HTTP_POST,
                        ),
                    ],
                },
                # Mandates that the IdP MUST authenticate the presenter directly
                # rather than rely on a previous security context.
                "force_authn":
                False,  # SPID
                "name_id_format_allow_create":
                False,
                # attributes that this project need to identify a user
                "required_attributes":
                _REQUIRED_ATTRIBUTES,
                "optional_attributes":
                _OPTIONAL_ATTRIBUTES,
                "requested_attribute_name_format":
                saml2.saml.NAME_FORMAT_BASIC,
                "name_format":
                saml2.saml.NAME_FORMAT_BASIC,
                "signing_algorithm":
                settings.SPID_SIG_ALG,
                "digest_algorithm":
                settings.SPID_DIG_ALG,
                "authn_requests_signed":
                True,
                "logout_requests_signed":
                True,
                # Indicates that Authentication Responses to this SP must
                # be signed. If set to True, the SP will not consume
                # any SAML Responses that are not signed.
                "want_assertions_signed":
                True,
                # When set to true, the SP will consume unsolicited SAML
                # Responses, i.e. SAML Responses for which it has not sent
                # a respective SAML Authentication Request. Set to True to
                # let ACS endpoint work.
                "allow_unsolicited":
                settings.SAML_CONFIG.get("allow_unsolicited", False),
                # Permits to have attributes not configured in attribute-mappings
                # otherwise...without OID will be rejected
                "allow_unknown_attributes":
                True,
            },
        },
        "disable_ssl_certificate_validation":
        settings.SAML_CONFIG.get("disable_ssl_certificate_validation"),
        "metadata": {
            "local": [settings.SPID_IDENTITY_PROVIDERS_METADATA_DIR],
            "remote": [],
        },
        # Signing
        "key_file":
        settings.SPID_PRIVATE_KEY,
        "cert_file":
        settings.SPID_PUBLIC_CERT,
        # Encryption
        "encryption_keypairs": [{
            "key_file": settings.SPID_PRIVATE_KEY,
            "cert_file": settings.SPID_PUBLIC_CERT,
        }],
        "organization":
        copy.deepcopy(settings.SAML_CONFIG["organization"]),
    }

    if settings.SAML_CONFIG.get("debug"):
        saml_config["debug"] = True

    if "xmlsec_binary" in settings.SAML_CONFIG:
        saml_config["xmlsec_binary"] = copy.deepcopy(
            settings.SAML_CONFIG["xmlsec_binary"])
    else:
        saml_config["xmlsec_binary"] = get_xmlsec_binary(
            ["/opt/local/bin", "/usr/bin/xmlsec1"])

    if settings.SPID_SAML_CHECK_IDP_ACTIVE:
        saml_config["metadata"]["remote"].append(
            {"url": settings.SPID_SAML_CHECK_METADATA_URL})

    if settings.SPID_DEMO_IDP_ACTIVE:
        saml_config["metadata"]["remote"].append(
            {"url": settings.SPID_DEMO_METADATA_URL})

    if settings.SPID_VALIDATOR_IDP_ACTIVE:
        saml_config["metadata"]["remote"].append(
            {"url": settings.SPID_VALIDATOR_METADATA_URL})

    logger.debug(f"SAML_CONFIG: {saml_config}")
    conf.load(saml_config)
    return conf