def globalSetup(): # Bootstrap GlobalConfig._no_post_save_publishing = True GlobalConfig.objects.update_or_create( key=Keys.GLOBAL_CONFIG_KEY, defaults=dict(bounds=GEOSGeometry('MULTIPOLYGON EMPTY'))) GlobalConfig._no_post_save_publishing = False update_or_create_group('superadmin') update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['superadmin'], is_super_user=True) return GlobalConfig.objects.update_or_create( key='global', defaults=dict( name='Global Config', scope='global', bounds= 'MULTIPOLYGON (((-20037508.3399999998509884 -20037508.3399999998509884, -20037508.3399999998509884 20037508.3399999998509884, \ 20037508.3399999998509884 20037508.3399999998509884, 20037508.3399999998509884 -20037508.3399999998509884, \ -20037508.3399999998509884 -20037508.3399999998509884)))'))[0]
def setUp(self, test_patch): self.global_config = globalSetup() self.scag = Region.objects.create( name='SCAG', key='scag_dm', scope='scag_dm', bounds= 'MULTIPOLYGON (((-20037508.3399999998509884 -20037508.3399999998509884, -20037508.3399999998509884 20037508.3399999998509884, \ 20037508.3399999998509884 20037508.3399999998509884, 20037508.3399999998509884 -20037508.3399999998509884, \ -20037508.3399999998509884 -20037508.3399999998509884)))', parent_config_entity=self.global_config) update_or_create_group('admin', config_entity=self.scag, superiors=['superadmin']) update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['admin']) self.test_admin = User.objects.get(username='******') self.client = Client()
def test_get_group_role(self): """Test that we extract the correct role key from a group.""" group = update_or_create_group('scag__or_cnty__lgnhls__manager') self.assertEqual('manager', get_role_key_for_group(group)) group = update_or_create_group('admin') self.assertEqual('admin', get_role_key_for_group(group))
def test_get_role_for_user(self): """Test that we extract the correct role key for a user.""" update_or_create_group('scag__or_cnty__admin', config_entity=self.scag) user_data = update_or_create_user(username='******', password='******', email='*****@*****.**', groups=['scag__or_cnty__admin']) self.assertEqual('admin', get_role_key_for_user(user_data['user']))
def application_initialization(**kwargs): """ Initialize or sync the application :param kwargs: 'limit_to_classes' as an array of ConfigEntity classes to limit processing to those 'no_post_save_publishing' set True to prevent the GlobalConfig save from starting publishers """ # Initialize lookup table data if SouthMigrationHistory.objects.filter(app_name='main').exists(): initialize_table_definitions() initialize_client_data() # Bootstrap the GlobalConfig. We'll fill it out later GlobalConfig._no_post_save_publishing = True global_config = GlobalConfig.objects.update_or_create( key=Keys.GLOBAL_CONFIG_KEY, defaults=dict(bounds=GEOSGeometry('MULTIPOLYGON EMPTY')))[0] GlobalConfig._no_post_save_publishing = False # Bootstrap the admin group and user so we can use them beforehand update_or_create_group(name=UserGroupKey.SUPERADMIN, config_entity=global_config) # These users have the same name as their group update_or_create_user(username=UserGroupKey.SUPERADMIN, password='******', groups=[UserGroupKey.SUPERADMIN], is_super_user=True) # Bootstrap the global config Behaviors behavior_fixture = resolve_fixture("behavior", "behavior", BehaviorFixture, 'global', **kwargs) for behavior in behavior_fixture.behaviors(): update_or_create_behavior(behavior) # Boot strap the global config AttributeGroups attribute_group_fixture = resolve_fixture("behavior", "attribute_group", AttributeGroupFixture, 'global', **kwargs) for attribute_group in attribute_group_fixture.attribute_groups(): update_or_create_attribute_group(attribute_group) # Cartocss template storage create_media_subdir('styles') create_media_subdir('cartocss') # Sync the DBEntities to tables in the global schema global_config = initialize_global_config(**kwargs) for region_fixture in region_fixtures(): # Create the Behavior instances. # These can be defined at the Region scope, but most # are simply defined at default_behavior.py behavior_fixture = resolve_fixture("behavior", "behavior", BehaviorFixture, region_fixture.schema, **kwargs) return map(lambda behavior: update_or_create_behavior(behavior), behavior_fixture.behaviors())
def test_get_role_for_user(self): """Test that we extract the correct role key for a user.""" update_or_create_group('scag__or_cnty__admin', config_entity=self.scag) user_data = update_or_create_user( username='******', password='******', email='*****@*****.**', groups=['scag__or_cnty__admin']) self.assertEqual('admin', get_role_key_for_user(user_data['user']))
def application_initialization(**kwargs): """ Initialize or sync the application :param kwargs: 'limit_to_classes' as an array of ConfigEntity classes to limit processing to those 'no_post_save_publishing' set True to prevent the GlobalConfig save from starting publishers """ # Initialize lookup table data if SouthMigrationHistory.objects.filter(app_name='main').exists(): initialize_table_definitions() initialize_client_data() # Bootstrap the GlobalConfig. We'll fill it out later GlobalConfig._no_post_save_publishing = True global_config = GlobalConfig.objects.update_or_create( key=Keys.GLOBAL_CONFIG_KEY, defaults=dict(bounds=GEOSGeometry('MULTIPOLYGON EMPTY')) )[0] GlobalConfig._no_post_save_publishing = False # Bootstrap the admin group and user so we can use them beforehand update_or_create_group(name=UserGroupKey.SUPERADMIN, config_entity=global_config) # These users have the same name as their group update_or_create_user(username=UserGroupKey.SUPERADMIN, password='******', groups=[UserGroupKey.SUPERADMIN], is_super_user=True) # Bootstrap the global config Behaviors behavior_fixture = resolve_fixture("behavior", "behavior", BehaviorFixture, 'global', **kwargs) for behavior in behavior_fixture.behaviors(): update_or_create_behavior(behavior) # Boot strap the global config AttributeGroups attribute_group_fixture = resolve_fixture("behavior", "attribute_group", AttributeGroupFixture, 'global', **kwargs) for attribute_group in attribute_group_fixture.attribute_groups(): update_or_create_attribute_group(attribute_group) # Cartocss template storage create_media_subdir('styles') create_media_subdir('cartocss') # Sync the DBEntities to tables in the global schema global_config = initialize_global_config(**kwargs) for region_fixture in region_fixtures(): # Create the Behavior instances. # These can be defined at the Region scope, but most # are simply defined at default_behavior.py behavior_fixture = resolve_fixture("behavior", "behavior", BehaviorFixture, region_fixture.schema, **kwargs) return map( lambda behavior: update_or_create_behavior(behavior), behavior_fixture.behaviors())
def test_update_user(self): self.client.get('/footprint/api/v1/user/?format=json&[email protected]&password=test_superadmin_user@uf') update_or_create_group('admin', superiors=['superadmin']) update_or_create_group('manager', superiors=['admin']) update_or_create_group('user', superiors=['admin']) update_or_create_group('scag__or_cnty__admin', self.orange, superiors=['superadmin']) update_or_create_user(username='******', password='******', email='*****@*****.**', first_name='Test', last_name='User', api_key=None, groups=['scag__or_cnty__admin'], is_super_user=False) user_to_update = User.objects.get(username='******') resp = self.client.post( '/footprint/user/{}/'.format(user_to_update.id), dict( username='******', raw_password='******', confirm_password='******', email='*****@*****.**', role='admin', config_entity=self.orange.id, first_name='Changed' ), follow=True ) self.assertEqual(200, resp.status_code) match = re.search(r'User successfully updated', resp.content) self.assertTrue(bool(match)) self.assertEqual('Changed', User.objects.get(id=user_to_update.id).first_name)
def on_config_entity_post_save_group(sender, **kwargs): """ Syncs the user, groups, and permissions for the ConfigEntity Some ConfigEntity classes create their own Groups and default Users. This makes it easy to give a client-specific user permission to certain ConfigEntity by joining the latter's group :param sender: :param kwargs: :return: """ config_entity = InstanceBundle.extract_single_instance(**kwargs) if config_entity._no_post_save_publishing: return user = kwargs.get('user') logger.info("Handler: post_save_user for config_entity {config_entity} and user {username}".format( config_entity=config_entity.name, username=user.username if user else 'undefined')) if kwargs.get('created') and not config_entity.creator: # Set the ConfigEntity.creator to the default admin group user if it wasn't set by the API config_entity.creator = User.objects.get(username=UserGroupKey.SUPERADMIN) config_entity._no_post_save_publishing = True config_entity.save() config_entity._no_post_save_publishing = False # First update_or_create any default groups. This usually just applies to global_config from footprint.client.configuration.fixture import UserFixture from footprint.client.configuration.utils import resolve_fixture user_fixture = resolve_fixture("user", "user", UserFixture, config_entity.schema(), config_entity=config_entity) for group_fixture in user_fixture.groups(): group = update_or_create_group(**group_fixture) logger.info("User Publishing. For ConfigEntity %s synced global UserGroup: %s" % (config_entity.name, group.name)) # Sync permissions for the ConfigEntity # Resolve the default ConfigEntity permissions for this config_entity # Update or Create the ConfigEntity Group(s) for this ConfigEntity config_entity_groups = _update_or_create_config_entity_groups(config_entity) # Get the mapping of groups to permission types for the config_entity's most relevant fixture # These group keys are generally all global groups. from footprint.client.configuration.fixture import ConfigEntitiesFixture config_entities_fixture = resolve_fixture("config_entity", "config_entities", ConfigEntitiesFixture, config_entity.schema()) permission_lookup = config_entities_fixture.default_config_entity_permissions() # Set the permissions for the config_entity groups. This will also set all superior group permissions # to the same permissions or greater is they match something in the permission_lookup config_entity_group_permissions = sync_config_entity_group_permissions( config_entity, config_entity_groups, permission_lookup, permission_key_class=ConfigEntityPermissionKey, **kwargs) # Give the groups read permissions on the ancestor config_entities # TODO restrict this access further for the UserGroupKey.DEMO group groups = Group.objects.filter(name__in=config_entity_group_permissions.keys()) for config_entity in config_entity.ancestors: config_entity.assign_permission_to_groups(groups, PermissionKey.VIEW) # TODO tell children to add themselves to all ancestors (resync) # This will only be needed if the parent ConfigEntity group permission configuration changes reset_queries()
def setUp(self, test_patch): self.global_config = globalSetup() self.scag = Region.objects.create( name='SCAG', key='scag_dm', scope='scag_dm', bounds='MULTIPOLYGON (((-20037508.3399999998509884 -20037508.3399999998509884, -20037508.3399999998509884 20037508.3399999998509884, \ 20037508.3399999998509884 20037508.3399999998509884, 20037508.3399999998509884 -20037508.3399999998509884, \ -20037508.3399999998509884 -20037508.3399999998509884)))', parent_config_entity=self.global_config ) update_or_create_group('admin', config_entity=self.scag, superiors=['superadmin']) update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['admin']) self.test_admin = User.objects.get(username='******') self.client = Client()
def globalSetup(): # Bootstrap GlobalConfig._no_post_save_publishing = True GlobalConfig.objects.update_or_create( key=Keys.GLOBAL_CONFIG_KEY, defaults=dict(bounds=GEOSGeometry('MULTIPOLYGON EMPTY')) ) GlobalConfig._no_post_save_publishing = False update_or_create_group('superadmin') update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['superadmin'], is_super_user=True) return GlobalConfig.objects.update_or_create( key='global', defaults=dict( name='Global Config', scope='global', bounds='MULTIPOLYGON (((-20037508.3399999998509884 -20037508.3399999998509884, -20037508.3399999998509884 20037508.3399999998509884, \ 20037508.3399999998509884 20037508.3399999998509884, 20037508.3399999998509884 -20037508.3399999998509884, \ -20037508.3399999998509884 -20037508.3399999998509884)))' ) )[0]
def test_create_user(self): """Test that a user can create a new user in a group subordinate to themselves.""" self.client.get( '/footprint/api/v1/user/?format=json&[email protected]&password=test_superadmin_user@uf' ) update_or_create_group('admin', superiors=['superadmin']) update_or_create_group('manager', superiors=['admin']) update_or_create_group('user', superiors=['admin']) update_or_create_group('scag__or_cnty__admin', self.orange, superiors=['superadmin']) resp = self.client.post( '/footprint/add_user/', dict(username='******', raw_password='******', confirm_password='******', email='*****@*****.**', role='admin', config_entity=self.orange.id), follow=True) self.assertEqual(200, resp.status_code) match = re.search(r'User successfully added', resp.content) self.assertTrue(bool(match)) created_user = User.objects.get(username='******') self.assertEqual('*****@*****.**', created_user.email) self.assertTrue( created_user.password.startswith('pbkdf2_sha256$10000')) self.assertTrue(created_user.is_active) self.assertTrue(created_user.is_staff) self.assertFalse(created_user.is_superuser) self.assertEqual(1, created_user.groups.count()) self.assertEqual('scag__or_cnty__admin', created_user.groups.all()[0].name) resp = self.client.get( '/footprint/api/v1/user/?format=json&username=test_oc_admin&password=test_oc_admin@uf' ) self.assertEqual( 40, len(json.loads(resp.content)['objects'][0]['api_key']))
def _update_or_create_config_entity_group(config_entity, global_group_name): # ConfigEntity group name is created by combining it's schema with the global # group's name. The exception is GlobalConfig, whose ConfigEntity Group is simply the global admin group if not config_entity.schema_prefix: # GlobalConfig case, nothing to do here return Group.objects.get(name=global_group_name) config_entity_group_name = '__'.join( compact([config_entity.schema_prefix, global_group_name])) # The superiors of this group are our global Group and # the parent ConfigEntity's groups whose global Group is equal or greater than ours # (e.g. foo__user's superior is user and and bar_user and bar_manager if bar is the parent ConfigEntity of foo) # Get the name of all groups of the parent ConfigEntity all_parent_config_entity_group_hierarchies = config_entity.parent_config_entity.group_hierarchies.all( ) # Only accept Groups that are at least at our global Group level eligible_global_groups_names = UserGroupKey.GLOBAL[ 0:UserGroupKey.GLOBAL.index(global_group_name) + 1] # Given a minimum eligible permission level (e.g. above Manager), # find all of the parent ConfigEntity's Groups that have at least that permission level (e.g. Manager and Admin). # These logically deserve all the permissions of the child ConfigEntity Group. # It's unlikely that a parent ConfigEntity Group would be a lower permission level (e.g. User), so this will normally accept all parent ConfigEntity Groups config_entity_groups_matching_eligible_global_groups = [] for group_hierarchy in all_parent_config_entity_group_hierarchies: if group_hierarchy.globalized_group( ).name in eligible_global_groups_names: config_entity_groups_matching_eligible_global_groups.append( group_hierarchy) # Sort by ascending permission. This probably doesn't matter-- # it just means we process the lower permission first for consistent logging. (There is typically only one Group anyway) group_hierarchies = sorted( config_entity_groups_matching_eligible_global_groups, key=lambda group_hierarchy: eligible_global_groups_names.index( group_hierarchy.globalized_group().name)) # Combine our global Group name with the parent ConfigEntity Groups superior_group_names = unique([global_group_name] + map( lambda group_hierarchy: group_hierarchy.group.name, group_hierarchies)) # Update or create the Group return update_or_create_group(name=config_entity_group_name, config_entity=config_entity, superiors=superior_group_names)
def _update_or_create_config_entity_group(config_entity, global_group_name): # ConfigEntity group name is created by combining it's schema with the global # group's name. The exception is GlobalConfig, whose ConfigEntity Group is simply the global admin group if not config_entity.schema_prefix: # GlobalConfig case, nothing to do here return Group.objects.get(name=global_group_name) config_entity_group_name = '__'.join(compact([config_entity.schema_prefix, global_group_name])) # The superiors of this group are our global Group and # the parent ConfigEntity's groups whose global Group is equal or greater than ours # (e.g. foo__user's superior is user and and bar_user and bar_manager if bar is the parent ConfigEntity of foo) # Get the name of all groups of the parent ConfigEntity all_parent_config_entity_group_hierarchies = config_entity.parent_config_entity.group_hierarchies.all() # Only accept Groups that are at least at our global Group level eligible_global_groups_names = UserGroupKey.GLOBAL[0:UserGroupKey.GLOBAL.index(global_group_name)+1] # Given a minimum eligible permission level (e.g. above Manager), # find all of the parent ConfigEntity's Groups that have at least that permission level (e.g. Manager and Admin). # These logically deserve all the permissions of the child ConfigEntity Group. # It's unlikely that a parent ConfigEntity Group would be a lower permission level (e.g. User), so this will normally accept all parent ConfigEntity Groups config_entity_groups_matching_eligible_global_groups = [] for group_hierarchy in all_parent_config_entity_group_hierarchies: if group_hierarchy.globalized_group().name in eligible_global_groups_names: config_entity_groups_matching_eligible_global_groups.append(group_hierarchy) # Sort by ascending permission. This probably doesn't matter-- # it just means we process the lower permission first for consistent logging. (There is typically only one Group anyway) group_hierarchies = sorted( config_entity_groups_matching_eligible_global_groups, key=lambda group_hierarchy: eligible_global_groups_names.index(group_hierarchy.globalized_group().name)) # Combine our global Group name with the parent ConfigEntity Groups superior_group_names = unique( [global_group_name] + map(lambda group_hierarchy: group_hierarchy.group.name, group_hierarchies) ) # Update or create the Group return update_or_create_group( name=config_entity_group_name, config_entity=config_entity, superiors=superior_group_names)
def test_update_user(self): self.client.get( '/footprint/api/v1/user/?format=json&[email protected]&password=test_superadmin_user@uf' ) update_or_create_group('admin', superiors=['superadmin']) update_or_create_group('manager', superiors=['admin']) update_or_create_group('user', superiors=['admin']) update_or_create_group('scag__or_cnty__admin', self.orange, superiors=['superadmin']) update_or_create_user(username='******', password='******', email='*****@*****.**', first_name='Test', last_name='User', api_key=None, groups=['scag__or_cnty__admin'], is_super_user=False) user_to_update = User.objects.get(username='******') resp = self.client.post( '/footprint/user/{}/'.format(user_to_update.id), dict(username='******', raw_password='******', confirm_password='******', email='*****@*****.**', role='admin', config_entity=self.orange.id, first_name='Changed'), follow=True) self.assertEqual(200, resp.status_code) match = re.search(r'User successfully updated', resp.content) self.assertTrue(bool(match)) self.assertEqual('Changed', User.objects.get(id=user_to_update.id).first_name)
def test_create_user(self): """Test that a user can create a new user in a group subordinate to themselves.""" self.client.get('/footprint/api/v1/user/?format=json&[email protected]&password=test_superadmin_user@uf') update_or_create_group('admin', superiors=['superadmin']) update_or_create_group('manager', superiors=['admin']) update_or_create_group('user', superiors=['admin']) update_or_create_group('scag__or_cnty__admin', self.orange, superiors=['superadmin']) resp = self.client.post( '/footprint/add_user/', dict( username='******', raw_password='******', confirm_password='******', email='*****@*****.**', role='admin', config_entity=self.orange.id ), follow=True ) self.assertEqual(200, resp.status_code) match = re.search(r'User successfully added', resp.content) self.assertTrue(bool(match)) created_user = User.objects.get(username='******') self.assertEqual('*****@*****.**', created_user.email) self.assertTrue(created_user.password.startswith('pbkdf2_sha256$10000')) self.assertTrue(created_user.is_active) self.assertTrue(created_user.is_staff) self.assertFalse(created_user.is_superuser) self.assertEqual(1, created_user.groups.count()) self.assertEqual('scag__or_cnty__admin', created_user.groups.all()[0].name) resp = self.client.get('/footprint/api/v1/user/?format=json&username=test_oc_admin&password=test_oc_admin@uf') self.assertEqual(40, len(json.loads(resp.content)['objects'][0]['api_key']))
def test_view_users(self): """Test that an admin user can view a list of users to manage.""" update_or_create_group('scag__admin', self.scag, superiors=['superadmin']) update_or_create_group('scag__or_cnty__admin', self.orange, superiors=['superadmin', 'scag__admin']) update_or_create_group('scag__or_cnty__lgnhls__manager', self.laguna, superiors=['superadmin', 'scag__admin', 'scag__or_cnty__admin']) update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['scag__or_cnty__admin'], is_super_user=False) update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['scag__or_cnty__lgnhls__manager'], is_super_user=False) self.client.get('/footprint/api/v1/user/?format=json&[email protected]&password=test_oc_admin@uf') resp = self.client.get('/footprint/users/') self.assertEqual(len(resp.context['users']), 1) self.assertEqual(len(resp.context['users']['Laguna Hills']), 1) self.assertEqual(resp.context['users']['Laguna Hills'][0]['user'].username, 'test_lh_manager') self.assertEqual(resp.context['users']['Laguna Hills'][0]['role'], 'Manager')
def test_view_users(self): """Test that an admin user can view a list of users to manage.""" update_or_create_group('scag__admin', self.scag, superiors=['superadmin']) update_or_create_group('scag__or_cnty__admin', self.orange, superiors=['superadmin', 'scag__admin']) update_or_create_group( 'scag__or_cnty__lgnhls__manager', self.laguna, superiors=['superadmin', 'scag__admin', 'scag__or_cnty__admin']) update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['scag__or_cnty__admin'], is_super_user=False) update_or_create_user(username='******', password='******', email='*****@*****.**', api_key=None, groups=['scag__or_cnty__lgnhls__manager'], is_super_user=False) self.client.get( '/footprint/api/v1/user/?format=json&[email protected]&password=test_oc_admin@uf' ) resp = self.client.get('/footprint/users/') self.assertEqual(len(resp.context['users']), 1) self.assertEqual(len(resp.context['users']['Laguna Hills']), 1) self.assertEqual( resp.context['users']['Laguna Hills'][0]['user'].username, 'test_lh_manager') self.assertEqual(resp.context['users']['Laguna Hills'][0]['role'], 'Manager')
def on_config_entity_post_save_group(sender, **kwargs): """ Syncs the user, groups, and permissions for the ConfigEntity Some ConfigEntity classes create their own Groups and default Users. This makes it easy to give a client-specific user permission to certain ConfigEntity by joining the latter's group :param sender: :param kwargs: :return: """ config_entity = InstanceBundle.extract_single_instance(**kwargs) if config_entity._no_post_save_publishing: return user = kwargs.get('user') logger.info( "Handler: post_save_user for config_entity {config_entity} and user {username}" .format(config_entity=config_entity.name, username=user.username if user else 'undefined')) if kwargs.get('created') and not config_entity.creator: # Set the ConfigEntity.creator to the default admin group user if it wasn't set by the API config_entity.creator = User.objects.get( username=UserGroupKey.SUPERADMIN) config_entity._no_post_save_publishing = True config_entity.save() config_entity._no_post_save_publishing = False # First update_or_create any default groups. This usually just applies to global_config from footprint.client.configuration.fixture import UserFixture from footprint.client.configuration.utils import resolve_fixture user_fixture = resolve_fixture("user", "user", UserFixture, config_entity.schema(), config_entity=config_entity) for group_fixture in user_fixture.groups(): group = update_or_create_group(**group_fixture) logger.info( "User Publishing. For ConfigEntity %s synced global UserGroup: %s" % (config_entity.name, group.name)) # Sync permissions for the ConfigEntity # Resolve the default ConfigEntity permissions for this config_entity # Update or Create the ConfigEntity Group(s) for this ConfigEntity config_entity_groups = _update_or_create_config_entity_groups( config_entity) # Get the mapping of groups to permission types for the config_entity's most relevant fixture # These group keys are generally all global groups. from footprint.client.configuration.fixture import ConfigEntitiesFixture config_entities_fixture = resolve_fixture("config_entity", "config_entities", ConfigEntitiesFixture, config_entity.schema()) permission_lookup = config_entities_fixture.default_config_entity_permissions( ) # Set the permissions for the config_entity groups. This will also set all superior group permissions # to the same permissions or greater is they match something in the permission_lookup config_entity_group_permissions = sync_config_entity_group_permissions( config_entity, config_entity_groups, permission_lookup, permission_key_class=ConfigEntityPermissionKey, **kwargs) # Give the groups read permissions on the ancestor config_entities # TODO restrict this access further for the UserGroupKey.DEMO group groups = Group.objects.filter( name__in=config_entity_group_permissions.keys()) for config_entity in config_entity.ancestors: config_entity.assign_permission_to_groups(groups, PermissionKey.VIEW) # TODO tell children to add themselves to all ancestors (resync) # This will only be needed if the parent ConfigEntity group permission configuration changes reset_queries()