def test_determine_desired_defaults(mockdbcontext): """ Make sure that desired default privileges include: (all object owners in the schema + schema owner) crossed with (all privileges associated with this object and access type) """ # Using sequence-write because it has 2 types of privileges (i.e. >1 but not a ton) object_kind = 'sequences' access = 'write' schema_writers = {ObjectName(SCHEMAS[0]): set(ROLES[1:])} privconf = privs.PrivilegeAnalyzer(rolename=ROLES[0], access=access, object_kind=object_kind, desired_items=DUMMY, schema_writers=schema_writers, personal_schemas=DUMMY, dbcontext=mockdbcontext) schemas = [ObjectName(SCHEMAS[0])] roles = ROLES[1:] possible_privs = privs.PRIVILEGE_MAP[object_kind][access] expected = set(itertools.product(roles, schemas, possible_privs)) privconf.determine_desired_defaults(schemas) actual = privconf.desired_defaults assert actual == expected
def test_get_object_owner(mockdbcontext, object_kind, item, expected): mockdbcontext.get_all_object_owners = lambda: { 'schemas': { SCHEMAS[0]: { SCHEMAS[0]: ROLES[0] }, }, 'sequences': { SCHEMAS[0]: { quoted_object(SCHEMAS[0], SEQUENCES[0]): ROLES[1] }, }, 'tables': { SCHEMAS[0]: { quoted_object(SCHEMAS[0], TABLES[1]): ROLES[2] }, }, } privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=DUMMY, object_kind=object_kind, desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) actual = privconf.get_object_owner(item) assert actual == expected
def test_identify_desired_objects_personal_schemas_object_kind_is_schema( mockdbcontext): """ Make sure that if we desire 'personal_schemas' and the object_kind is a schema that the personal schemas do show up """ mockdbcontext.get_all_object_owners = lambda: { 'schemas': { SCHEMAS[0]: { SCHEMAS[0]: ROLES[0] }, SCHEMAS[1]: { SCHEMAS[1]: ROLES[0] }, }, } personal_schemas = ROLES[1:3] access = 'read' object_kind = 'schemas' desired_items = list(SCHEMAS[:2]) + ['personal_schemas'] privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=access, object_kind=object_kind, desired_items=desired_items, schema_writers=DUMMY, personal_schemas=personal_schemas, dbcontext=mockdbcontext) privconf.identify_desired_objects() expected_schemas = set(SCHEMAS[:2] + personal_schemas) possible_privs = privs.PRIVILEGE_MAP[object_kind][access] expected = set(itertools.product(expected_schemas, possible_privs)) actual = set(privconf.desired_nondefaults) assert actual == expected
def test_get_schema_objects_sequences(mockdbcontext): """ While this test is almost identical to test_get_schema_objects_tables(), it's here because we want to ensure we have coverage over more than just tables """ objattributes = {'owner': ROLES[0], 'is_dependent': False} all_attributes = { ObjectName(SCHEMAS[0], seq): objattributes for seq in SEQUENCES } mockdbcontext.get_all_object_attributes = lambda: { 'sequences': { SCHEMAS[0]: all_attributes } } privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=DUMMY, object_kind='sequences', desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) # Assert that we get back the sequences that are in the context object actual = privconf.get_schema_objects(SCHEMAS[0]) expected = set([ObjectName(SCHEMAS[0], seq) for seq in SEQUENCES]) assert actual == expected
def test_init_default_acl_possible(object_kind, mockdbcontext): privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=DUMMY, object_kind=object_kind, desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) expected = object_kind in privs.OBJECTS_WITH_DEFAULTS assert privconf.default_acl_possible is expected
def test_analyze_defaults(mockdbcontext): mockdbcontext.get_role_current_defaults = lambda x, y, z: set([ (ROLES[3], ObjectName(SCHEMAS[0]), 'SELECT'), ]) mockdbcontext.get_role_current_nondefaults = lambda x, y, z: set() mockdbcontext.get_all_object_attributes = lambda: { 'tables': { SCHEMAS[0]: { ObjectName(SCHEMAS[0], TABLES[0]): { 'owner': ROLES[2], 'is_dependent': False }, }, }, 'schemas': { SCHEMAS[0]: { ObjectName(SCHEMAS[0]): { 'owner': ROLES[1], 'is_dependent': False }, }, }, } desired_items = [ObjectName(SCHEMAS[0], '*')] schema_writers = { ObjectName(SCHEMAS[0]): set([ROLES[1], ROLES[2]]), } privconf = privs.PrivilegeAnalyzer(rolename=ROLES[0], access='read', object_kind='tables', desired_items=desired_items, schema_writers=schema_writers, personal_schemas=DUMMY, dbcontext=mockdbcontext, excepted_items=[]) # Run analyze_defaults(); note that we have to do identify_desired_objects() # first to set things up privconf.identify_desired_objects() privconf.analyze_defaults() expected = [ privs.Q_REVOKE_DEFAULT.format(ROLES[3], SCHEMAS[0], 'SELECT', 'TABLES', ROLES[0]), privs.Q_GRANT_DEFAULT.format(ROLES[1], SCHEMAS[0], 'SELECT', 'TABLES', ROLES[0]), privs.Q_GRANT_DEFAULT.format(ROLES[2], SCHEMAS[0], 'SELECT', 'TABLES', ROLES[0]), ] assert set(expected) == set(privconf.sql_to_run)
def test_identify_desired_objects_personal_schemas_object_kind_is_schema( mockdbcontext): """ Make sure that if we desire 'personal_schemas' and the object_kind is a schema that the personal schemas do show up """ mockdbcontext.get_all_object_attributes = lambda: { 'schemas': { SCHEMAS[0]: { ObjectName(SCHEMAS[0]): { 'owner': ROLES[0] }, 'is_dependent': False }, SCHEMAS[1]: { ObjectName(SCHEMAS[1]): { 'owner': ROLES[0] }, 'is_dependent': False }, }, } personal_schemas = set([ ObjectName(ROLES[1]), ObjectName(ROLES[2]), ObjectName(ROLES[3]), ]) access = 'read' object_kind = 'schemas' desired_items = [ ObjectName(SCHEMAS[0]), ObjectName(SCHEMAS[1]), ObjectName('personal_schemas') ] privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=access, object_kind=object_kind, desired_items=desired_items, schema_writers=DUMMY, personal_schemas=personal_schemas, dbcontext=mockdbcontext, excepted_items=[]) privconf.identify_desired_objects() nonpersonal_expected_schemas = set( [ObjectName(SCHEMAS[0]), ObjectName(SCHEMAS[1])]) expected_schemas = nonpersonal_expected_schemas.union(personal_schemas) possible_privs = privs.PRIVILEGE_MAP[object_kind][access] expected = set(itertools.product(expected_schemas, possible_privs)) actual = set(privconf.desired_nondefaults) assert actual == expected
def test_get_schema_owner(mockdbcontext): mockdbcontext.get_all_object_owners = lambda: { 'schemas': { SCHEMAS[0]: { SCHEMAS[0]: ROLES[1] }, }, } privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=DUMMY, object_kind=DUMMY, desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) assert privconf.get_schema_owner(SCHEMAS[0]) == ROLES[1]
def test_get_object_owner_nonexistent_object(capsys, mockdbcontext): object_kind = 'tables' objname = ObjectName('foo', 'bar') mockdbcontext.get_all_object_attributes = lambda: {} privconf = privs.PrivilegeAnalyzer(rolename=ROLES[0], access=DUMMY, object_kind=object_kind, desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) with pytest.raises(SystemExit): privconf.get_object_owner(objname) out, _ = capsys.readouterr() assert out == privs.OBJECT_DOES_NOT_EXIST_ERROR_MSG.format( 'table', objname.qualified_name, ROLES[0]) + '\n'
def test_revoke_nondefault(mockdbcontext): table = quoted_object(SCHEMAS[0], TABLES[0]) rolename = ROLES[0] # Revoke the privilege privconf = privs.PrivilegeAnalyzer(rolename=rolename, access=DUMMY, object_kind='tables', desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) privconf.revoke_nondefault(table, 'SELECT') expected = [ privs.Q_REVOKE_NONDEFAULT.format('SELECT', 'TABLE', table, rolename) ] assert privconf.sql_to_run == expected
def test_identify_desired_objects_personal_schemas_error_expected( capsys, mockdbcontext): """ Verify that an error is raised if 'personal_schemas' is requested and the object_kind is not 'schema' """ access = 'read' object_kind = 'tables' privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=access, object_kind=object_kind, desired_items=['personal_schemas'], schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) with pytest.raises(SystemExit): privconf.identify_desired_objects() expected_err_msg = privs.PERSONAL_SCHEMAS_ERROR_MSG.format( DUMMY, object_kind, access) + '\n' assert capsys.readouterr()[0] == expected_err_msg
def test_grant_nondefault(mockdbcontext): table = ObjectName(SCHEMAS[0], TABLES[0]) rolename = ROLES[0] # Grant the privilege privconf = privs.PrivilegeAnalyzer(rolename=rolename, access=DUMMY, object_kind='tables', desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) privconf.grant_nondefault(table, 'SELECT') expected = [ privs.Q_GRANT_NONDEFAULT.format('SELECT', 'TABLE', table.qualified_name, rolename) ] assert privconf.sql_to_run == expected
def test_get_schema_objects_tables(mockdbcontext): ownership = {quoted_object(SCHEMAS[0], t): ROLES[0] for t in TABLES} mockdbcontext.get_all_object_owners = lambda: { 'tables': { SCHEMAS[0]: ownership } } privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=DUMMY, object_kind='tables', desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) # Assert that we get back the tables that are in the context object actual = privconf.get_schema_objects(SCHEMAS[0]) expected = set([quoted_object(SCHEMAS[0], t) for t in TABLES]) assert actual == expected
def test_grant_default(mockdbcontext): rolename = ROLES[0] privconf = privs.PrivilegeAnalyzer(rolename=rolename, access='read', object_kind='tables', desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) # Grant default privileges to role0 from role1 for this schema privconf.grant_default(grantor=ROLES[1], schema=ObjectName(SCHEMAS[0]), privilege='SELECT') expected = [ privs.Q_GRANT_DEFAULT.format(ROLES[1], SCHEMAS[0], 'SELECT', 'TABLES', rolename) ] assert privconf.sql_to_run == expected
def test_revoke_default(mockdbcontext): rolename = ROLES[0] # Revoke default privileges from role0 for this schema granted by role1 privconf = privs.PrivilegeAnalyzer(rolename=rolename, access='read', object_kind='tables', desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) privconf.revoke_default(grantor=ROLES[1], schema=SCHEMAS[0], privilege='SELECT') expected = [ privs.Q_REVOKE_DEFAULT.format(ROLES[1], SCHEMAS[0], 'SELECT', 'TABLES', rolename) ] assert privconf.sql_to_run == expected
def test_get_schema_objects_tables(mockdbcontext): objattributes = {'owner': ROLES[0], 'is_dependent': False} all_attributes = {ObjectName(SCHEMAS[0], t): objattributes for t in TABLES} mockdbcontext.get_all_object_attributes = lambda: { 'tables': { SCHEMAS[0]: all_attributes } } privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=DUMMY, object_kind='tables', desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext) # Assert that we get back the tables that are in the context object actual = privconf.get_schema_objects(SCHEMAS[0]) expected = set([ObjectName(SCHEMAS[0], t) for t in TABLES]) assert actual == expected
def test_get_object_owner(mockdbcontext, object_kind, objname, expected): mockdbcontext.get_all_object_attributes = lambda: { 'schemas': { SCHEMAS[0]: { ObjectName(SCHEMAS[0]): { 'owner': ROLES[0], 'is_dependent': False }, }, }, 'sequences': { SCHEMAS[0]: { ObjectName(SCHEMAS[0], SEQUENCES[0]): { 'owner': ROLES[1], 'is_dependent': False }, }, }, 'tables': { SCHEMAS[0]: { ObjectName(SCHEMAS[0], TABLES[1]): { 'owner': ROLES[2], 'is_dependent': False }, }, }, } privconf = privs.PrivilegeAnalyzer(rolename=DUMMY, access=DUMMY, object_kind=object_kind, desired_items=DUMMY, schema_writers=DUMMY, personal_schemas=DUMMY, dbcontext=mockdbcontext, excepted_items=[]) actual = privconf.get_object_owner(objname) assert actual == expected
def test_analyze_nondefaults(mockdbcontext): """ Test that: - schema0.* is expanded out and all are granted - an existing and desired grant is skipped (schema0.table1) - a desired but non-existent grant is made (schema0.table0 and schema1.table2) - an existing but undesired grant is revoked (schema1.table3) Setup: - schema0.table0 (owned by role2) - DESIRED --> GRANT - schema0.table1 (owned by role3) - GRANTED DESIRED - schema1.table2 (owned by role2) - DESIRED --> GRANT - schema1.table3 (owned by role3) - GRANTED --> REVOKE """ mockdbcontext.get_role_current_nondefaults = lambda x, y, z: set([ (ObjectName(SCHEMAS[0], TABLES[1]), 'SELECT'), (ObjectName(SCHEMAS[1], TABLES[3]), 'SELECT'), ]) mockdbcontext.get_all_object_attributes = lambda: { 'schemas': { SCHEMAS[0]: { ObjectName(SCHEMAS[0]): { 'owner': ROLES[1], 'is_dependent': False } }, SCHEMAS[1]: { ObjectName(SCHEMAS[1]): { 'owner': ROLES[1], 'is_dependent': False } }, }, 'tables': { SCHEMAS[0]: { ObjectName(SCHEMAS[0], TABLES[0]): { 'owner': ROLES[2], 'is_dependent': False }, ObjectName(SCHEMAS[0], TABLES[1]): { 'owner': ROLES[3], 'is_dependent': False }, }, SCHEMAS[1]: { ObjectName(SCHEMAS[1], TABLES[2]): { 'owner': ROLES[2], 'is_dependent': False }, ObjectName(SCHEMAS[1], TABLES[3]): { 'owner': ROLES[3], 'is_dependent': False }, }, } } desired_items = [ ObjectName(SCHEMAS[0], '*'), ObjectName(SCHEMAS[1], TABLES[2]), ] dummy_schema_writers = defaultdict(set) privconf = privs.PrivilegeAnalyzer(rolename=ROLES[0], access='read', object_kind='tables', desired_items=desired_items, schema_writers=dummy_schema_writers, personal_schemas=DUMMY, dbcontext=mockdbcontext) expected = set([ # Grant for schema0.table0 privs.Q_GRANT_NONDEFAULT.format('SELECT', 'TABLE', quoted_object(SCHEMAS[0], TABLES[0]), ROLES[0]), # Grant for schema1.table2 privs.Q_GRANT_NONDEFAULT.format('SELECT', 'TABLE', quoted_object(SCHEMAS[1], TABLES[2]), ROLES[0]), # Revoke for schema1.table3 privs.Q_REVOKE_NONDEFAULT.format('SELECT', 'TABLE', quoted_object(SCHEMAS[1], TABLES[3]), ROLES[0]), ]) # Run analyze_nondefaults(); note that we have to do identify_desired_objects() # first to set things up privconf.identify_desired_objects() privconf.analyze_nondefaults() actual = privconf.sql_to_run assert expected.difference(actual) == set()
def test_identify_desired_objects_personal_schemas_object_kind_is_not_schema( mockdbcontext): """ Make sure that if we desire 'personal_schemas.*' and the object_kind is something other than 'schema' that items in personal schemas show up in the the desired_nondefaults and the personal schemas show up in the desired_defaults """ mockdbcontext.get_all_object_attributes = lambda: { 'tables': { SCHEMAS[0]: { ObjectName(SCHEMAS[0], TABLES[0]): { 'owner': ROLES[1], 'is_dependent': False }, }, ROLES[2]: { ObjectName(ROLES[2], TABLES[2]): { 'owner': ROLES[2], 'is_dependent': False }, ObjectName(ROLES[2], TABLES[3]): { 'owner': ROLES[2], 'is_dependent': False }, }, ROLES[3]: { ObjectName(ROLES[3], TABLES[4]): { 'owner': ROLES[3], 'is_dependent': False }, ObjectName(ROLES[3], TABLES[5]): { 'owner': ROLES[3], 'is_dependent': False }, }, }, 'schemas': { SCHEMAS[0]: { ObjectName(SCHEMAS[0]): { 'owner': ROLES[1] }, 'is_dependent': False }, ROLES[2]: { ObjectName(ROLES[2]): { 'owner': ROLES[2] }, 'is_dependent': False }, ROLES[3]: { ObjectName(ROLES[3]): { 'owner': ROLES[3] }, 'is_dependent': False }, }, } personal_schemas = set([ObjectName(ROLES[2]), ObjectName(ROLES[3])]) access = 'read' object_kind = 'tables' desired_items = [ ObjectName(SCHEMAS[0], TABLES[0]), ObjectName('personal_schemas', '*') ] schema_writers = { ObjectName(ROLES[2]): set([ROLES[2], ROLES[1]]), ObjectName(ROLES[3]): set([ROLES[3]]), } privconf = privs.PrivilegeAnalyzer(ROLES[0], access=access, object_kind=object_kind, desired_items=desired_items, schema_writers=schema_writers, personal_schemas=personal_schemas, dbcontext=mockdbcontext) privconf.identify_desired_objects() # Check default privileges possible_privs = privs.PRIVILEGE_MAP[object_kind][access] expected_defaults = set([ (ROLES[2], ObjectName(ROLES[2]), possible_privs[0]), (ROLES[1], ObjectName(ROLES[2]), possible_privs[0]), (ROLES[3], ObjectName(ROLES[3]), possible_privs[0]) ]) actual_defaults = privconf.desired_defaults assert actual_defaults == expected_defaults # Check non-default privileges expected_nondefault_items = [ ObjectName(SCHEMAS[0], TABLES[0]), ObjectName(ROLES[2], TABLES[2]), ObjectName(ROLES[2], TABLES[3]), ObjectName(ROLES[3], TABLES[4]), ObjectName(ROLES[3], TABLES[5]), ] expected_nondefaults = set( itertools.product(expected_nondefault_items, possible_privs)) actual_nondefaults = privconf.desired_nondefaults assert actual_nondefaults == expected_nondefaults
def test_identify_desired_objects(rolename, mockdbcontext): """ Verify a variety of aspects of the PrivilegeAnalyzer.identify_desired_objects() method, including: * We properly deal with schema.* * When a schema owner doesn't have any objects it shows up in our default privileges set * When a schema owner doesn't have any objects it does not show up in our non default privileges set * It doesn't matter to this method whether the role in question owns the schema, tables, or anything else (that's the mark.parametrize part) """ # Using sequence-write because it has 2 types of privileges (i.e. >1 but not a ton) object_kind = 'sequences' access = 'write' mockdbcontext.get_all_object_attributes = lambda: { 'sequences': { SCHEMAS[0]: { ObjectName(SCHEMAS[0], SEQUENCES[0]): { 'owner': ROLES[1], 'is_dependent': False }, ObjectName(SCHEMAS[0], SEQUENCES[1]): { 'owner': ROLES[2], 'is_dependent': False }, ObjectName(SCHEMAS[0], SEQUENCES[2]): { 'owner': ROLES[2], 'is_dependent': False }, }, SCHEMAS[1]: { ObjectName(SCHEMAS[1], SEQUENCES[0]): { 'owner': ROLES[2], 'is_dependent': False }, ObjectName(SCHEMAS[1], SEQUENCES[1]): { 'owner': ROLES[1], 'is_dependent': False }, }, }, 'schemas': { SCHEMAS[0]: { ObjectName(SCHEMAS[0]): { 'owner': ROLES[0] }, 'is_dependent': False }, SCHEMAS[1]: { ObjectName(SCHEMAS[1]): { 'owner': ROLES[0] }, 'is_dependent': False }, }, } desired_items = [ ObjectName(SCHEMAS[0], '*'), ObjectName(SCHEMAS[1], SEQUENCES[0]), ObjectName(SCHEMAS[1], SEQUENCES[1]) ] schema_writers = {ObjectName(SCHEMAS[0]): set(ROLES[:3])} privconf = privs.PrivilegeAnalyzer(rolename, access=access, object_kind=object_kind, desired_items=desired_items, schema_writers=schema_writers, personal_schemas=DUMMY, dbcontext=mockdbcontext) privconf.identify_desired_objects() # We don't grant default privileges when the grantor is the role itself because in that case # the role already can access the objects (since it owns them). As a result, we need to remove # the grantee role from this list. We need this `if` check because there is one role (role3) # that doesn't own anything and shouldn't show up in our list possible_privs = privs.PRIVILEGE_MAP[object_kind][access] roles = list(ROLES[:3]) # We have to remove this role since we don't grant default privileges to ourselves roles.remove(rolename) expected_defaults = set( itertools.product(roles, [ObjectName(SCHEMAS[0])], possible_privs)) actual_defaults = privconf.desired_defaults assert actual_defaults == expected_defaults nondefault_items = [ObjectName(SCHEMAS[0], t) for t in SEQUENCES[0:3]] \ + [ObjectName(SCHEMAS[1], t) for t in SEQUENCES[:2]] # Remove things owned by the given role if rolename == ROLES[1]: nondefault_items.remove(ObjectName(SCHEMAS[0], SEQUENCES[0])) nondefault_items.remove(ObjectName(SCHEMAS[1], SEQUENCES[1])) elif rolename == ROLES[2]: nondefault_items.remove(ObjectName(SCHEMAS[0], SEQUENCES[1])) nondefault_items.remove(ObjectName(SCHEMAS[0], SEQUENCES[2])) nondefault_items.remove(ObjectName(SCHEMAS[1], SEQUENCES[0])) expected_nondefaults = set( itertools.product(nondefault_items, possible_privs)) actual_nondefaults = privconf.desired_nondefaults assert actual_nondefaults == expected_nondefaults