Exemple #1
0
    def test_logout_service_global(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )

        self.do_login()

        # now simulate a global logout process initiated by another SP
        subject_id = views._get_subject_id(self.client.session)
        instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
        saml_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/"><saml:Issuer>https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>""" % (
            instant, subject_id.text)

        response = self.client.get(
            reverse('saml2_ls'), {
                'SAMLRequest': deflate_and_base64_encode(saml_request),
            })
        self.assertEqual(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEqual(url.hostname, 'idp.example.com')
        self.assertEqual(url.path,
                         '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = parse_qs(url.query)
        self.assertIn('SAMLResponse', params)
        saml_response = params['SAMLResponse'][0]

        if 'Response xmlns' not in decode_base64_and_inflate(
                saml_response).decode('utf-8'):
            raise Exception('Not a valid Response')
Exemple #2
0
    def test_logout_service_global(self):
        settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com',
                                                idp_hosts=['idp.example.com'])

        self.do_login()

        # now simulate a global logout process initiated by another SP
        subject_id = views._get_subject_id(self.client.session)
        instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
        saml_request = '<samlp:LogoutRequest ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>' % (
            instant, subject_id)

        response = self.client.get('/ls/', {
                'SAMLRequest': deflate_and_base64_encode(saml_request),
                })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path,
                          '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = urlparse.parse_qs(url.query)
        self.assert_('SAMLResponse' in params)

        saml_response = params['SAMLResponse'][0]
        expected_response = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutResponse Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="_9961abbaae6d06d251226cb25e38bf8f468036e57e" IssueInstant="2010-09-05T09:10:12Z" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>"""
        xml = decode_base64_and_inflate(saml_response)
        self.assertSAMLRequestsEquals(expected_response, xml)
Exemple #3
0
    def test_login_several_idps(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp1.example.com',
                       'idp2.example.com',
                       'idp3.example.com'],
            metadata_file='remote_metadata_three_idps.xml',
        )
        response = self.client.get(reverse('saml2_login'))
        # a WAYF page should be displayed
        self.assertContains(response, 'Where are you from?', status_code=200)
        for i in range(1, 4):
            link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/'
            self.assertContains(response, link % i)

        # click on the second idp
        response = self.client.get(reverse('saml2_login'), {
            'idp': 'https://idp2.example.com/simplesaml/saml2/idp/metadata.php',
            'next': '/',
        })
        self.assertEqual(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEqual(url.hostname, 'idp2.example.com')
        self.assertEqual(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assertIn('SAMLRequest', params)
        self.assertIn('RelayState', params)

        saml_request = params['SAMLRequest'][0]
        self.assertIn('AuthnRequest xmlns', decode_base64_and_inflate(
            saml_request).decode('utf-8'))
Exemple #4
0
    def test_logout(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )
        self.do_login()

        response = self.client.get(reverse('saml2_logout'))
        self.assertEqual(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEqual(url.hostname, 'idp.example.com')
        self.assertEqual(url.path,
                         '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = parse_qs(url.query)
        self.assertIn('SAMLRequest', params)

        saml_request = params['SAMLRequest'][0]

        if 'LogoutRequest xmlns' not in decode_base64_and_inflate(
                saml_request).decode('utf-8'):
            raise Exception('Not a valid LogoutRequest')
Exemple #5
0
    def test_logout_service_global(self):
        settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com',
                                                idp_hosts=['idp.example.com'])

        self.do_login()

        # now simulate a global logout process initiated by another SP
        subject_id = views._get_subject_id(self.client.session)
        instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
        saml_request = '<samlp:LogoutRequest ID="_9961abbaae6d06d251226cb25e38bf8f468036e57e" Version="2.0" IssueInstant="%s" Destination="http://sp.example.com/saml2/ls/" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">%s</saml:NameID><samlp:SessionIndex>_1837687b7bc9faad85839dbeb319627889f3021757</samlp:SessionIndex></samlp:LogoutRequest>' % (
            instant, subject_id)

        response = self.client.get(
            '/ls/', {
                'SAMLRequest': deflate_and_base64_encode(saml_request),
            })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path,
                          '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = urlparse.parse_qs(url.query)
        self.assert_('SAMLResponse' in params)

        saml_response = params['SAMLResponse'][0]
        expected_response = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutResponse Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="_9961abbaae6d06d251226cb25e38bf8f468036e57e" IssueInstant="2010-09-05T09:10:12Z" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>"""
        xml = decode_base64_and_inflate(saml_response)
        self.assertSAMLRequestsEquals(expected_response, xml)
Exemple #6
0
    def unravel(self, txt, binding, msgtype="response"):
        #logger.debug("unravel '%s'" % txt)
        if binding not in [
                BINDING_HTTP_REDIRECT, BINDING_HTTP_POST, BINDING_SOAP,
                BINDING_URI, BINDING_HTTP_ARTIFACT, None
        ]:
            raise ValueError("Don't know how to handle '%s'" % binding)
        else:
            try:
                if binding == BINDING_HTTP_REDIRECT:
                    xmlstr = decode_base64_and_inflate(txt)
                elif binding == BINDING_HTTP_POST:
                    xmlstr = base64.b64decode(txt)
                elif binding == BINDING_SOAP:
                    func = getattr(soap,
                                   "parse_soap_enveloped_saml_%s" % msgtype)
                    xmlstr = func(txt)
                elif binding == BINDING_HTTP_ARTIFACT:
                    xmlstr = base64.b64decode(txt)
                else:
                    xmlstr = txt
            except Exception:
                raise UnravelError()

        return xmlstr
Exemple #7
0
    def unravel(txt, binding, msgtype="response"):
        """
        Will unpack the received text. Depending on the context the original
         response may have been transformed before transmission.
        :param txt:
        :param binding:
        :param msgtype:
        :return:
        """
        #logger.debug("unravel '%s'" % txt)
        if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST,
                           BINDING_SOAP, BINDING_URI, BINDING_HTTP_ARTIFACT,
                           None]:
            raise ValueError("Don't know how to handle '%s'" % binding)
        else:
            try:
                if binding == BINDING_HTTP_REDIRECT:
                    xmlstr = decode_base64_and_inflate(txt)
                elif binding == BINDING_HTTP_POST:
                    xmlstr = base64.b64decode(txt)
                elif binding == BINDING_SOAP:
                    func = getattr(soap,
                                   "parse_soap_enveloped_saml_%s" % msgtype)
                    xmlstr = func(txt)
                elif binding == BINDING_HTTP_ARTIFACT:
                    xmlstr = base64.b64decode(txt)
                else:
                    xmlstr = txt
            except Exception:
                raise UnravelError()

        return xmlstr
Exemple #8
0
    def test_logout(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )
        self.do_login()

        response = self.client.get('/logout/')
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path,
                          '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = urlparse.parse_qs(url.query)
        self.assert_('SAMLRequest' in params)

        saml_request = params['SAMLRequest'][0]
        expected_request26 = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
        expected_request27 = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
        self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request),
                                      {
                                          '2.6': expected_request26,
                                          '2.7': expected_request27
                                      })
Exemple #9
0
    def unravel(txt, binding, msgtype="response"):
        """
        Will unpack the received text. Depending on the context the original
         response may have been transformed before transmission.
        :param txt:
        :param binding:
        :param msgtype:
        :return:
        """
        #logger.debug("unravel '%s'" % txt)
        if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST,
                           BINDING_SOAP, BINDING_URI, BINDING_HTTP_ARTIFACT,
                           None]:
            raise ValueError("Don't know how to handle '%s'" % binding)
        else:
            try:
                if binding == BINDING_HTTP_REDIRECT:
                    xmlstr = decode_base64_and_inflate(txt)
                elif binding == BINDING_HTTP_POST:
                    xmlstr = base64.b64decode(txt)
                elif binding == BINDING_SOAP:
                    func = getattr(soap,
                                   "parse_soap_enveloped_saml_%s" % msgtype)
                    xmlstr = func(txt)
                elif binding == BINDING_HTTP_ARTIFACT:
                    xmlstr = base64.b64decode(txt)
                else:
                    xmlstr = txt
            except Exception:
                raise UnravelError()

        return xmlstr
Exemple #10
0
    def test_logout(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )
        self.do_login()

        response = self.client.get('/logout/')
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path,
                          '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = urlparse.parse_qs(url.query)
        self.assert_('SAMLRequest' in params)

        saml_request = params['SAMLRequest'][0]
        expected_request26 = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
        expected_request27 = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
        self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request),
                                      {'2.6': expected_request26,
                                       '2.7': expected_request27})
Exemple #11
0
    def test_login_several_idps(self):
        settings.SAML_CONFIG = conf.create_conf(sp_host='sp.example.com',
                                                idp_hosts=['idp1.example.com',
                                                           'idp2.example.com',
                                                           'idp3.example.com'])

        response = self.client.get('/login/')
        # a WAYF page should be displayed
        self.assertContains(response, 'Where are you from?', status_code=200)
        for i in range(1, 4):
            link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/'
            self.assertContains(response, link % i)

        # click on the second idp
        response = self.client.get('/login/', {
                'idp': 'https://idp2.example.com/simplesaml/saml2/idp/metadata.php',
                'next': '/',
                })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse.urlparse(location)
        self.assertEquals(url.hostname, 'idp2.example.com')
        self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = urlparse.parse_qs(url.query)
        self.assert_('SAMLRequest' in params)
        self.assert_('RelayState' in params)

        saml_request = params['SAMLRequest'][0]
        expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" ProviderName="Test SP" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" /></samlp:AuthnRequest>"""
        xml = decode_base64_and_inflate(saml_request)
        self.assertSAMLRequestsEquals(expected_request, xml)
Exemple #12
0
def test_inflate_then_deflate():
    str = """Selma Lagerlöf (1858-1940) was born in Östra Emterwik, Värmland, 
    Sweden. She was brought up on Mårbacka, the family estate, which she did 
    not leave until 1881, when she went to a teachers' college at Stockholm"""

    interm = utils.deflate_and_base64_encode(str)
    bis = utils.decode_base64_and_inflate(interm)
    assert bis == str
def test_inflate_then_deflate():
    txt = """Selma Lagerlöf (1858-1940) was born in Östra Emterwik, Värmland,
    Sweden. She was brought up on Mårbacka, the family estate, which she did 
    not leave until 1881, when she went to a teachers' college at Stockholm"""

    interm = utils.deflate_and_base64_encode(txt)
    bis = utils.decode_base64_and_inflate(interm)
    assert bis == txt
Exemple #14
0
 def test_Saml_handle_logout_request(self):
     not_on_or_after = time.time() + 3600
     identity = {
         'id-1': {
             'https://sso.example.com/idp/metadata': (not_on_or_after, {
                 'authn_info': [],
                 'name_id': 'id-1',
                 'not_on_or_after': not_on_or_after,
                 'came_from': '/next',
                 'ava': {
                     'uid': ['123456']
                 }
             })
         }
     }
     state = {
         'entity_ids': ['https://sso.example.com/idp/metadata'],
         'subject_id': 'id-1',
         'return_to': '/next'
     }
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # create a response to assert upon
     sp = auth.Saml(tmp_sp_config)
     logout_request = create_logout_request(
         'id-1',
         destination='https://foo.example.com/sp/slo',
         issuer_entity_id='https://sso.example.com/idp/metadata',
         req_entity_id='https://sso.example.com/idp/metadata')
     # test SAMLRequest logout
     with self.app.test_request_context(
             '/',
             method='GET',
             query_string=dict(
                 SAMLRequest=deflate_and_base64_encode(str(logout_request)),
                 RelayState=deflate_and_base64_encode(logout_request.id))):
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         session['_saml_state'] = {logout_request.id: state}
         success, resp = sp.handle_logout(request, next_url='/next')
         self.assertTrue(success)
         self.assertEqual(resp.status_code, 302)
         self.assert_("SAMLResponse" in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLResponse' in params)
         logout = samlp.logout_response_from_string(
             decode_base64_and_inflate(params['SAMLResponse'][0]))
         self.assertEqual(logout.status.status_code.value,
                          'urn:oasis:names:tc:SAML:2.0:status:Success')
         self.assertEqual(logout.destination,
                          'https://sso.example.com/idp/slo')
Exemple #15
0
 def test_Saml_authenticate(self):
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # test signed authentication request
     with self.app.test_request_context('/',
             method='GET'):
         sp = auth.Saml(tmp_sp_config)
         resp = sp.authenticate(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_('SAMLRequest' in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         self.assertEqual(url.hostname, 'sso.example.com')
         self.assertEqual(url.path, '/idp/sso')
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLRequest' in params)
         self.assertEqual(params['RelayState'], ['/next'])
         authn = samlp.authn_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertEqual(authn.destination,
             'https://sso.example.com/idp/sso')
         self.assertEqual(authn.assertion_consumer_service_url,
             'https://foo.example.com/sp/acs')
         self.assertEqual(authn.protocol_binding, BINDING_HTTP_POST)
         self.assertIsNotNone(authn.signature)
         self.assertEqual(session['_saml_outstanding_queries'],
             {authn.id: '/next'})
     # test un-signed authentication request
     with self.app.test_request_context('/',
             method='GET'):
         tmp_sp_config['key_file'] = None
         tmp_sp_config['service']['sp']['authn_requests_signed'] = None
         sp = auth.Saml(tmp_sp_config)
         resp = sp.authenticate(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_('SAMLRequest' in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         params = urlparse.parse_qs(url.query)
         authn = samlp.authn_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertIsNone(authn.signature)
Exemple #16
0
 def test_authenticate(self):
     print self.client.config.idps()
     id, response = self.client.do_authenticate(
                                     "urn:mace:example.com:saml:roland:idp",
                                     "http://www.example.com/relay_state")
     assert response[0] == "Location"
     o = urlparse(response[1])
     qdict = parse_qs(o.query)
     assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState'])
     saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
     print saml_request
     authnreq = samlp.authn_request_from_string(saml_request)
Exemple #17
0
    def logout_response(self, xmlstr, log=None, binding=BINDING_SOAP):
        """ Deal with a LogoutResponse

        :param xmlstr: The response as a xml string
        :param log: logging function
        :param binding: What type of binding this message came through.
        :return: None if the reply doesn't contain a valid SAML LogoutResponse,
            otherwise the reponse if the logout was successful and None if it 
            was not.
        """
        
        response = None
        if log is None:
            log = self.logger

        if xmlstr:
            try:
                # expected return address
                return_addr = self.config.endpoint("single_logout_service",
                                                   binding=binding)[0]
            except Exception:
                if log:
                    log.info("Not supposed to handle this!")
                return None
            
            try:
                response = LogoutResponse(self.sec, return_addr, debug=self.debug,
                                            log=log)
            except Exception, exc:
                if log:
                    log.info("%s" % exc)
                return None
                
            if binding == BINDING_HTTP_REDIRECT:
                xmlstr = decode_base64_and_inflate(xmlstr)
            elif binding == BINDING_HTTP_POST:
                xmlstr = base64.b64decode(xmlstr)

            if log:
                log.debug("XMLSTR: %s" % xmlstr)

            response = response.loads(xmlstr, False)

            if response:
                response = response.verify()
                
            if not response:
                return None
            
            if log:
                log.debug(response)
                
            return self.handle_logout_response(response, log)
Exemple #18
0
def test_inflate_then_deflate():
    txt = """Selma Lagerlöf (1858-1940) was born in Östra Emterwik, Värmland,
    Sweden. She was brought up on Mårbacka, the family estate, which she did 
    not leave until 1881, when she went to a teachers' college at Stockholm"""
    if not isinstance(txt, six.binary_type):
        txt = txt.encode('utf-8')

    interm = utils.deflate_and_base64_encode(txt)
    bis = utils.decode_base64_and_inflate(interm)
    if not isinstance(bis, six.binary_type):
        bis = bis.encode('utf-8')
    assert bis == txt
Exemple #19
0
    def do(self, message, binding, relay_state="", mtype="response"):
        try:
            txt = decode_base64_and_inflate(message)
            is_logout_request = 'LogoutRequest' in txt.split('>', 1)[0]
        except:  # TODO: parse the XML correctly
            is_logout_request = False

        if is_logout_request:
            self.sp.parse_logout_request(message, binding)
        else:
            self.sp.parse_logout_request_response(message, binding)

        return finish_logout(self.environ, self.start_response)
Exemple #20
0
    def do(self, message, binding, relay_state="", mtype="response"):
        try:
            txt = decode_base64_and_inflate(message)
            is_logout_request = "LogoutRequest" in txt.split(">", 1)[0]
        except:  # TODO: parse the XML correctly
            is_logout_request = False

        if is_logout_request:
            self.sp.parse_logout_request(message, binding)
        else:
            self.sp.parse_logout_request_response(message, binding)

        return finish_logout(self.environ, self.start_response)
Exemple #21
0
 def test_Saml_authenticate(self):
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # test signed authentication request
     with self.app.test_request_context('/', method='GET'):
         sp = auth.Saml(tmp_sp_config)
         resp = sp.authenticate(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_('SAMLRequest' in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         self.assertEqual(url.hostname, 'sso.example.com')
         self.assertEqual(url.path, '/idp/sso')
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLRequest' in params)
         self.assertEqual(params['RelayState'], ['/next'])
         authn = samlp.authn_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertEqual(authn.destination,
                          'https://sso.example.com/idp/sso')
         self.assertEqual(authn.assertion_consumer_service_url,
                          'https://foo.example.com/sp/acs')
         self.assertEqual(authn.protocol_binding, BINDING_HTTP_POST)
         self.assertIsNotNone(authn.signature)
         self.assertEqual(session['_saml_outstanding_queries'],
                          {authn.id: '/next'})
     # test un-signed authentication request
     with self.app.test_request_context('/', method='GET'):
         tmp_sp_config['key_file'] = None
         tmp_sp_config['service']['sp']['authn_requests_signed'] = None
         sp = auth.Saml(tmp_sp_config)
         resp = sp.authenticate(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_('SAMLRequest' in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         params = urlparse.parse_qs(url.query)
         authn = samlp.authn_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertIsNone(authn.signature)
Exemple #22
0
 def test_authenticate(self):
     print self.client.config.idps()
     (sid, response) = self.client.authenticate(
         "urn:mace:example.com:saml:roland:idp",
         "http://www.example.com/relay_state")
     assert sid is not None
     assert response[0] == "Location"
     o = urlparse(response[1])
     qdict = parse_qs(o.query)
     assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState'])
     saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
     print saml_request
     authnreq = samlp.authn_request_from_string(saml_request)
     assert authnreq.id == sid
Exemple #23
0
    def http_redirect_logout_request(self, get, subject_id, log=None):
        """ Deal with a LogoutRequest received through HTTP redirect

        :param get: The request as a dictionary 
        :param subject_id: the id of the current logged user
        :return: a tuple with a list of header tuples (presently only location)
            and a status which will be True in case of success or False 
            otherwise.
        """
        headers = []
        success = False
        if log is None:
            log = self.logger

        try:
            saml_request = get['SAMLRequest']
        except KeyError:
            return None

        if saml_request:
            xml = decode_base64_and_inflate(saml_request)

            request = samlp.logout_request_from_string(xml)
            if log:
                log.debug(request)

            if request.name_id.text == subject_id:
                status = samlp.STATUS_SUCCESS
                success = self.local_logout(subject_id)
            else:
                status = samlp.STATUS_REQUEST_DENIED

            response, destination = self .make_logout_response(
                                                        request.issuer.text,
                                                        request.id,
                                                        status)

            if log:
                log.info("RESPONSE: {0:>s}".format(response))

            if 'RelayState' in get:
                rstate = get['RelayState']
            else:
                rstate = ""
                
            (headers, _body) = http_redirect_message(str(response), 
                                                    destination, 
                                                    rstate, 'SAMLResponse')

        return headers, success
Exemple #24
0
    def test_login_one_idp(self):
        # monkey patch SAML configuration
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )

        response = self.client.get(reverse('saml2_login'))
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assert_('SAMLRequest' in params)
        self.assert_('RelayState' in params)

        saml_request = params['SAMLRequest'][0]
        if PY_VERSION < (2, 7):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
        elif PY_VERSION < (3, ):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
        else:
            expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""

        self.assertSAMLRequestsEquals(
            decode_base64_and_inflate(saml_request).decode('utf-8'),
            expected_request)

        # if we set a next arg in the login view, it is preserverd
        # in the RelayState argument
        next = '/another-view/'
        response = self.client.get(reverse('saml2_login'), {'next': next})
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assert_('SAMLRequest' in params)
        self.assert_('RelayState' in params)
        self.assertEquals(params['RelayState'][0], next)
Exemple #25
0
 def test_Saml_handle_logout_request(self):
     not_on_or_after = time.time()+3600
     identity = {'id-1': {
         'https://sso.example.com/idp/metadata': (
             not_on_or_after, {
                 'authn_info': [],
                 'name_id': 'id-1',
                 'not_on_or_after': not_on_or_after,
                 'came_from': '/next',
                 'ava': {'uid': ['123456']}
             }
         )
     }}
     state = {
         'entity_ids': ['https://sso.example.com/idp/metadata'],
         'subject_id': 'id-1',
         'return_to': '/next'
     }
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # create a response to assert upon
     sp = auth.Saml(tmp_sp_config)
     logout_request = create_logout_request('id-1',
         destination='https://foo.example.com/sp/slo',
         issuer_entity_id='https://sso.example.com/idp/metadata',
         req_entity_id='https://sso.example.com/idp/metadata')
     # test SAMLRequest logout
     with self.app.test_request_context('/',
             method='GET',
             query_string=dict(
                 SAMLRequest=deflate_and_base64_encode(str(logout_request)),
                 RelayState=deflate_and_base64_encode(logout_request.id))):
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         session['_saml_state'] = {logout_request.id: state}
         success, resp = sp.handle_logout(request, next_url='/next')
         self.assertTrue(success)
         self.assertEqual(resp.status_code, 302)
         self.assert_("SAMLResponse" in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLResponse' in params)
         logout = samlp.logout_response_from_string(
             decode_base64_and_inflate(params['SAMLResponse'][0]))
         self.assertEqual(logout.status.status_code.value,
             'urn:oasis:names:tc:SAML:2.0:status:Success')
         self.assertEqual(logout.destination, 'https://sso.example.com/idp/slo')
Exemple #26
0
    def test_login_one_idp(self):
        # monkey patch SAML configuration
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )

        response = self.client.get(reverse('saml2_login'))
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assert_('SAMLRequest' in params)
        self.assert_('RelayState' in params)

        saml_request = params['SAMLRequest'][0]
        if PY_VERSION < (2, 7):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
        elif PY_VERSION < (3,):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
        else:
            expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""

        self.assertSAMLRequestsEquals(
            decode_base64_and_inflate(saml_request).decode('utf-8'),
            expected_request)

        # if we set a next arg in the login view, it is preserverd
        # in the RelayState argument
        next = '/another-view/'
        response = self.client.get(reverse('saml2_login'), {'next': next})
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assert_('SAMLRequest' in params)
        self.assert_('RelayState' in params)
        self.assertEquals(params['RelayState'][0], next)
Exemple #27
0
    def unravel(self, txt, binding, msgtype="response"):
        #logger.debug("unravel '%s'" % txt)
        if binding == BINDING_HTTP_REDIRECT:
            xmlstr = decode_base64_and_inflate(txt)
        elif binding == BINDING_HTTP_POST:
            xmlstr = base64.b64decode(txt)
        elif binding == BINDING_SOAP:
            func = getattr(soap, "parse_soap_enveloped_saml_%s" % msgtype)
            xmlstr = func(txt)
        elif binding == BINDING_URI or binding is None:
            xmlstr = txt
        else:
            raise ValueError("Don't know how to handle '%s'" % binding)

        return xmlstr
Exemple #28
0
    def test_logout_service_local(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )

        self.do_login()

        response = self.client.get(reverse('saml2_logout'))
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path,
                          '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = parse_qs(url.query)
        self.assert_('SAMLRequest' in params)

        saml_request = params['SAMLRequest'][0]
        if PY_VERSION < (2, 7):
            expected_request = """<?xml version='1.0' encoing='UTF-8'?>
<samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
        elif PY_VERSION < (3, ):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
        else:
            expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
        self.assertSAMLRequestsEquals(
            decode_base64_and_inflate(saml_request).decode('utf-8'),
            expected_request)

        # now simulate a logout response sent by the idp
        request_id = re.findall(r' ID="(.*?)" ', xml)[0]
        instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')

        saml_response = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % (
            request_id, instant)

        response = self.client.get(
            reverse('saml2_ls'), {
                'SAMLResponse': deflate_and_base64_encode(saml_response),
            })
        self.assertContains(response, "Logged out", status_code=200)
        self.assertEquals(self.client.session.keys(), [])
Exemple #29
0
    def _loads(self, xmldata, decode=True):
        if decode:
            logger.debug("Expected to decode and inflate xml data")
            decoded_xml = s_utils.decode_base64_and_inflate(xmldata)
        else:
            decoded_xml = xmldata

        # own copy
        self.xmlstr = decoded_xml[:]
        logger.info("xmlstr: %s" % (self.xmlstr, ))
        try:
            self.message = self.signature_check(decoded_xml)
        except TypeError:
            raise
        except Exception, excp:
            logger.info("EXCEPTION: %s", excp)
Exemple #30
0
 def _loads(self, xmldata, decode=True):
     if decode:
         logger.debug("Expected to decode and inflate xml data")
         decoded_xml = s_utils.decode_base64_and_inflate(xmldata)
     else:
         decoded_xml = xmldata
 
     # own copy
     self.xmlstr = decoded_xml[:]
     logger.info("xmlstr: %s" % (self.xmlstr,))
     try:
         self.message = self.signature_check(decoded_xml)
     except TypeError:
         raise
     except Exception, excp:
         logger.info("EXCEPTION: %s", excp)
Exemple #31
0
    def test_login_several_idps(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=[
                'idp1.example.com', 'idp2.example.com', 'idp3.example.com'
            ],
            metadata_file='remote_metadata_three_idps.xml',
        )
        response = self.client.get(reverse('saml2_login'))
        # a WAYF page should be displayed
        self.assertContains(response, 'Where are you from?', status_code=200)
        for i in range(1, 4):
            link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/'
            self.assertContains(response, link % i)

        # click on the second idp
        response = self.client.get(
            reverse('saml2_login'), {
                'idp':
                'https://idp2.example.com/simplesaml/saml2/idp/metadata.php',
                'next': '/',
            })
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEquals(url.hostname, 'idp2.example.com')
        self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assert_('SAMLRequest' in params)
        self.assert_('RelayState' in params)

        saml_request = params['SAMLRequest'][0]
        if PY_VERSION < (2, 7):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
        elif PY_VERSION < (3, ):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
        else:
            expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""

        self.assertSAMLRequestsEquals(
            decode_base64_and_inflate(saml_request).decode('utf-8'),
            expected_request)
Exemple #32
0
    def test_logout_service_local(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )

        self.do_login()

        response = self.client.get(reverse('saml2_logout'))
        self.assertEquals(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEquals(url.hostname, 'idp.example.com')
        self.assertEquals(url.path,
                          '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = parse_qs(url.query)
        self.assert_('SAMLRequest' in params)

        saml_request = params['SAMLRequest'][0]
        if PY_VERSION < (2, 7):
            expected_request = """<?xml version='1.0' encoing='UTF-8'?>
<samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
        elif PY_VERSION < (3,):
            expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
        else:
            expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
        self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'),
                                      expected_request)

        # now simulate a logout response sent by the idp
        request_id = re.findall(r' ID="(.*?)" ', xml)[0]
        instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')

        saml_response = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % (
            request_id, instant)

        response = self.client.get(reverse('saml2_ls'), {
                'SAMLResponse': deflate_and_base64_encode(saml_response),
                })
        self.assertContains(response, "Logged out", status_code=200)
        self.assertEquals(self.client.session.keys(), [])
Exemple #33
0
    def logout_request_response(self, xmlstr, binding=BINDING_SOAP):
        """ Deal with a LogoutResponse

        :param xmlstr: The response as a xml string
        :param binding: What type of binding this message came through.
        :return: None if the reply doesn't contain a valid SAML LogoutResponse,
            otherwise the reponse if the logout was successful and None if it
            was not.
        """

        response = None

        if xmlstr:
            if binding == BINDING_HTTP_REDIRECT:
                try:
                    # expected return address
                    return_addr = self.config.endpoint("single_logout_service",
                                                       binding=binding)[0]
                except Exception:
                    logger.info("Not supposed to handle this!")
                    return None
            else:
                return_addr = None

            try:
                response = LogoutResponse(self.sec, return_addr)
            except Exception, exc:
                logger.info("%s" % exc)
                return None

            if binding == BINDING_HTTP_REDIRECT:
                xmlstr = decode_base64_and_inflate(xmlstr)
            elif binding == BINDING_HTTP_POST:
                xmlstr = base64.b64decode(xmlstr)

            logger.debug("XMLSTR: %s" % xmlstr)

            response = response.loads(xmlstr, False)

            if response:
                response = response.verify()

            if not response:
                return None

            logger.debug(response)
Exemple #34
0
    def http_redirect_logout_request(self, get, subject_id):
        """ Deal with a LogoutRequest received through HTTP redirect

        :param get: The request as a dictionary 
        :param subject_id: the id of the current logged user
        :return: a tuple with a list of header tuples (presently only location)
            and a status which will be True in case of success or False 
            otherwise.
        """
        headers = []
        success = False

        try:
            saml_request = get['SAMLRequest']
        except KeyError:
            return None

        if saml_request:
            xml = decode_base64_and_inflate(saml_request)

            request = samlp.logout_request_from_string(xml)
            logger.debug(request)

            if request.name_id.text == subject_id:
                status = samlp.STATUS_SUCCESS
                success = self.local_logout(subject_id)
            else:
                status = samlp.STATUS_REQUEST_DENIED

            response, destination = self.make_logout_response(
                request.issuer.text, request.id, status)

            logger.info("RESPONSE: {0:>s}".format(response))

            if 'RelayState' in get:
                rstate = get['RelayState']
            else:
                rstate = ""

            (headers, _body) = http_redirect_message(str(response),
                                                     destination, rstate,
                                                     'SAMLResponse')

        return headers, success
Exemple #35
0
    def test_logout_service_local(self):
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )

        self.do_login()

        response = self.client.get(reverse('saml2_logout'))
        self.assertEqual(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEqual(url.hostname, 'idp.example.com')
        self.assertEqual(url.path,
                         '/simplesaml/saml2/idp/SingleLogoutService.php')

        params = parse_qs(url.query)
        self.assertIn('SAMLRequest', params)

        saml_request = params['SAMLRequest'][0]

        self.assertIn('LogoutRequest xmlns',
                      decode_base64_and_inflate(saml_request).decode('utf-8'),
                      'Not a valid LogoutRequest')

        # now simulate a logout response sent by the idp
        expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="XXXXXXXXXXXXXXXXXXXXXX" Version="2.0" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" Reason=""><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID SPNameQualifier="http://sp.example.com/saml2/metadata/" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">1f87035b4c1325b296a53d92097e6b3fa36d7e30ee82e3fcb0680d60243c1f03</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""

        request_id = re.findall(r' ID="(.*?)" ', expected_request)[0]
        instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')

        saml_response = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % (
            request_id, instant)

        response = self.client.get(
            reverse('saml2_ls'), {
                'SAMLResponse': deflate_and_base64_encode(saml_response),
            })
        self.assertContains(response, "Logged out", status_code=200)
        self.assertListEqual(list(self.client.session.keys()), [])
Exemple #36
0
 def test_authenticate_no_args(self):
     id, response = self.client.do_authenticate(relay_state="http://www.example.com/relay_state")
     assert response[0] == "Location"
     o = urlparse(response[1])
     qdict = parse_qs(o.query)
     assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState'])
     saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
     assert qdict["RelayState"][0] == "http://www.example.com/relay_state"
     print saml_request
     authnreq = samlp.authn_request_from_string(saml_request)
     print authnreq.keyswv()
     assert authnreq.destination == "http://localhost:8088/sso"
     assert authnreq.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
     assert authnreq.provider_name == "urn:mace:example.com:saml:roland:sp"
     assert authnreq.protocol_binding == BINDING_HTTP_REDIRECT
     name_id_policy = authnreq.name_id_policy
     assert name_id_policy.allow_create == "false"
     assert name_id_policy.format == NAMEID_FORMAT_PERSISTENT
     issuer = authnreq.issuer
     assert issuer.text == "urn:mace:example.com:saml:roland:sp"
    def http_redirect_logout_request_check_session_index(self, get, session_index, log=None):
        """ Deal with a LogoutRequest received through HTTP redirect

        :param get: The request as a dictionary 
        :param subject_id: the id of the current logged user
        :return: a tuple with a list of header tuples (presently only location)
        """
        msg = {}

        try:
            saml_request = get['SAMLRequest']
        except KeyError:
            return None

        if saml_request:
            xml = decode_base64_and_inflate(saml_request)
            logger.info('logout request: %s' % xml)
            request = samlp.logout_request_from_string(xml)
            logger.debug(request)

            if request.session_index[0].text == session_index:
                status = samlp.STATUS_SUCCESS
            else:
                status = samlp.STATUS_REQUEST_DENIED

            response, destination = self .make_logout_response(
                                                        request.issuer.text,
                                                        request.id,
                                                        status)

            logger.info("RESPONSE: {0:>s}".format(response))

            if 'RelayState' in get:
                rstate = get['RelayState']
            else:
                rstate = ""
            msg = http_redirect_message(str(response),
                                        destination,
                                        rstate, 'SAMLResponse')

        return msg
Exemple #38
0
    def test_login_one_idp(self):
        # monkey patch SAML configuration
        settings.SAML_CONFIG = conf.create_conf(
            sp_host='sp.example.com',
            idp_hosts=['idp.example.com'],
            metadata_file='remote_metadata_one_idp.xml',
        )

        response = self.client.get(reverse('saml2_login'))
        self.assertEqual(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEqual(url.hostname, 'idp.example.com')
        self.assertEqual(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assertIn('SAMLRequest', params)
        self.assertIn('RelayState', params)

        saml_request = params['SAMLRequest'][0]
        if 'AuthnRequest xmlns' not in decode_base64_and_inflate(
                saml_request).decode('utf-8'):
            raise Exception('Not a valid AuthnRequest')

        # if we set a next arg in the login view, it is preserverd
        # in the RelayState argument
        next = '/another-view/'
        response = self.client.get(reverse('saml2_login'), {'next': next})
        self.assertEqual(response.status_code, 302)
        location = response['Location']

        url = urlparse(location)
        self.assertEqual(url.hostname, 'idp.example.com')
        self.assertEqual(url.path, '/simplesaml/saml2/idp/SSOService.php')

        params = parse_qs(url.query)
        self.assertIn('SAMLRequest', params)
        self.assertIn('RelayState', params)
        self.assertEqual(params['RelayState'][0], next)
Exemple #39
0
 def unravel(txt, binding, msgtype="response"):
     """
     Will unpack the received text. Depending on the context the original
         response may have been transformed before transmission.
     :param txt:
     :param binding:
     :param msgtype:
     :return:
     """
     if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST, None]:
         raise UnknownBinding("Don't know how to handle '%s'" % binding)
     else:
         try:
             if binding == BINDING_HTTP_REDIRECT:
                 xmlstr = decode_base64_and_inflate(txt)
             elif binding == BINDING_HTTP_POST:
                 xmlstr = base64.b64decode(txt)
             else:
                 xmlstr = txt
         except Exception:
             raise UnravelError("Unravelling binding '%s' failed" % binding)
     return xmlstr
Exemple #40
0
 def test_authenticate_no_args(self):
     (sid, request) = self.client.authenticate(relay_state="http://www.example.com/relay_state")
     assert sid is not None
     assert request[0] == "Location"
     o = urlparse(request[1])
     qdict = parse_qs(o.query)
     assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState'])
     saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
     assert qdict["RelayState"][0] == "http://www.example.com/relay_state"
     print saml_request
     authnreq = samlp.authn_request_from_string(saml_request)
     print authnreq.keyswv()
     assert authnreq.id == sid
     assert authnreq.destination == "http://localhost:8088/sso"
     assert authnreq.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
     assert authnreq.provider_name == "urn:mace:example.com:saml:roland:sp"
     assert authnreq.protocol_binding == BINDING_HTTP_POST
     name_id_policy = authnreq.name_id_policy
     assert name_id_policy.allow_create == "true" 
     assert name_id_policy.format == "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" 
     issuer = authnreq.issuer
     assert issuer.text == "urn:mace:example.com:saml:roland:sp"
Exemple #41
0
def create_logout_response(subject_id, destination, issuer_entity_id,
        req_entity_id, sign=True):
    config = IdPConfig()
    config.load(idp_config)
    idp_server = Server(config=config)
    # construct a request
    logout_request = create_logout_request(
        subject_id=subject_id,
        destination=destination,
        issuer_entity_id=issuer_entity_id,
        req_entity_id=req_entity_id)
    #idp_server.ident = Identifier(auth.AuthDictCache(dict(), '_ident'))
    resp, headers, message = idp_server.logout_response(
        request=logout_request,
        bindings=[BINDING_HTTP_REDIRECT],
        sign=sign)
    location = dict(headers).get('Location')
    url = urlparse.urlparse(location)
    params = urlparse.parse_qs(url.query)
    logout_response_xml = decode_base64_and_inflate(params['SAMLResponse'][0])
    response = samlp.logout_response_from_string(logout_response_xml)
    return response.in_response_to, logout_response_xml
Exemple #42
0
    def http_redirect_logout_request_check_session_index(
            self, get, session_index, log=None):
        """ Deal with a LogoutRequest received through HTTP redirect

        :param get: The request as a dictionary 
        :param subject_id: the id of the current logged user
        :return: a tuple with a list of header tuples (presently only location)
        """
        msg = {}

        try:
            saml_request = get['SAMLRequest']
        except KeyError:
            return None

        if saml_request:
            xml = decode_base64_and_inflate(saml_request)
            logger.info('logout request: %s' % xml)
            request = samlp.logout_request_from_string(xml)
            logger.debug(request)

            if request.session_index[0].text == session_index:
                status = samlp.STATUS_SUCCESS
            else:
                status = samlp.STATUS_REQUEST_DENIED

            response, destination = self.make_logout_response(
                request.issuer.text, request.id, status)

            logger.info("RESPONSE: {0:>s}".format(response))

            if 'RelayState' in get:
                rstate = get['RelayState']
            else:
                rstate = ""
            msg = http_redirect_message(str(response), destination, rstate,
                                        'SAMLResponse')

        return msg
Exemple #43
0
def create_logout_response(subject_id,
                           destination,
                           issuer_entity_id,
                           req_entity_id,
                           sign=True):
    config = IdPConfig()
    config.load(idp_config)
    idp_server = Server(config=config)
    # construct a request
    logout_request = create_logout_request(subject_id=subject_id,
                                           destination=destination,
                                           issuer_entity_id=issuer_entity_id,
                                           req_entity_id=req_entity_id)
    #idp_server.ident = Identifier(auth.AuthDictCache(dict(), '_ident'))
    resp, headers, message = idp_server.logout_response(
        request=logout_request, bindings=[BINDING_HTTP_REDIRECT], sign=sign)
    location = dict(headers).get('Location')
    url = urlparse.urlparse(location)
    params = urlparse.parse_qs(url.query)
    logout_response_xml = decode_base64_and_inflate(params['SAMLResponse'][0])
    response = samlp.logout_response_from_string(logout_response_xml)
    return response.in_response_to, logout_response_xml
Exemple #44
0
 def test_authenticate_no_args(self):
     (sid, request) = self.client.authenticate(
         relay_state="http://www.example.com/relay_state")
     assert sid is not None
     assert request[0] == "Location"
     o = urlparse(request[1])
     qdict = parse_qs(o.query)
     assert _leq(qdict.keys(), ['SAMLRequest', 'RelayState'])
     saml_request = decode_base64_and_inflate(qdict["SAMLRequest"][0])
     assert qdict["RelayState"][0] == "http://www.example.com/relay_state"
     print saml_request
     authnreq = samlp.authn_request_from_string(saml_request)
     print authnreq.keyswv()
     assert authnreq.id == sid
     assert authnreq.destination == "http://localhost:8088/sso"
     assert authnreq.assertion_consumer_service_url == "http://lingon.catalogix.se:8087/"
     assert authnreq.provider_name == "urn:mace:example.com:saml:roland:sp"
     assert authnreq.protocol_binding == BINDING_HTTP_POST
     name_id_policy = authnreq.name_id_policy
     assert name_id_policy.allow_create == "true"
     assert name_id_policy.format == "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
     issuer = authnreq.issuer
     assert issuer.text == "urn:mace:example.com:saml:roland:sp"
Exemple #45
0
    def unravel(self, txt, binding, msgtype="response"):
        #logger.debug("unravel '%s'" % txt)
        if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST,
                           BINDING_SOAP, BINDING_URI, BINDING_HTTP_ARTIFACT,
                           None]:
            raise ValueError("Don't know how to handle '%s'" % binding)
        else:
            try:
                if binding == BINDING_HTTP_REDIRECT:
                    xmlstr = decode_base64_and_inflate(txt)
                elif binding == BINDING_HTTP_POST:
                    xmlstr = base64.b64decode(txt)
                elif binding == BINDING_SOAP:
                    func = getattr(soap, "parse_soap_enveloped_saml_%s" % msgtype)
                    xmlstr = func(txt)
                elif binding == BINDING_HTTP_ARTIFACT:
                    xmlstr = base64.b64decode(txt)
                else:
                    xmlstr = txt
            except Exception:
                raise UnravelError()

        return xmlstr
Exemple #46
0
 def test_logout_response_http_redirect(self, unravel, verified):
     # See: https://github.com/italia/spid-testenv2/issues/88
     with patch('testenv.server.IdpServer._sp_single_logout_service',
                return_value=_sp_single_logout_service(
                    self.idp_server.server, 'https://spid.test:8000',
                    BINDING_HTTP_REDIRECT)) as mocked:
         response = self.test_client.get(
             '/slo-test?SAMLRequest=b64encodedrequest&SigAlg={}&Signature=sign'
             .format(quote(SIG_RSA_SHA256)),
             follow_redirects=False)
         self.assertEqual(response.status_code, 302)
         response_location = response.headers.get('Location')
         url = urlparse(response_location)
         query = parse_qs(url.query)
         self.assertIn('Signature', query)
         saml_response = query.get('SAMLResponse')[0]
         response = decode_base64_and_inflate(saml_response)
         xml = ET.fromstring(response)
         signatures = xml.findall(
             './/{http://www.w3.org/2000/09/xmldsig#}Signature')
         self.assertEqual(0, len(signatures))
         self.assertEqual(len(self.idp_server.ticket), 0)
         self.assertEqual(len(self.idp_server.responses), 0)
Exemple #47
0
 def _parse_authn_request(self, enc_request):
     """
     enc_request is an encrypted and base64 encoded request
     """
     xmlstr = decode_base64_and_inflate(urllib.unquote(enc_request))
     return authn_request_from_string(xmlstr)
Exemple #48
0
 def test_Saml_logout(self):
     not_on_or_after = time.time()+3600
     identity = {'id-1': {
         'https://sso.example.com/idp/metadata': (
             not_on_or_after, {
                 'authn_info': [],
                 'name_id': 'id-1',
                 'not_on_or_after': not_on_or_after,
                 'came_from': '/next',
                 'ava': {'uid': ['123456']}
             }
         )
     }}
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     with self.app.test_request_context('/',
             method='GET'):
         sp = auth.Saml(tmp_sp_config)
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         resp = sp.logout(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_("SAMLRequest" in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         self.assertEqual(url.hostname, 'sso.example.com')
         self.assertEqual(url.path, '/idp/slo')
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLRequest' in params)
         logout = samlp.logout_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertEqual(logout.destination,
             'https://sso.example.com/idp/slo')
         self.assertEqual(logout.name_id.text, 'id-1')
         self.assertIsNotNone(logout.signature)
         # check the caches still contain data
         self.assertEqual(session['_saml_identity'], identity)
         self.assertEqual(session['_saml_subject_id'], 'id-1')
         # verify state cache
         self.assert_(logout.id in session['_saml_state'])
         self.assertEqual(session['_saml_state'][logout.id]['entity_id'],
             'https://sso.example.com/idp/metadata')
         self.assertEqual(session['_saml_state'][logout.id]['operation'],
             'SLO')
         self.assertEqual(session['_saml_state'][logout.id]['subject_id'],
             'id-1')
         self.assertEqual(session['_saml_state'][logout.id]['return_to'],
             '/next')
         self.assertTrue(session['_saml_state'][logout.id]['sign'])
     # test unsigned logout request
     with self.app.test_request_context('/',
             method='GET'):
         tmp_sp_config['key_file'] = None
         tmp_sp_config['service']['sp']['logout_requests_signed'] = 'false'
         sp = auth.Saml(tmp_sp_config)
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         resp = sp.logout(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_("SAMLRequest" in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLRequest' in params)
         logout = samlp.logout_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertIsNone(logout.signature)
         # verify state cache shows signing off
         self.assertFalse(session['_saml_state'][logout.id]['sign'])
Exemple #49
0
 def _parse_logout_response(self, enc_response):
     xmlstr = decode_base64_and_inflate(urllib.unquote(enc_response))
     return logout_response_from_string(xmlstr)
Exemple #50
0
 def test_Saml_logout(self):
     not_on_or_after = time.time() + 3600
     identity = {
         'id-1': {
             'https://sso.example.com/idp/metadata': (not_on_or_after, {
                 'authn_info': [],
                 'name_id': 'id-1',
                 'not_on_or_after': not_on_or_after,
                 'came_from': '/next',
                 'ava': {
                     'uid': ['123456']
                 }
             })
         }
     }
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     with self.app.test_request_context('/', method='GET'):
         sp = auth.Saml(tmp_sp_config)
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         resp = sp.logout(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_("SAMLRequest" in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         self.assertEqual(url.hostname, 'sso.example.com')
         self.assertEqual(url.path, '/idp/slo')
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLRequest' in params)
         logout = samlp.logout_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertEqual(logout.destination,
                          'https://sso.example.com/idp/slo')
         self.assertEqual(logout.name_id.text, 'id-1')
         self.assertIsNotNone(logout.signature)
         # check the caches still contain data
         self.assertEqual(session['_saml_identity'], identity)
         self.assertEqual(session['_saml_subject_id'], 'id-1')
         # verify state cache
         self.assert_(logout.id in session['_saml_state'])
         self.assertEqual(session['_saml_state'][logout.id]['entity_id'],
                          'https://sso.example.com/idp/metadata')
         self.assertEqual(session['_saml_state'][logout.id]['operation'],
                          'SLO')
         self.assertEqual(session['_saml_state'][logout.id]['subject_id'],
                          'id-1')
         self.assertEqual(session['_saml_state'][logout.id]['return_to'],
                          '/next')
         self.assertTrue(session['_saml_state'][logout.id]['sign'])
     # test unsigned logout request
     with self.app.test_request_context('/', method='GET'):
         tmp_sp_config['key_file'] = None
         tmp_sp_config['service']['sp']['logout_requests_signed'] = 'false'
         sp = auth.Saml(tmp_sp_config)
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         resp = sp.logout(next_url='/next')
         self.assertEqual(resp.status_code, 302)
         self.assert_("SAMLRequest" in resp.headers['Location'])
         url = urlparse.urlparse(resp.headers['Location'])
         params = urlparse.parse_qs(url.query)
         self.assert_('SAMLRequest' in params)
         logout = samlp.logout_request_from_string(
             decode_base64_and_inflate(params['SAMLRequest'][0]))
         self.assertIsNone(logout.signature)
         # verify state cache shows signing off
         self.assertFalse(session['_saml_state'][logout.id]['sign'])