コード例 #1
0
 def test_Saml_get_metadata(self):
     entity_id = 'https://sso.example.com/idp/metadata'
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # test with defined private key file
     with self.app.test_request_context('/', method='GET'):
         sp = auth.Saml(tmp_sp_config)
         resp = sp.get_metadata()
         self.assertTrue(
             'Content-type: text/xml; charset=utf-8' in str(resp.headers))
         metadata_xml = resp.data
         self.assert_("Signature" in metadata_xml)
         md = MetaData(tmp_sp_config['xmlsec_binary'])
         md.import_metadata(metadata_xml, 'sp_config')
         self.assertEqual(
             sp._config.single_logout_services(entity_id,
                                               BINDING_HTTP_REDIRECT),
             ['https://sso.example.com/idp/slo'])
         self.assertEqual(sp._config.single_sign_on_services(entity_id),
                          ['https://sso.example.com/idp/sso'])
     # test without defined private key file
     with self.app.test_request_context('/', method='GET'):
         tmp_sp_config['key_file'] = None
         sp = auth.Saml(tmp_sp_config)
         resp = sp.get_metadata()
         self.assertTrue(
             'Content-type: text/xml; charset=utf-8' in str(resp.headers))
         metadata_xml = resp.data
         self.assert_(not "Signature" in metadata_xml)
コード例 #2
0
 def test_Saml_logout_invalid_config(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'):
         tmp_sp_config['key_file'] = None
         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'
         try:
             sp.logout(next_url='/next')
             self.fail(
                 'Expected AuthException on invalid Saml logout request')
         except auth.AuthException, e:
             self.assertEqual(
                 'Signature requested for this Saml logout request,'
                 ' but no private key file configured', str(e))
コード例 #3
0
 def test_Saml_logout_via_post(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)
     # test signed authentication request
     with self.app.test_request_context('/', method='GET'):
         tmp_sp_config['metadata'] = {
             'local': [root_path + '/idp_post_metadata.xml']
         }
         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, 200)
         self.assert_('SAMLRequest' in resp.data)
         self.assert_('RelayState' in resp.data)
コード例 #4
0
 def test_Saml_handle_assertion_allow_unsolicited(self):
     ava = {'uid': '123456'}
     session_id = 'a0123456789abcdef0123456789abcdef'
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # The following setting allows for handling unsolicited
     # assertions which ironically is the expected behavior according to
     # the SAML 2.0 specification
     tmp_sp_config['service']['sp']['allow_unsolicited'] = 'true'
     # create a response to assert upon
     name_id, authn_response = create_authn_response(session_id, ava)
     self.assert_('Signature' in authn_response)
     # test success if no outstanding queries exist
     with self.app.test_request_context(
             '/',
             method='POST',
             data=dict(SAMLResponse=base64.b64encode(authn_response),
                       RelayState='/next')):
         sp = auth.Saml(tmp_sp_config)
         user_id, user_attributes, resp = sp.handle_assertion(request)
         self.assertEqual(user_id, name_id)
         self.assertEqual(resp.status_code, 302)
         self.assertEqual(resp.headers['Location'], '/next')
         # identity and subject_id should now be set
         self.assert_(name_id in session.get('_saml_identity'))
         self.assertEqual(session.get('_saml_subject_id'), name_id)
     # test success if outstanding queries exist
     with self.app.test_request_context(
             '/',
             method='POST',
             data=dict(SAMLResponse=base64.b64encode(authn_response),
                       RelayState='/next')):
         sp = auth.Saml(tmp_sp_config)
         session['_saml_outstanding_queries'] = {session_id: '/next'}
         user_id, user_attributes, resp = sp.handle_assertion(request)
         self.assertEqual(user_id, name_id)
         self.assertEqual(resp.status_code, 302)
         self.assertEqual(resp.headers['Location'], '/next')
         # outstanding queury cache should now be empty
         self.assertEqual(session.get('_saml_outstanding_queries', {}), {})
         # identity and subject_id should now be set
         self.assert_(name_id in session.get('_saml_identity'))
         self.assertEqual(session.get('_saml_subject_id'), name_id)
コード例 #5
0
 def test_Saml_init(self):
     entity_id = 'https://sso.example.com/idp/metadata'
     with self.app.test_request_context('/', method='GET'):
         try:
             sp = auth.Saml({'service': {'sp': 'invalid'}})
             self.fail(
                 'Expected TypeError on invalid submission to Saml __init__'
             )
         except TypeError:
             pass
         sp = auth.Saml(sp_config)
         self.assertEqual(sp._config.idps(),
                          {entity_id: 'https://sso.example.com/idp/sso'})
         self.assertEqual(
             sp._config.single_logout_services(entity_id,
                                               BINDING_HTTP_REDIRECT),
             ['https://sso.example.com/idp/slo'])
         self.assertEqual(sp._config.single_sign_on_services(entity_id),
                          ['https://sso.example.com/idp/sso'])
コード例 #6
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')
コード例 #7
0
 def test_Saml_logout_not_logged_in(self):
     # 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)
         try:
             sp.logout(next_url='/next')
             self.fail('Expected AuthException on attempted logout when'
                       ' not logged in')
         except auth.AuthException, e:
             self.assertEqual('Unable to retrieve subject id for logout',
                              str(e))
コード例 #8
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)
コード例 #9
0
 def test_Saml_authenticate_no_idp(self):
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # test using binding method not configured for in IdP metedata
     with self.app.test_request_context('/', method='GET'):
         sp = auth.Saml(tmp_sp_config)
         try:
             sp.authenticate(next_url='/next', binding=BINDING_HTTP_POST)
             self.fail(
                 'Expected AuthException on invalid Saml authentication')
         except auth.AuthException, e:
             self.assertEqual('Unable to locate valid IdP for this request',
                              str(e))
コード例 #10
0
 def test_Saml_handle_logout_invalid_missing(self):
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # test missing any GET/POST parameters
     with self.app.test_request_context('/', method='GET'):
         sp = auth.Saml(tmp_sp_config)
         try:
             sp.handle_logout(request)
             self.fail('Expected BadRequest on missing SAMLResponse'
                       ' GET arg')
         except BadRequest, e:
             self.assertEqual('400: Unable to find supported binding',
                              str(e))
コード例 #11
0
 def test_Saml_init_idp_as_config(self):
     tmp_sp_config = copy.deepcopy(sp_config)
     entity_id = 'https://sso.example.com/idp/metadata'
     tmp_sp_config['metadata'] = {
         'config': [{
             'entityid':
             entity_id,
             'contact_person': [{
                 'email_address': '*****@*****.**',
                 'type': 'technical',
             }],
             'service': {
                 'idp': {
                     'name': 'Test Identity Provider',
                     'endpoints': {
                         'single_sign_on_service':
                         [('https://sso.example.com/idp/sso',
                           BINDING_HTTP_REDIRECT)],
                         'single_logout_service':
                         [('https://sso.example.com/idp/slo',
                           BINDING_HTTP_REDIRECT)],
                     },
                     'policy': {
                         'default': {
                             'lifetime': {
                                 'hours': 24
                             },
                             'attribute_restrictions':
                             None,
                             'name_form':
                             'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
                         },
                     },
                 },
             },
             'cert_file':
             root_path + '/sso_public.crt',
         }]
     }
     with self.app.test_request_context('/', method='GET'):
         sp = auth.Saml(tmp_sp_config)
         self.assertEqual(sp._config.idps(),
                          {entity_id: 'https://sso.example.com/idp/sso'})
         self.assertEqual(
             sp._config.single_logout_services(entity_id,
                                               BINDING_HTTP_REDIRECT),
             ['https://sso.example.com/idp/slo'])
         self.assertEqual(sp._config.single_sign_on_services(entity_id),
                          ['https://sso.example.com/idp/sso'])
コード例 #12
0
 def test_Saml_handle_assertion_invalid_SAMLResponse(self):
     ava = {'uid': '123456'}
     session_id = 'a0123456789abcdef0123456789abcdef'
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # test missing SAMLResponse
     with self.app.test_request_context('/', method='POST'):
         sp = auth.Saml(tmp_sp_config)
         try:
             sp.handle_assertion(request)
             self.fail(
                 'Expected BadRequest on missing SAMLResponse POST var')
         except BadRequest, e:
             self.assertEqual('400: SAMLResponse missing from POST', str(e))
コード例 #13
0
 def test_Saml_handle_logout_invalid_SAMLRequest(self):
     # modifying config in this test, make copy so as not to effect
     # following tests.
     tmp_sp_config = copy.deepcopy(sp_config)
     # test invalid SAMLRequest
     with self.app.test_request_context('/',
                                        method='GET',
                                        query_string=dict(
                                            SAMLRequest='invalid',
                                            RelayState='/next')):
         sp = auth.Saml(tmp_sp_config)
         try:
             sp.handle_logout(request)
             self.fail('Expected BadRequest on invalid SAMLRequest GET arg')
         except BadRequest, e:
             self.assertEqual('400: SAML request is invalid', str(e))
コード例 #14
0
 def test_Saml_authenticate_invalid_config(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 w/o private key file
     with self.app.test_request_context('/', method='GET'):
         tmp_sp_config['key_file'] = None
         sp = auth.Saml(tmp_sp_config)
         try:
             sp.authenticate(next_url='/next')
             self.fail(
                 'Expected AuthException on invalid Saml authentication')
         except auth.AuthException, e:
             self.assertEqual(
                 'Signature requested for this Saml authentication request,'
                 ' but no private key file configured', str(e))
         # outstanding queury cache should still be empty
         self.assertEqual(session.get('_saml_outstanding_queries', {}), {})
コード例 #15
0
 def test_Saml_authenticate_via_post(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'):
         tmp_sp_config['metadata'] = {
             'local': [root_path + '/idp_post_metadata.xml']
         }
         sp = auth.Saml(tmp_sp_config)
         # TODO: when the next release of pysaml2 is released, we need to
         # change these tests to reflect a successful POST.
         try:
             resp = sp.authenticate(next_url='/next',
                                    binding=BINDING_HTTP_POST)
             self.fail('Expected exception from pysaml2 due to missing'
                       ' support')
         except:
             pass
コード例 #16
0
class TestSaml(unittest.TestCase):
    def setUp(self):
        super(TestSaml, self).setUp()
        self.app = Flask(__name__)
        self.app.secret_key = 'Super secret key. Shhhh!'
        self.client = self.app.test_client()

    def test_AuthDictCache(self):
        with self.app.test_request_context('/', method='GET'):
            # assert empty session
            self.assertEqual(session, {})
            # create cache pointing at undefined key in empty session
            cache = auth.AuthDictCache(session, '_test')
            # assert empty
            self.assertEqual(cache, {})
            self.assertEqual(cache.session_data, {})
            # verify that cache works like a normal dict
            cache['key_1'] = 'value_1'
            self.assertEqual(cache, {'key_1': 'value_1'})
            cache['key_2'] = 'value_2'
            cache.update({'key_1': 'value_4', 'key_3': 'value_3'})
            self.assertEqual(cache, {
                'key_1': 'value_4',
                'key_2': 'value_2',
                'key_3': 'value_3'
            })
            # verify that data has not been sync'ed to the session
            self.assertEqual(session, {})
            self.assertEqual(cache.session_data, {})
            # verify that data has been sync'ed to the session
            cache.sync()
            self.assertEqual(cache.session_data, {
                'key_1': 'value_4',
                'key_2': 'value_2',
                'key_3': 'value_3'
            })
            self.assertEqual(session.get('_test'), {
                'key_1': 'value_4',
                'key_2': 'value_2',
                'key_3': 'value_3'
            })
            # if we create a cache pointing to existing session data, it
            # should be populated
            cache = auth.AuthDictCache(session, '_test')
            self.assertEqual(cache, {
                'key_1': 'value_4',
                'key_2': 'value_2',
                'key_3': 'value_3'
            })

    def test_IdentityCache(self):
        with self.app.test_request_context('/', method='GET'):
            # assert empty session
            self.assertEqual(session, {})
            # create cache pointing at undefined key in empty session
            cache = auth.IdentityCache(session, '_test')
            self.assertEqual(session, {})
            self.assertEqual(cache._db, {})
            self.assertTrue(cache._sync)
            # Add something to the cache _db
            cache.set('subject_id', 'entity_id', 'info')
            # verify that data has  been sync'ed to the session automagically
            self.assertEqual(cache._db,
                             {'subject_id': {
                                 'entity_id': (0, 'info')
                             }})
            self.assertEqual(session.get('_test'),
                             {'subject_id': {
                                 'entity_id': (0, 'info')
                             }})
            # if we create a cache pointing to existing session data, it
            # should be populated
            cache = auth.IdentityCache(session, '_test')
            self.assertEqual(cache._db,
                             {'subject_id': {
                                 'entity_id': (0, 'info')
                             }})

    def test_Saml_init(self):
        entity_id = 'https://sso.example.com/idp/metadata'
        with self.app.test_request_context('/', method='GET'):
            try:
                sp = auth.Saml({'service': {'sp': 'invalid'}})
                self.fail(
                    'Expected TypeError on invalid submission to Saml __init__'
                )
            except TypeError:
                pass
            sp = auth.Saml(sp_config)
            self.assertEqual(sp._config.idps(),
                             {entity_id: 'https://sso.example.com/idp/sso'})
            self.assertEqual(
                sp._config.single_logout_services(entity_id,
                                                  BINDING_HTTP_REDIRECT),
                ['https://sso.example.com/idp/slo'])
            self.assertEqual(sp._config.single_sign_on_services(entity_id),
                             ['https://sso.example.com/idp/sso'])

    def test_Saml_init_idp_as_config(self):
        tmp_sp_config = copy.deepcopy(sp_config)
        entity_id = 'https://sso.example.com/idp/metadata'
        tmp_sp_config['metadata'] = {
            'config': [{
                'entityid':
                entity_id,
                'contact_person': [{
                    'email_address': '*****@*****.**',
                    'type': 'technical',
                }],
                'service': {
                    'idp': {
                        'name': 'Test Identity Provider',
                        'endpoints': {
                            'single_sign_on_service':
                            [('https://sso.example.com/idp/sso',
                              BINDING_HTTP_REDIRECT)],
                            'single_logout_service':
                            [('https://sso.example.com/idp/slo',
                              BINDING_HTTP_REDIRECT)],
                        },
                        'policy': {
                            'default': {
                                'lifetime': {
                                    'hours': 24
                                },
                                'attribute_restrictions':
                                None,
                                'name_form':
                                'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
                            },
                        },
                    },
                },
                'cert_file':
                root_path + '/sso_public.crt',
            }]
        }
        with self.app.test_request_context('/', method='GET'):
            sp = auth.Saml(tmp_sp_config)
            self.assertEqual(sp._config.idps(),
                             {entity_id: 'https://sso.example.com/idp/sso'})
            self.assertEqual(
                sp._config.single_logout_services(entity_id,
                                                  BINDING_HTTP_REDIRECT),
                ['https://sso.example.com/idp/slo'])
            self.assertEqual(sp._config.single_sign_on_services(entity_id),
                             ['https://sso.example.com/idp/sso'])

    def test_Saml_init_IdP(self):
        entity_id = 'https://foo.example.com/sp/metadata'
        with self.app.test_request_context('/', method='GET'):
            idp = auth.SamlServer(idp_config)
            self.assertEqual(
                idp._config.single_logout_services(entity_id,
                                                   BINDING_HTTP_REDIRECT),
                ['https://foo.example.com/sp/slo'])
            # Since all of the methods which are unique to IdP config are
            # broken, we'll check to see if one of them is there to confirm
            # that we loaded the IdP config rather than the SP config.
            self.assertIsNotNone(
                getattr(idp._config, 'assertion_consumer_services'))
            # pysaml2 config assertion_consumer_services method is broken
            # - https://github.com/rohe/pysaml2/issues/7
            # pysaml2 config authz_services method is broken
            # - https://github.com/rohe/pysaml2/issues/8

    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)

    def test_Saml_authenticate_via_post(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'):
            tmp_sp_config['metadata'] = {
                'local': [root_path + '/idp_post_metadata.xml']
            }
            sp = auth.Saml(tmp_sp_config)
            # TODO: when the next release of pysaml2 is released, we need to
            # change these tests to reflect a successful POST.
            try:
                resp = sp.authenticate(next_url='/next',
                                       binding=BINDING_HTTP_POST)
                self.fail('Expected exception from pysaml2 due to missing'
                          ' support')
            except:
                pass
#            resp = sp.authenticate(next_url='/next',
#                binding=BINDING_HTTP_POST)
#            self.assertEqual(resp.status_code, 200)
#            self.assert_('SAMLRequest' in resp.data)
#            self.assert_('RelayState' in resp.data)
#            authn_id = session['_saml_outstanding_queries'].keys()[0]
#            self.assertEqual(session['_saml_outstanding_queries'],
#                {authn_id: '/next'})

    def test_Saml_authenticate_no_idp(self):
        # modifying config in this test, make copy so as not to effect
        # following tests.
        tmp_sp_config = copy.deepcopy(sp_config)
        # test using binding method not configured for in IdP metedata
        with self.app.test_request_context('/', method='GET'):
            sp = auth.Saml(tmp_sp_config)
            try:
                sp.authenticate(next_url='/next', binding=BINDING_HTTP_POST)
                self.fail(
                    'Expected AuthException on invalid Saml authentication')
            except auth.AuthException, e:
                self.assertEqual('Unable to locate valid IdP for this request',
                                 str(e))
        # test with only allowed IdP not included in metedata file
        with self.app.test_request_context('/', method='GET'):
            tmp_sp_config['service']['sp']['idp'] = {'invalid': None}
            sp = auth.Saml(tmp_sp_config)
            try:
                sp.authenticate(next_url='/next')
                self.fail(
                    'Expected AuthException on invalid Saml authentication')
            except auth.AuthException, e:
                self.assertEqual('Unable to locate valid IdP for this request',
                                 str(e))
            # outstanding queury cache should still be empty
            self.assertEqual(session.get('_saml_outstanding_queries', {}), {})
コード例 #17
0
 def test_Saml_handle_logout_response(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)
     session_id, logout_response = create_logout_response(
         'id-1',
         destination='https://sso.example.com/idp/slo',
         issuer_entity_id='https://sso.example.com/idp/metadata',
         req_entity_id='https://foo.example.com/sp/metadata')
     self.assert_('Signature' in logout_response)
     # test SAMLResponse logout as GET
     with self.app.test_request_context(
             '/',
             method='GET',
             query_string=dict(
                 SAMLResponse=deflate_and_base64_encode(logout_response),
                 RelayState='/next')):
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         session['_saml_state'] = {session_id: state}
         success, resp = sp.handle_logout(request, next_url='/next')
         self.assertTrue(success)
         self.assertEqual(resp.status_code, 302)
         self.assertEqual(resp.headers['Location'], '/next')
     # test SAMLResponse logout as POST
     with self.app.test_request_context(
             '/',
             method='POST',
             data=dict(SAMLResponse=base64.b64encode(logout_response),
                       RelayState='/next')):
         endpoints = tmp_sp_config['service']['sp']['endpoints']
         slo = endpoints['single_logout_service'][0][0]
         endpoints['single_logout_service'] = [(slo, BINDING_HTTP_POST)]
         # first need to be logged in, let's pretend
         session['_saml_identity'] = identity
         session['_saml_subject_id'] = 'id-1'
         session['_saml_state'] = {session_id: state}
         success, resp = sp.handle_logout(request, next_url='/next')
         self.assertTrue(success)
         self.assertEqual(resp.status_code, 302)
         self.assertEqual(resp.headers['Location'], '/next')
コード例 #18
0
 def test_Saml_handle_assertion(self):
     ava = {'uid': '123456'}
     session_id = 'a0123456789abcdef0123456789abcdef'
     # 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
     name_id, authn_response = create_authn_response(session_id, ava)
     self.assert_('Signature' in authn_response)
     # test fails if there is no known outstanding auth request
     with self.app.test_request_context(
             '/',
             method='POST',
             data=dict(SAMLResponse=base64.b64encode(authn_response),
                       RelayState='/next')):
         sp = auth.Saml(tmp_sp_config)
         try:
             sp.handle_assertion(request)
             self.fail(
                 'Expected Exception due to lack of outstanding request')
         except:
             pass
     # test with default user_id mapping
     with self.app.test_request_context(
             '/',
             method='POST',
             data=dict(SAMLResponse=base64.b64encode(authn_response),
                       RelayState='/next')):
         # make the client thing there is outstanding request
         session['_saml_outstanding_queries'] = {session_id: '/next'}
         user_id, user_attributes, resp = sp.handle_assertion(request)
         self.assertEqual(user_id, name_id)
         self.assertEqual(resp.status_code, 302)
         self.assertEqual(resp.headers['Location'], '/next')
         # outstanding queury cache should now be empty
         self.assertEqual(session.get('_saml_outstanding_queries', {}), {})
         # identity and subject_id should now be set
         self.assert_(name_id in session.get('_saml_identity'))
         self.assertEqual(session.get('_saml_subject_id'), name_id)
     # test user_id mapped to 'uid' attribute
     with self.app.test_request_context(
             '/',
             method='POST',
             data=dict(SAMLResponse=base64.b64encode(authn_response),
                       RelayState='/next')):
         sp.attribute_map = dict(uid='uid')
         session['_saml_outstanding_queries'] = {session_id: '/next'}
         user_id, user_attributes, resp = sp.handle_assertion(request)
         self.assertEqual(user_id, '123456')
         # outstanding queury cache should now be empty
         self.assertEqual(session.get('_saml_outstanding_queries', {}), {})
         # identity and subject_id should now be set
         self.assert_(name_id in session.get('_saml_identity'))
         self.assertEqual(session.get('_saml_subject_id'), name_id)
     # test user_id mapped to missing attribute
     with self.app.test_request_context(
             '/',
             method='POST',
             data=dict(SAMLResponse=base64.b64encode(authn_response),
                       RelayState='/next')):
         sp.attribute_map = dict(uid='invalid')
         session['_saml_outstanding_queries'] = {session_id: '/next'}
         try:
             user_id, user_attributes, resp = \
                 sp.handle_assertion(request)
             self.fail('Expected AuthException for invalid attribute name')
         except auth.AuthException, e:
             self.assertEqual(
                 'Unable to find "invalid" attribute in response', str(e))
         # outstanding queury cache should now be empty
         self.assertEqual(session.get('_saml_outstanding_queries', {}), {})
         # identity is still set by internal Saml client call
         # ~ I feel like maybe this should get cleared if we couldn't
         #   find an exceptable uid.
         self.assert_(name_id in session.get('_saml_identity'))
         # subject_id is not set if unable to parse attribute
         self.assertEqual(session.get('_saml_subject_id'), None)
コード例 #19
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'])
コード例 #20
0
 # test missing SAMLResponse
 with self.app.test_request_context('/', method='POST'):
     sp = auth.Saml(tmp_sp_config)
     try:
         sp.handle_assertion(request)
         self.fail(
             'Expected BadRequest on missing SAMLResponse POST var')
     except BadRequest, e:
         self.assertEqual('400: SAMLResponse missing from POST', str(e))
 # test SAMLResponse via GET
 with self.app.test_request_context('/',
                                    method='GET',
                                    query_string=dict(
                                        SAMLResponse='invalid',
                                        RelayState='/next')):
     sp = auth.Saml(tmp_sp_config)
     try:
         sp.handle_assertion(request)
         self.fail(
             'Expected BadRequest on missing SAMLResponse POST var')
     except BadRequest, e:
         self.assertEqual('400: SAMLResponse missing from POST', str(e))
 # test invalid SAMLResponse
 with self.app.test_request_context('/',
                                    method='POST',
                                    data=dict(SAMLResponse='invalid',
                                              RelayState='/next')):
     sp = auth.Saml(tmp_sp_config)
     try:
         sp.handle_assertion(request)
         self.fail(