def test_31_validate_config_good(self): ''' Test that we get a good run through _validate_config_file ''' with mock.patch.object(IAMVPNLibraryBase, '__init__'), \ mock.patch.object(IAMVPNLibraryLDAP, '_validate_config_file'), \ mock.patch.object(IAMVPNLibraryLDAP, '_create_ldap_connection'): library = IAMVPNLibraryLDAP() library.configfile = self.config result = library._validate_config_file() self.assertIsInstance(result, dict)
def test_32_validate_config_bad(self): ''' Test that we get a bad run through _validate_config_file ''' with mock.patch.object(IAMVPNLibraryBase, '__init__'), \ mock.patch.object(IAMVPNLibraryLDAP, '_validate_config_file'), \ mock.patch.object(IAMVPNLibraryLDAP, '_create_ldap_connection'): library = IAMVPNLibraryLDAP() library.configfile = self.config library.configfile.remove_option('ldap-bind', 'bind_dn') with self.assertRaises(ValueError): library._validate_config_file()
def test_34_init_mock_bad(self): ''' Test that init fails when ldap dies. ''' with mock.patch.object(IAMVPNLibraryBase, '_ingest_config_from_file', return_value=self.config), \ mock.patch.object(IAMVPNLibraryLDAP, '_create_ldap_connection', side_effect=ldap.LDAPError('err')), \ self.assertRaises(RuntimeError): IAMVPNLibraryLDAP()
def test_36_mock_ldap_connect(self): ''' Test setting up an LDAP connection. We'll do it for real in another function ''' with self.assertRaises(ldap.INVALID_CREDENTIALS): IAMVPNLibraryLDAP._create_ldap_connection('someurl', 'someuser', None) with self.assertRaises(ldap.LDAPError): IAMVPNLibraryLDAP._create_ldap_connection('someurl', 'someuser', 'somepass') mock_ldap = mock.Mock() mock_ldap.start_tls_s.return_value = None mock_ldap.simple_bind_s.return_value = None with mock.patch.object(ldap, 'initialize', return_value=mock_ldap): conn = IAMVPNLibraryLDAP._create_ldap_connection( 'someurl', 'someuser', 'somepass') mock_ldap.start_tls_s.assert_called_once_with() mock_ldap.simple_bind_s.assert_called_once_with('someuser', 'somepass') self.assertIsInstance(conn, mock.Mock)
def test_33_init_mock_good(self): ''' Test that init is mocked out. ''' with mock.patch.object(IAMVPNLibraryBase, '_ingest_config_from_file', return_value=self.config), \ mock.patch.object(IAMVPNLibraryLDAP, '_create_ldap_connection', return_value='something2') as mock_ldap: result = IAMVPNLibraryLDAP() mock_ldap.assert_called_once() self.assertEqual(result.conn, 'something2')
def test_30_ldap_init(self): ''' A "does this call everything?" test ''' with mock.patch.object(IAMVPNLibraryBase, '__init__') as mock_base, \ mock.patch.object(IAMVPNLibraryLDAP, '_validate_config_file') as mock_valid, \ mock.patch.object(IAMVPNLibraryLDAP, '_create_ldap_connection') as mock_ldap: IAMVPNLibraryLDAP() mock_base.assert_called_once_with() mock_valid.assert_called_once_with() mock_ldap.assert_called_once() with mock.patch.object(IAMVPNLibraryBase, '__init__') as mock_base, \ mock.patch.object(IAMVPNLibraryLDAP, '_validate_config_file') as mock_valid, \ mock.patch.object(IAMVPNLibraryLDAP, '_create_ldap_connection', side_effect=ldap.LDAPError) as mock_ldap, \ self.assertRaises(RuntimeError): IAMVPNLibraryLDAP() mock_base.assert_called_once_with() mock_valid.assert_called_once_with() mock_ldap.assert_called_once()
def setUp(self): """ Prepare test rig """ try: # This should never fail. But if it does, I have no # idea why it would, so, catch all exceptions deliberately. # Keep in mind that we don't want to detail LDAP-specific # reasons here. "It failed" is enough for testing. self.library = IAMVPNLibraryLDAP() except Exception as err: # pragma: no cover pylint: disable=broad-except self.fail(err) self.library.conn.unbind_s()
def setUp(self): """ Preparing test rig """ self.library = IAMVPNLibraryLDAP() self.normal_user = self.library.read_item_from_config( section='testing', key='normal_user', default=None)
class TestLDAPFunctions(unittest.TestCase): """ These are intended to exercise primarily internal functions of LDAP. We call the public-facing functions in the test suite, also, which essentially means redundant testing, but in case we have a parallel suite to LDAP we want to make sure we don't break this when touching some other area. """ def setUp(self): """ Preparing test rig """ self.library = IAMVPNLibraryLDAP() self.normal_user = self.library.read_item_from_config( section='testing', key='normal_user', default=None) def tearDown(self): """ Clear the test rig """ self.library = None def test_00_is_online(self): """ exercise is_online function """ with mock.patch.object(self.library.conn, 'result', return_value=None): self.assertTrue(self.library.is_online()) with mock.patch.object(self.library.conn, 'result', side_effect=ldap.LDAPError): self.assertFalse(self.library.is_online()) def test_get_all_enabled_users(self): """ Testing that we get back a reasonably sized set of user DNs """ # First, simulation tests: with mock.patch.object(self.library.conn, 'search_s', return_value=[('mail=dn3', {}), ('mail=dn9', {})]): result = self.library._get_all_enabled_users() self.assertEqual(result, set(['mail=dn3', 'mail=dn9'])) # Now, test it live: result = self.library._get_all_enabled_users() self.assertIsInstance(result, set, 'Must return a set') # CAUTION! this verifies that it has 100 at test time. # Keep in mind what will happen if this happens at RUN time. self.assertGreater(len(result), 100, 'We expect a sizeable list of enabled users') self.assertIn(','+self.library.config['ldap_base'], result.pop(), ('A random user from the set does not match ' 'the ldap base of the config. Bad search?')) def test_get_acl_allowed_users(self): """ Testing that we get back a reasonably sized set of user DNs """ # First, simulation tests: with mock.patch.object(self.library.conn, 'search_s', return_value=[('cn=vpn_allowed_folks', {'member': ['a', 'b']})]): result = self.library._get_acl_allowed_users() self.assertEqual(result, set(['a', 'b'])) # Now, test it live: result = self.library._get_acl_allowed_users() self.assertIsInstance(result, set, 'Must return a set') # CAUTION! this verifies that it has 100 at test time. # Keep in mind what will happen if this happens at RUN time. self.assertGreater(len(result), 100, 'We expect a sizeable list of allowed users') self.assertIn(','+self.library.config['ldap_base'], result.pop(), ('A random user from the set does not match ' 'the ldap base of the config. Bad search?')) def test_all_vpn_allowed_users(self): """ Testing that we get back a reasonably sized set of user DNs """ # First, simulation tests: with mock.patch.object(self.library, '_get_all_enabled_users', return_value=set(['a', 'b', 'C'])), \ mock.patch.object(self.library, '_get_acl_allowed_users', return_value=set(['B', 'c', 'd'])): result = self.library._all_vpn_allowed_users() self.assertEqual(result, set(['b', 'c'])) # Now, test it live: result = self.library._all_vpn_allowed_users() self.assertIsInstance(result, set, 'Must return a set') # CAUTION! this verifies that it has 100 at test time. # Keep in mind what will happen if this happens at RUN time. self.assertGreater(len(result), 100, 'We expect a sizeable list of allowed users') self.assertIn(','+self.library.config['ldap_base'], result.pop(), ('A random user from the set does not match ' 'the ldap base of the config. Bad search?')) def test_fetch_vpn_acls_for_user(self): """ Testing that we get back raw/ldap'ed acls for our test user. """ with self.assertRaises(TypeError): self.library._fetch_vpn_acls_for_user([]) # First, simulation tests: # This is going to just be an assert-how-you're-called mock # because the basis of this function is "return a thing from ldap" with mock.patch.object(self.library.conn, 'search_s') as mock_ldap, \ mock.patch.object(self.library, '_get_user_dn_by_username', return_value='ddn'), \ mock.patch.dict(self.library.config, {'ldap_groups_base': 'ou=groupz,dc=myplace', 'ldap_vpn_acls_all_acls_filter': '(&(objectClass=GroupOfNames)(%(rdn_attribute)s=vpn_*))', 'ldap_vpn_acls_attribute_user': '******', 'ldap_vpn_acls_rdn_attribute': 'cn', 'ldap_vpn_acls_attribute_host': 'hostattr'}): self.library._fetch_vpn_acls_for_user('dude') expect_filter = '(&(&(objectClass=GroupOfNames)(%(rdn_attribute)s=vpn_*))(uzer=ddn))' mock_ldap.assert_called_with('ou=groupz,dc=myplace', ldap.SCOPE_SUBTREE, filterstr=expect_filter, attrlist=['cn', 'hostattr']) # Now, test it live: if self.normal_user is None: # pragma: no cover self.skipTest('Must provide a .normal_user to test') result = self.library._fetch_vpn_acls_for_user(self.normal_user) self.assertIsInstance(result, list, 'Must return a list') self.assertGreater(len(result), 5, 'If this failed, someone has very few acls.') # Having now verified we have a list, grab one: acl = result[0] # This should be an LDAP ACL, and that has a format, This is an # exhaustive, painful check of that. You'll either bomb early # because your code returned the wrong thing, or you'll wonder how # the LDAP format changed. Most of this you don't need to stare at. self.assertIsInstance(acl, tuple, 'Did not get an LDAP ACL tuple') self.assertIsInstance(acl[0], six.string_types, ('The supposed LDAP ACL tuple did not have ' 'a DN string as arg 0')) self.assertIsInstance(acl[1], dict, ('The supposed LDAP ACL tuple did not have ' 'an attr dict as arg 1')) self.assertIn(self.library.config['ldap_vpn_acls_rdn_attribute'], acl[1], 'The attr dict of the LDAP acl did not contain the RDN') _rdn = acl[1][self.library.config['ldap_vpn_acls_rdn_attribute']] self.assertIsInstance(_rdn, list, ('The RDN in the attr dict of the ' 'LDAP acl was not a list')) self.assertGreater(len(_rdn), 0, ('The RDN in the attr dict of the ' 'LDAP acl was empty')) self.assertIn(self.library.config['ldap_vpn_acls_attribute_host'], acl[1], 'The attr dict of the LDAP acl did not contain ACLs') _acl = acl[1][self.library.config['ldap_vpn_acls_attribute_host']] self.assertIsInstance(_acl, list, ('The ACLs in the attr dict of the ' 'LDAP acl was not a list')) self.assertGreater(len(_acl), 0, ('The ACLs in the attr dict of the ' 'LDAP acl was empty')) def test_sanitized_vpn_acls(self): """ Testing that we get back acls that we've flattened into being a list of ParsedACLs """ with self.assertRaises(TypeError): self.library._sanitized_vpn_acls_for_user([]) # First, simulation tests: with mock.patch.dict(self.library.config, {'ldap_vpn_acls_rdn_attribute': 'cn', 'ldap_vpn_acls_attribute_host': 'hostattr'}): # User with no ACLs: with mock.patch.object(self.library, '_fetch_vpn_acls_for_user', return_value=[]): result = self.library._sanitized_vpn_acls_for_user('anyone') self.assertEqual(result, []) # User with empty ACLs: with mock.patch.object(self.library, '_fetch_vpn_acls_for_user', return_value=[('_cn', {})]): result = self.library._sanitized_vpn_acls_for_user('anyone') self.assertEqual(result, []) # User with a normal ACL: with mock.patch.object(self.library, '_fetch_vpn_acls_for_user', return_value=[('_cn', {'cn': ['vpn_something'], 'hostattr': ['1.1.1.1 # foo.m.c']})]): result = self.library._sanitized_vpn_acls_for_user('anyone') self.assertEqual(result, [ParsedACL(rule='vpn_something', address=IPNetwork('1.1.1.1/32'), portstring='', description='foo.m.c')]) # User with a bogus ACL: with mock.patch.object(self.library, '_fetch_vpn_acls_for_user', return_value=[('_cn', {'cn': ['vpn_something'], 'hostattr': ['999.999.999.999']})]): result = self.library._sanitized_vpn_acls_for_user('anyone') self.assertEqual(result, []) # User with a hostname ACL: with mock.patch.object(self.library, '_fetch_vpn_acls_for_user', return_value=[('_cn', {'cn': ['vpn_lh'], 'hostattr': ['localhost # lokal']})]): result = self.library._sanitized_vpn_acls_for_user('anyone') self.assertEqual(result, [ParsedACL(rule='vpn_lh', address=IPNetwork('127.0.0.1/32'), portstring='', description='lokal')]) # User with a null hostname ACL somehow: with mock.patch.object(self.library, '_fetch_vpn_acls_for_user', return_value=[('_cn', {'cn': ['vpn_badstr'], 'hostattr': ['']})]): result = self.library._sanitized_vpn_acls_for_user('anyone') self.assertEqual(result, []) # Now, test it live: if self.normal_user is None: # pragma: no cover self.skipTest('Must provide a .normal_user to test') result = self.library._sanitized_vpn_acls_for_user(self.normal_user) self.assertIsInstance(result, list, 'Did not return a list') self.assertGreater(len(result), 5, 'If this failed, someone has very few acls.') pacl = result[0] self.assertIsInstance(pacl, ParsedACL, 'Did not return a list of ParsedACLs') # rule can be empty self.assertIsInstance(pacl.rule, six.string_types, 'The ParsedACL rule was not a string') # address is an object and must be there self.assertIsInstance(pacl.address, IPNetwork, 'The ParsedACL address was not an IPNetwork') self.assertGreaterEqual(pacl.address.size, 1, 'The ParsedACL address did not have a size?') # portstring can be empty self.assertIsInstance(pacl.portstring, six.string_types, 'The ParsedACL portstring was not a string') # description can be empty self.assertIsInstance(pacl.description, six.string_types, 'The ParsedACL description was not a string') def test_vpn_mfa_exempt_users(self): """ Testing that we get the set of user DNs who are exempt from having to MFA """ # First, simulation tests: with mock.patch.object(self.library.conn, 'search_s', return_value=[('cn=vpn_allowed_folks', {'member': ['r', 'w']})]): result = self.library._vpn_mfa_exempt_users() self.assertEqual(result, set(['r', 'w'])) # Now, test it live: result = self.library._vpn_mfa_exempt_users() self.assertIsInstance(result, set, 'Must return a set') self.assertGreater( len(result), 0, ('If this failed, check the group size or your bind user perms. ' "It should be small-ish, but if it's zero that's weird.")) self.assertLess( len(result), 10, ('If this failed, check the group size. ' 'It should be small-ish, but this test may be too small.')) self.assertIn(','+self.library.config['ldap_base'], result.pop(), ('A random user from the set does not match ' 'the ldap base of the config. Bad search?')) def test_get_user_dn_by_username(self): """ Testing that can turn an email address into a user's DN """ with self.assertRaises(TypeError): self.library._get_user_dn_by_username([]) # First, simulation tests: with self.assertRaises(ldap.NO_SUCH_OBJECT), \ mock.patch.object(self.library.conn, 'search_s', return_value=[]): self.library._get_user_dn_by_username('somename') with self.assertRaises(ldap.LDAPError), \ mock.patch.object(self.library.conn, 'search_s', return_value=[('dnX', {}), ('dnY', {})]): self.library._get_user_dn_by_username('somename') with mock.patch.object(self.library.conn, 'search_s', return_value=[('dn1', {})]): result = self.library._get_user_dn_by_username('somename') self.assertEqual(result, 'dn1') # Now, test it live: if self.normal_user is None: # pragma: no cover self.skipTest('Must provide a .normal_user to test') result = self.library._get_user_dn_by_username(self.normal_user) self.assertIsInstance(result, six.string_types, 'search for username must return a DN string') self.assertIn(','+self.library.config['ldap_base'], result, ('A random user from the set does not match ' 'the ldap base of the config. Bad search?'))
def test_21_acl_parsing(self): """ This tests for various cases of ACL strings that we get from the ldap server, and verifies that they break into chunks that we expect. """ with self.assertRaises(TypeError): IAMVPNLibraryLDAP._split_vpn_acl_string([]) self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('1.1.1.1'), ParsedACL(rule='', address=IPNetwork('1.1.1.1/32'), portstring='', description=''), 'Simple IPv4 host parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('1.1.1.1 # a test'), ParsedACL(rule='', address=IPNetwork('1.1.1.1/32'), portstring='', description='a test'), 'Simple commented IPv4 host parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('1.1.1.1/30'), ParsedACL(rule='', address=IPNetwork('1.1.1.1/30'), portstring='', description=''), 'Simple IPv4 CIDR parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('1.1.1.1:443'), ParsedACL(rule='', address=IPNetwork('1.1.1.1/32'), portstring='443', description=''), 'IPv4 host:port parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('dead::beef'), ParsedACL(rule='', address=IPNetwork('dead::beef/128'), portstring='', description=''), 'Simple abbreviated IPv6 host parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string( 'fdf2:c3cc:8c71:c263:dead:beef:dead:beef'), ParsedACL(rule='', address=IPNetwork( 'fdf2:c3cc:8c71:c263:dead:beef:dead:beef/128'), portstring='', description=''), 'Simple nonabbreviated IPv6 host parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('dead::beef/64'), ParsedACL(rule='', address=IPNetwork('dead::beef/64'), portstring='', description=''), 'Simple IPv6 CIDR parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('[dead::beef]:443'), ParsedACL(rule='', address=IPNetwork('dead::beef/128'), portstring='443', description=''), 'IPv6 host:port parsing failed') self.assertEqual( IAMVPNLibraryLDAP._split_vpn_acl_string('hostname.domain.org'), ParsedACL(rule='', address='hostname.domain.org', portstring='', description='hostname.domain.org'), 'hostname parsing failed') with self.assertRaises(netaddr.core.AddrFormatError): # Bogus IPv4 address:port must be fatal IAMVPNLibraryLDAP._split_vpn_acl_string('1.1.1.1111:443') with self.assertRaises(netaddr.core.AddrFormatError): # Bogus IPv4 address must be fatal IAMVPNLibraryLDAP._split_vpn_acl_string('1.1.1.1111')