From 3a2f5d136285ca8fd402544570ececce93d6aae2 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Wed, 20 Apr 2016 16:58:40 -0400 Subject: [PATCH 1/8] Update BaseSerializer to handle objects that dont have created/modified fields --- awx/api/serializers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 3da45dc70d..0382e6d549 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -340,16 +340,18 @@ class BaseSerializer(serializers.ModelSerializer): return None elif isinstance(obj, User): return obj.date_joined - else: + elif hasattr(obj, 'created'): return obj.created + return None def get_modified(self, obj): if obj is None: return None elif isinstance(obj, User): return obj.last_login # Not actually exposed for User. - else: + elif hasattr(obj, 'modified'): return obj.modified + return None def build_standard_field(self, field_name, model_field): # DRF 3.3 serializers.py::build_standard_field() -> utils/field_mapping.py::get_field_kwargs() short circuits From aae548fbdc8f0abac015b5c689fb28a110c6fd12 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Wed, 20 Apr 2016 22:36:57 -0400 Subject: [PATCH 2/8] Regenerated 0008 migration to get rid of Implicit role names/descriptions --- awx/main/migrations/0008_v300_rbac_changes.py | 533 ++++++++---------- 1 file changed, 236 insertions(+), 297 deletions(-) diff --git a/awx/main/migrations/0008_v300_rbac_changes.py b/awx/main/migrations/0008_v300_rbac_changes.py index c22e89cde9..a704d37425 100644 --- a/awx/main/migrations/0008_v300_rbac_changes.py +++ b/awx/main/migrations/0008_v300_rbac_changes.py @@ -2,21 +2,21 @@ from __future__ import unicode_literals from django.db import migrations, models -import django.db.models.deletion from django.conf import settings -import taggit.managers import awx.main.fields class Migration(migrations.Migration): dependencies = [ - ('taggit', '0002_auto_20150616_2121'), ('contenttypes', '0002_remove_content_type_name'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('main', '0007_v300_active_flag_removal'), ] operations = [ + # + # Patch up existing + # migrations.RenameField( 'Organization', 'admins', @@ -47,300 +47,6 @@ class Migration(migrations.Migration): name='deprecated_projects', field=models.ManyToManyField(related_name='deprecated_teams', to='main.Project', blank=True), ), - - migrations.CreateModel( - name='RoleAncestorEntry', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('role_field', models.TextField()), - ('content_type_id', models.PositiveIntegerField(null=False)), - ('object_id', models.PositiveIntegerField(null=False)), - ], - options={ - 'db_table': 'main_rbac_role_ancestors', - 'verbose_name_plural': 'role_ancestors', - }, - ), - migrations.CreateModel( - name='Role', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('created', models.DateTimeField(default=None, editable=False)), - ('modified', models.DateTimeField(default=None, editable=False)), - ('description', models.TextField(default=b'', blank=True)), - ('name', models.CharField(max_length=512)), - ('singleton_name', models.TextField(default=None, unique=True, null=True, db_index=True)), - ('object_id', models.PositiveIntegerField(default=None, null=True)), - ('ancestors', models.ManyToManyField(related_name='descendents', through='main.RoleAncestorEntry', to='main.Role')), - ('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)), - ('created_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), - ('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)), - ('modified_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), - ('parents', models.ManyToManyField(related_name='children', to='main.Role')), - ('implicit_parents', models.TextField(null=False, default=b'[]')), - ('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')), - ], - options={ - 'db_table': 'main_rbac_roles', - 'verbose_name_plural': 'roles', - }, - ), - migrations.AddField( - model_name='roleancestorentry', - name='ancestor', - field=models.ForeignKey(related_name='+', to='main.Role'), - ), - migrations.AddField( - model_name='roleancestorentry', - name='descendent', - field=models.ForeignKey(related_name='+', to='main.Role'), - ), - migrations.AlterIndexTogether( - name='roleancestorentry', - index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field')]), - ), - - migrations.AddField( - model_name='credential', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Auditor of the credential', parent_role=[b'singleton:System Auditor'], to='main.Role', role_name=b'Credential Auditor', null=b'True'), - ), - migrations.AddField( - model_name='credential', - name='owner_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Owner of the credential', parent_role=[b'singleton:System Administrator'], to='main.Role', role_name=b'Credential Owner', null=b'True'), - ), - migrations.AddField( - model_name='credential', - name='use_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Credential User', null=b'True'), - ), - migrations.AddField( - model_name='custominventoryscript', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'CustomInventory Administrator', null=b'True'), - ), - migrations.AddField( - model_name='custominventoryscript', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'CustomInventory Auditor', null=b'True'), - ), - migrations.AddField( - model_name='custominventoryscript', - name='member_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.member_role', to='main.Role', role_name=b'CustomInventory Member', null=b'True'), - ), - migrations.AddField( - model_name='group', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', role_name=b'Inventory Group Administrator', null=b'True'), - ), - migrations.AddField( - model_name='group', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.auditor_role', b'parents.auditor_role'], to='main.Role', role_name=b'Inventory Group Auditor', null=b'True'), - ), - migrations.AddField( - model_name='group', - name='execute_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.execute_role', b'parents.executor_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'), - ), - migrations.AddField( - model_name='group', - name='update_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.update_role', b'parents.updater_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True'), - ), - migrations.AddField( - model_name='inventory', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Inventory Administrator', null=b'True'), - ), - migrations.AddField( - model_name='inventory', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Inventory Auditor', null=b'True'), - ), - migrations.AddField( - model_name='inventory', - name='execute_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=None, to='main.Role', role_name=b'Inventory Executor', null=b'True'), - ), - migrations.AddField( - model_name='inventory', - name='update_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=None, to='main.Role', role_name=b'Inventory Updater', null=b'True'), - ), - migrations.AddField( - model_name='inventory', - name='use_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Inventory User', null=b'True'), - ), - migrations.AddField( - model_name='jobtemplate', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Full access to all settings', parent_role=[(b'project.admin_role', b'inventory.admin_role')], to='main.Role', role_name=b'Job Template Administrator', null=b'True'), - ), - migrations.AddField( - model_name='jobtemplate', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read-only access to all settings', parent_role=[(b'project.auditor_role', b'inventory.auditor_role')], to='main.Role', role_name=b'Job Template Auditor', null=b'True'), - ), - migrations.AddField( - model_name='jobtemplate', - name='execute_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=None, to='main.Role', role_name=b'Job Template Runner', null=b'True'), - ), - migrations.AddField( - model_name='organization', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage all aspects of this organization', parent_role=b'singleton:System Administrator', to='main.Role', role_name=b'Organization Administrator', null=b'True'), - ), - migrations.AddField( - model_name='organization', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this organization', parent_role=b'singleton:System Auditor', to='main.Role', role_name=b'Organization Auditor', null=b'True'), - ), - migrations.AddField( - model_name='organization', - name='member_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this organization', parent_role=b'admin_role', to='main.Role', role_name=b'Organization Member', null=b'True'), - ), - migrations.AddField( - model_name='project', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this project', parent_role=[b'organization.admin_role', b'singleton:System Administrator'], to='main.Role', role_name=b'Project Administrator', null=b'True'), - ), - migrations.AddField( - model_name='project', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this project', parent_role=[b'organization.auditor_role', b'singleton:System Auditor'], to='main.Role', role_name=b'Project Auditor', null=b'True'), - ), - migrations.AddField( - model_name='project', - name='member_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=None, to='main.Role', role_name=b'Project Member', null=b'True'), - ), - migrations.AddField( - model_name='project', - name='scm_update_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update this project from the source control management system', parent_role=b'admin_role', to='main.Role', role_name=b'Project Updater', null=b'True'), - ), - migrations.AddField( - model_name='team', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this team', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Team Administrator', null=b'True'), - ), - migrations.AddField( - model_name='team', - name='auditor_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this team', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Team Auditor', null=b'True'), - ), - migrations.AddField( - model_name='team', - name='member_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this team', to='main.Role', role_name=b'Team Member', null=b'True'), - ), - - migrations.AddField( - model_name='credential', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read this credential', parent_role=[b'use_role', b'auditor_role', b'owner_role'], to='main.Role', role_name=b'Credential REad', null=b'True'), - ), - migrations.AddField( - model_name='custominventoryscript', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=[b'auditor_role', b'member_role', b'admin_role'], to='main.Role', role_name=b'CustomInventory Read', null=b'True'), - ), - migrations.AddField( - model_name='group', - name='adhoc_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute ad hoc commands against this inventory', parent_role=[b'inventory.adhoc_role', b'parents.adhoc_role', b'admin_role'], to='main.Role', role_name=b'Inventory Ad Hoc', null=b'True'), - ), - migrations.AddField( - model_name='group', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'execute_role', b'update_role', b'auditor_role', b'admin_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'), - ), - migrations.AddField( - model_name='inventory', - name='adhoc_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute ad hoc commands against this inventory', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory Ad Hoc', null=b'True'), - ), - migrations.AddField( - model_name='inventory', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view this inventory', parent_role=[b'auditor_role', b'execute_role', b'update_role', b'use_role', b'admin_role'], to='main.Role', role_name=b'Read', null=b'True'), - ), - migrations.AddField( - model_name='jobtemplate', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=[b'execute_role', b'auditor_role', b'admin_role'], to='main.Role', role_name=b'Job Template Runner', null=b'True'), - ), - migrations.AddField( - model_name='organization', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read an organization', parent_role=[b'member_role', b'auditor_role'], to='main.Role', role_name=b'Organization Read Access', null=b'True'), - ), - migrations.AddField( - model_name='project', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read access to this project', parent_role=[b'member_role', b'auditor_role', b'scm_update_role'], to='main.Role', role_name=b'Project Read Access', null=b'True'), - ), - migrations.AddField( - model_name='role', - name='role_field', - field=models.TextField(default=b''), - ), - migrations.AddField( - model_name='team', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Can view this team', parent_role=[b'admin_role', b'auditor_role', b'member_role'], to='main.Role', role_name=b'Read', null=b'True'), - ), - migrations.AlterField( - model_name='credential', - name='use_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=[b'owner_role'], to='main.Role', role_name=b'Credential User', null=b'True'), - ), - migrations.AlterField( - model_name='group', - name='execute_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.execute_role', b'parents.execute_role', b'adhoc_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'), - ), - migrations.AlterField( - model_name='group', - name='update_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.update_role', b'parents.update_role', b'admin_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True'), - ), - migrations.AlterField( - model_name='inventory', - name='execute_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=b'adhoc_role', to='main.Role', role_name=b'Inventory Executor', null=b'True'), - ), - migrations.AlterField( - model_name='inventory', - name='update_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory Updater', null=b'True'), - ), - migrations.AlterField( - model_name='inventory', - name='use_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory User', null=b'True'), - ), - migrations.AlterField( - model_name='jobtemplate', - name='execute_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=[b'admin_role'], to='main.Role', role_name=b'Job Template Runner', null=b'True'), - ), - migrations.AlterField( - model_name='project', - name='member_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=b'admin_role', to='main.Role', role_name=b'Project Member', null=b'True'), - ), - - - - - migrations.RenameField( model_name='organization', old_name='projects', @@ -380,4 +86,237 @@ class Migration(migrations.Migration): name='credential', unique_together=set([]), ), + + + # + # New RBAC models and fields + # + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('role_field', models.TextField()), + ('singleton_name', models.TextField(default=None, unique=True, null=True, db_index=True)), + ('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)), + ('parents', models.ManyToManyField(related_name='children', to='main.Role')), + ('implicit_parents', models.TextField(default=b'[]')), + ('ancestors', models.ManyToManyField(related_name='descendents', through='main.RoleAncestorEntry', to='main.Role')), + ('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)), + ('object_id', models.PositiveIntegerField(default=None, null=True)), + + ], + options={ + 'db_table': 'main_rbac_roles', + 'verbose_name_plural': 'roles', + }, + ), + migrations.CreateModel( + name='RoleAncestorEntry', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('role_field', models.TextField()), + ('content_type_id', models.PositiveIntegerField()), + ('object_id', models.PositiveIntegerField()), + ('ancestor', models.ForeignKey(related_name='+', to='main.Role')), + ('descendent', models.ForeignKey(related_name='+', to='main.Role')), + ], + options={ + 'db_table': 'main_rbac_role_ancestors', + 'verbose_name_plural': 'role_ancestors', + }, + ), + migrations.AlterIndexTogether( + name='roleancestorentry', + index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field')]), + ), + migrations.AddField( + model_name='credential', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_auditor'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='credential', + name='owner_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_administrator'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='credential', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'use_role', b'auditor_role', b'owner_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='credential', + name='use_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'owner_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='custominventoryscript', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.admin_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='custominventoryscript', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.auditor_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='custominventoryscript', + name='member_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.member_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='custominventoryscript', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'auditor_role', b'member_role', b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='group', + name='adhoc_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.adhoc_role', b'parents.adhoc_role', b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='group', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='group', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.auditor_role', b'parents.auditor_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='group', + name='execute_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.execute_role', b'parents.execute_role', b'adhoc_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='group', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'execute_role', b'update_role', b'auditor_role', b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='group', + name='update_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.update_role', b'parents.update_role', b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='inventory', + name='adhoc_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='inventory', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.admin_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='inventory', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.auditor_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='inventory', + name='execute_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'adhoc_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='inventory', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'auditor_role', b'execute_role', b'update_role', b'use_role', b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='inventory', + name='update_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='inventory', + name='use_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='jobtemplate', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[(b'project.admin_role', b'inventory.admin_role')], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='jobtemplate', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[(b'project.auditor_role', b'inventory.auditor_role')], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='jobtemplate', + name='execute_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='jobtemplate', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'execute_role', b'auditor_role', b'admin_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='organization', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'singleton:system_administrator', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='organization', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'singleton:system_auditor', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='organization', + name='member_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='organization', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'member_role', b'auditor_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='project', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'organization.admin_role', b'singleton:system_administrator'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='project', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'organization.auditor_role', b'singleton:system_auditor'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='project', + name='member_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='project', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'member_role', b'auditor_role', b'scm_update_role'], to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='project', + name='scm_update_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='team', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.admin_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='team', + name='auditor_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.auditor_role', to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='team', + name='member_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=None, to='main.Role', null=b'True'), + ), + migrations.AddField( + model_name='team', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role', b'auditor_role', b'member_role'], to='main.Role', null=b'True'), + ), ] From 280993a15dc89e3d160e18b16c92057e1ca88b3d Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Wed, 20 Apr 2016 22:38:32 -0400 Subject: [PATCH 3/8] Dropped stored role name/description and other superflous fields For name and description, we'll derive these from the role_field and content type, which is much better for lots of reasons (eg changing text the future). Also ditched the rest of the fields comming from the standard common base model, we didn't use them and they cost several indexes on the table. --- awx/main/fields.py | 17 +---- awx/main/migrations/_rbac.py | 14 ++-- awx/main/models/credential.py | 8 --- awx/main/models/inventory.py | 31 --------- awx/main/models/jobs.py | 8 --- awx/main/models/organization.py | 19 +----- awx/main/models/projects.py | 10 --- awx/main/models/rbac.py | 64 ++++++++++++++++--- awx/main/signals.py | 3 +- .../api/test_resource_access_lists.py | 3 +- awx/main/tests/functional/conftest.py | 6 -- awx/main/tests/functional/test_rbac_api.py | 6 +- awx/main/tests/functional/test_rbac_core.py | 28 ++++---- .../tests/functional/test_rbac_project.py | 2 +- awx/main/tests/functional/test_rbac_user.py | 2 +- 15 files changed, 88 insertions(+), 133 deletions(-) diff --git a/awx/main/fields.py b/awx/main/fields.py index e116299bcb..568126c536 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -92,9 +92,7 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor): class ImplicitRoleField(models.ForeignKey): """Implicitly creates a role entry for a resource""" - def __init__(self, role_name=None, role_description=None, parent_role=None, *args, **kwargs): - self.role_name = role_name - self.role_description = role_description if role_description else "" + def __init__(self, parent_role=None, *args, **kwargs): self.parent_role = parent_role kwargs.setdefault('to', 'Role') @@ -104,8 +102,6 @@ class ImplicitRoleField(models.ForeignKey): def deconstruct(self): name, path, args, kwargs = super(ImplicitRoleField, self).deconstruct() - kwargs['role_name'] = self.role_name - kwargs['role_description'] = self.role_description kwargs['parent_role'] = self.parent_role return name, path, args, kwargs @@ -190,11 +186,7 @@ class ImplicitRoleField(models.ForeignKey): if cur_role is None: missing_roles.append( Role_( - created=now(), - modified=now(), role_field=implicit_role_field.name, - name=implicit_role_field.role_name, - description=implicit_role_field.role_description, content_type_id=ct_id, object_id=instance.id ) @@ -247,12 +239,7 @@ class ImplicitRoleField(models.ForeignKey): if qs.count() >= 1: role = qs[0] else: - role = Role_.objects.create(created=now(), - modified=now(), - role_field=path, - singleton_name=singleton_name, - name=singleton_name, - description=singleton_name) + role = Role_.objects.create(singleton_name=singleton_name, role_field=singleton_name) parents = [role.id] else: parents = resolve_role_field(instance, path) diff --git a/awx/main/migrations/_rbac.py b/awx/main/migrations/_rbac.py index 0d7aa3ecb7..8710f8c772 100644 --- a/awx/main/migrations/_rbac.py +++ b/awx/main/migrations/_rbac.py @@ -44,9 +44,7 @@ def migrate_users(apps, schema_editor): logger.info(smart_text(u"found existing role for user: {}".format(user.username))) except Role.DoesNotExist: role = Role.objects.create( - created=now(), - modified=now(), - singleton_name = smart_text(u'{}-admin_role'.format(user.username)), + role_field='admin_role', content_type = user_content_type, object_id = user.id ) @@ -54,14 +52,12 @@ def migrate_users(apps, schema_editor): logger.info(smart_text(u"migrating to new role for user: {}".format(user.username))) if user.is_superuser: - if Role.objects.filter(singleton_name='System Administrator').exists(): - sa_role = Role.objects.get(singleton_name='System Administrator') + if Role.objects.filter(singleton_name='system_administrator').exists(): + sa_role = Role.objects.get(singleton_name='system_administrator') else: sa_role = Role.objects.create( - created=now(), - modified=now(), - singleton_name='System Administrator', - name='System Administrator' + singleton_name='system_administrator', + role_field='system_administrator' ) sa_role.members.add(user) diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index d47153285d..11b2a31e87 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -204,27 +204,19 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): help_text=_('Tenant identifier for this credential'), ) owner_role = ImplicitRoleField( - role_name='Credential Owner', - role_description='Owner of the credential', parent_role=[ 'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ], ) auditor_role = ImplicitRoleField( - role_name='Credential Auditor', - role_description='Auditor of the credential', parent_role=[ 'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, ], ) use_role = ImplicitRoleField( - role_name='Credential User', - role_description='May use this credential, but not read sensitive portions or modify it', parent_role=['owner_role'] ) read_role = ImplicitRoleField( - role_name='Credential REad', - role_description='May read this credential', parent_role=[ 'use_role', 'auditor_role', 'owner_role' ], diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index d192825ec7..ac9a8840cf 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -97,39 +97,25 @@ class Inventory(CommonModel, ResourceMixin): help_text=_('Number of external inventory sources in this inventory with failures.'), ) admin_role = ImplicitRoleField( - role_name='Inventory Administrator', - role_description='May manage this inventory', parent_role='organization.admin_role', ) auditor_role = ImplicitRoleField( - role_name='Inventory Auditor', - role_description='May view but not modify this inventory', parent_role='organization.auditor_role', ) update_role = ImplicitRoleField( - role_name='Inventory Updater', - role_description='May update the inventory', parent_role=['admin_role'], ) use_role = ImplicitRoleField( - role_name='Inventory User', - role_description='May use this inventory, but not read sensitive portions or modify it', parent_role=['admin_role'], ) adhoc_role = ImplicitRoleField( - role_name='Inventory Ad Hoc', - role_description='May execute ad hoc commands against this inventory', parent_role=['admin_role'], ) execute_role = ImplicitRoleField( - role_name='Inventory Executor', - role_description='May execute jobs against this inventory', parent_role='adhoc_role', ) read_role = ImplicitRoleField( - role_name='Read', parent_role=['auditor_role', 'execute_role', 'update_role', 'use_role', 'admin_role'], - role_description='May view this inventory', ) def get_absolute_url(self): @@ -531,28 +517,21 @@ class Group(CommonModelNameNotUnique, ResourceMixin): help_text=_('Inventory source(s) that created or modified this group.'), ) admin_role = ImplicitRoleField( - role_name='Inventory Group Administrator', parent_role=['inventory.admin_role', 'parents.admin_role'], ) auditor_role = ImplicitRoleField( - role_name='Inventory Group Auditor', parent_role=['inventory.auditor_role', 'parents.auditor_role'], ) update_role = ImplicitRoleField( - role_name='Inventory Group Updater', parent_role=['inventory.update_role', 'parents.update_role', 'admin_role'], ) adhoc_role = ImplicitRoleField( - role_name='Inventory Ad Hoc', parent_role=['inventory.adhoc_role', 'parents.adhoc_role', 'admin_role'], - role_description='May execute ad hoc commands against this inventory', ) execute_role = ImplicitRoleField( - role_name='Inventory Group Executor', parent_role=['inventory.execute_role', 'parents.execute_role', 'adhoc_role'], ) read_role = ImplicitRoleField( - role_name='Inventory Group Executor', parent_role=['execute_role', 'update_role', 'auditor_role', 'admin_role'], ) @@ -1321,25 +1300,15 @@ class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin): ) admin_role = ImplicitRoleField( - role_name='CustomInventory Administrator', - role_description='May manage this inventory', parent_role='organization.admin_role', ) - member_role = ImplicitRoleField( - role_name='CustomInventory Member', - role_description='May view but not modify this inventory', parent_role='organization.member_role', ) - auditor_role = ImplicitRoleField( - role_name='CustomInventory Auditor', - role_description='May view but not modify this inventory', parent_role='organization.auditor_role', ) read_role = ImplicitRoleField( - role_name='CustomInventory Read', - role_description='May view but not modify this inventory', parent_role=['auditor_role', 'member_role', 'admin_role'], ) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index c48007e24a..c64b1e1f8b 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -226,23 +226,15 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin): default={}, ) admin_role = ImplicitRoleField( - role_name='Job Template Administrator', - role_description='Full access to all settings', parent_role=[('project.admin_role', 'inventory.admin_role')] ) auditor_role = ImplicitRoleField( - role_name='Job Template Auditor', - role_description='Read-only access to all settings', parent_role=[('project.auditor_role', 'inventory.auditor_role')] ) execute_role = ImplicitRoleField( - role_name='Job Template Runner', - role_description='May run the job template', parent_role=['admin_role'], ) read_role = ImplicitRoleField( - role_name='Job Template Runner', - role_description='May run the job template', parent_role=['execute_role', 'auditor_role', 'admin_role'], ) diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index 571f9117ab..3d8a06f446 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -53,23 +53,15 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin): related_name='deprecated_organizations', ) admin_role = ImplicitRoleField( - role_name='Organization Administrator', - role_description='May manage all aspects of this organization', parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ) auditor_role = ImplicitRoleField( - role_name='Organization Auditor', - role_description='May read all settings associated with this organization', parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, ) member_role = ImplicitRoleField( - role_name='Organization Member', - role_description='A member of this organization', parent_role='admin_role', ) read_role = ImplicitRoleField( - role_name='Organization Read Access', - role_description='Read an organization', parent_role=['member_role', 'auditor_role'], ) @@ -110,22 +102,13 @@ class Team(CommonModelNameNotUnique, ResourceMixin): related_name='deprecated_teams', ) admin_role = ImplicitRoleField( - role_name='Team Administrator', - role_description='May manage this team', parent_role='organization.admin_role', ) auditor_role = ImplicitRoleField( - role_name='Team Auditor', - role_description='May read all settings associated with this team', parent_role='organization.auditor_role', ) - member_role = ImplicitRoleField( - role_name='Team Member', - role_description='A member of this team', - ) + member_role = ImplicitRoleField() read_role = ImplicitRoleField( - role_name='Read', - role_description='Can view this team', parent_role=['admin_role', 'auditor_role', 'member_role'], ) diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 12357fca2e..41145821f4 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -221,34 +221,24 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin): blank=True, ) admin_role = ImplicitRoleField( - role_name='Project Administrator', - role_description='May manage this project', parent_role=[ 'organization.admin_role', 'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ], ) auditor_role = ImplicitRoleField( - role_name='Project Auditor', - role_description='May read all settings associated with this project', parent_role=[ 'organization.auditor_role', 'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, ], ) member_role = ImplicitRoleField( - role_name='Project Member', - role_description='Implies membership within this project', parent_role='admin_role', ) scm_update_role = ImplicitRoleField( - role_name='Project Updater', - role_description='May update this project from the source control management system', parent_role='admin_role', ) read_role = ImplicitRoleField( - role_name='Project Read Access', - role_description='Read access to this project', parent_role=['member_role', 'auditor_role', 'scm_update_role'], ) diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index f757dc580e..c2cdca2f92 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -29,8 +29,39 @@ __all__ = [ logger = logging.getLogger('awx.main.models.rbac') -ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator' -ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor' +ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='system_administrator' +ROLE_SINGLETON_SYSTEM_AUDITOR='system_auditor' + +role_names = { + 'system_administrator' : 'System Administrator', + 'system_auditor' : 'System Auditor', + 'adhoc_role' : 'Ad Hoc', + 'admin_role' : 'Admin', + 'auditor_role' : 'Auditor', + 'execute_role' : 'Execute', + 'member_role' : 'Member', + 'owner_role' : 'Owner', + 'read_role' : 'Read', + 'scm_update_role' : 'SCM Update', + 'update_role' : 'Update', + 'use_role' : 'Use', +} + +role_descriptions = { + 'system_administrator' : '[TODO] System Administrator', + 'system_auditor' : '[TODO] System Auditor', + 'adhoc_role' : '[TODO] Ad Hoc', + 'admin_role' : '[TODO] Admin', + 'auditor_role' : '[TODO] Auditor', + 'execute_role' : '[TODO] Execute', + 'member_role' : '[TODO] Member', + 'owner_role' : '[TODO] Owner', + 'read_role' : '[TODO] Read', + 'scm_update_role' : '[TODO] SCM Update', + 'update_role' : '[TODO] Update', + 'use_role' : '[TODO] Use', +} + tls = threading.local() # thread local storage @@ -67,7 +98,7 @@ def batch_role_ancestor_rebuilding(allow_nesting=False): delattr(tls, 'roles_needing_rebuilding') -class Role(CommonModelNameNotUnique): +class Role(models.Model): ''' Role model ''' @@ -77,8 +108,8 @@ class Role(CommonModelNameNotUnique): verbose_name_plural = _('roles') db_table = 'main_rbac_roles' + role_field = models.TextField(null=False) singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True) - role_field = models.TextField(null=False, default='') parents = models.ManyToManyField('Role', related_name='children') implicit_parents = models.TextField(null=False, default='[]') ancestors = models.ManyToManyField( @@ -123,6 +154,17 @@ class Role(CommonModelNameNotUnique): ''' Role._simultaneous_ancestry_rebuild([self.id]) + @property + def name(self): + global role_names + return role_names[self.role_field] + + @property + def description(self): + global role_descriptions + return role_descriptions[self.role_field] + + @staticmethod def _simultaneous_ancestry_rebuild(role_ids_to_rebuild): @@ -226,12 +268,18 @@ class Role(CommonModelNameNotUnique): 'roles_table': Role._meta.db_table, } + # SQLlite has a 1M sql statement limit.. since the django sqllite + # driver isn't letting us pass in the ids through the preferred + # parameter binding system, this function exists to obey this. + # est max 12 bytes per number, used up to 3 times in a query, + # minus 4k of padding for the other parts of the query, leads us + # to the magic number of 20748, or 20500 for a nice round number def split_ids_for_sqlite(role_ids): - for i in xrange(0, len(role_ids), 999): - yield role_ids[i:i + 999] + for i in xrange(0, len(role_ids), 20500): + yield role_ids[i:i + 20500] while role_ids_to_rebuild: - if loop_ct > 1000: + if loop_ct > 100: raise Exception('Ancestry role rebuilding error: infinite loop detected') loop_ct += 1 @@ -313,7 +361,7 @@ class Role(CommonModelNameNotUnique): @staticmethod def singleton(name): - role, _ = Role.objects.get_or_create(singleton_name=name, name=name) + role, _ = Role.objects.get_or_create(singleton_name=name, role_field=name) return role def is_ancestor_of(self, role): diff --git a/awx/main/signals.py b/awx/main/signals.py index e4893d34c2..ec1e80869b 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -127,11 +127,10 @@ def create_user_role(instance, **kwargs): Role.objects.get( content_type=ContentType.objects.get_for_model(instance), object_id=instance.id, - name = 'User Admin' + role_field='admin_role' ) except Role.DoesNotExist: role = Role.objects.create( - name = 'User Admin', role_field='admin_role', content_object = instance, ) diff --git a/awx/main/tests/functional/api/test_resource_access_lists.py b/awx/main/tests/functional/api/test_resource_access_lists.py index 75e55fd8ca..9d8d95c98a 100644 --- a/awx/main/tests/functional/api/test_resource_access_lists.py +++ b/awx/main/tests/functional/api/test_resource_access_lists.py @@ -1,6 +1,7 @@ import pytest from django.core.urlresolvers import reverse +from awx.main.models import Role @pytest.mark.django_db def test_indirect_access_list(get, organization, project, team_factory, user, admin): @@ -53,5 +54,5 @@ def test_indirect_access_list(get, organization, project, team_factory, user, ad assert org_admin_team_member_entry['team_name'] == org_admin_team.name admin_entry = admin_res['summary_fields']['indirect_access'][0]['role'] - assert admin_entry['name'] == 'System Administrator' + assert admin_entry['name'] == Role.singleton('system_administrator').name diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 4b481895d6..33c24f3cc6 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -36,7 +36,6 @@ from awx.main.models.organization import ( Team, ) -from awx.main.models.rbac import Role from awx.main.models.notifications import Notifier ''' @@ -193,11 +192,6 @@ def notifier(organization): notification_type="webhook", notification_configuration=dict(url="http://localhost", headers={"Test": "Header"})) - -@pytest.fixture -def role(): - return Role.objects.create(name='role') - @pytest.fixture def admin(user): return user('admin', True) diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index 3e080c8453..9c1fd34356 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -10,6 +10,10 @@ def mock_feature_enabled(feature, bypass_database=None): #@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) +@pytest.fixture +def role(): + return Role.objects.create() + # # /roles @@ -85,7 +89,7 @@ def test_get_user_roles_list(get, admin): response = get(url, admin) assert response.status_code == 200 roles = response.data - assert roles['count'] > 0 # 'System Administrator' role if nothing else + assert roles['count'] > 0 # 'system_administrator' role if nothing else @pytest.mark.django_db def test_user_view_other_user_roles(organization, inventory, team, get, alice, bob): diff --git a/awx/main/tests/functional/test_rbac_core.py b/awx/main/tests/functional/test_rbac_core.py index 537052afd2..8001cb6d71 100644 --- a/awx/main/tests/functional/test_rbac_core.py +++ b/awx/main/tests/functional/test_rbac_core.py @@ -11,8 +11,8 @@ from awx.main.models import ( @pytest.mark.django_db def test_auto_inheritance_by_children(organization, alice): - A = Role.objects.create(name='A', role_field='') - B = Role.objects.create(name='B', role_field='') + A = Role.objects.create() + B = Role.objects.create() A.members.add(alice) assert alice not in organization.admin_role @@ -38,8 +38,8 @@ def test_auto_inheritance_by_children(organization, alice): @pytest.mark.django_db def test_auto_inheritance_by_parents(organization, alice): - A = Role.objects.create(name='A') - B = Role.objects.create(name='B') + A = Role.objects.create() + B = Role.objects.create() A.members.add(alice) assert alice not in organization.admin_role @@ -58,9 +58,9 @@ def test_auto_inheritance_by_parents(organization, alice): @pytest.mark.django_db def test_accessible_objects(organization, alice, bob): - A = Role.objects.create(name='A') + A = Role.objects.create() A.members.add(alice) - B = Role.objects.create(name='B') + B = Role.objects.create() B.members.add(alice) B.members.add(bob) @@ -118,7 +118,7 @@ def test_auto_field_adjustments(organization, inventory, team, alice): def test_implicit_deletes(alice): 'Ensures implicit resources and roles delete themselves' delorg = Organization.objects.create(name='test-org') - child = Role.objects.create(name='child-role') + child = Role.objects.create() child.parents.add(delorg.admin_role) delorg.admin_role.members.add(alice) @@ -129,14 +129,14 @@ def test_implicit_deletes(alice): assert Role.objects.filter(id=admin_role_id).count() == 1 assert Role.objects.filter(id=auditor_role_id).count() == 1 n_alice_roles = alice.roles.count() - n_system_admin_children = Role.singleton('System Administrator').children.count() + n_system_admin_children = Role.singleton('system_administrator').children.count() delorg.delete() assert Role.objects.filter(id=admin_role_id).count() == 0 assert Role.objects.filter(id=auditor_role_id).count() == 0 assert alice.roles.count() == (n_alice_roles - 1) - assert Role.singleton('System Administrator').children.count() == (n_system_admin_children - 1) + assert Role.singleton('system_administrator').children.count() == (n_system_admin_children - 1) assert child.ancestors.count() == 1 assert child.ancestors.all()[0] == child @@ -152,11 +152,11 @@ def test_content_object(user): def test_hierarchy_rebuilding_multi_path(): 'Tests a subdtle cases around role hierarchy rebuilding when you have multiple paths to the same role of different length' - X = Role.objects.create(name='X') - A = Role.objects.create(name='A') - B = Role.objects.create(name='B') - C = Role.objects.create(name='C') - D = Role.objects.create(name='D') + X = Role.objects.create() + A = Role.objects.create() + B = Role.objects.create() + C = Role.objects.create() + D = Role.objects.create() A.children.add(B) A.children.add(D) diff --git a/awx/main/tests/functional/test_rbac_project.py b/awx/main/tests/functional/test_rbac_project.py index d2e504645a..a225154d21 100644 --- a/awx/main/tests/functional/test_rbac_project.py +++ b/awx/main/tests/functional/test_rbac_project.py @@ -148,7 +148,7 @@ def test_project_user_project(user_project, project, user): def test_project_accessible_by_sa(user, project): u = user('systemadmin', is_superuser=True) # This gets setup by a signal, but we want to test the migration which will set this up too, so remove it - Role.singleton('System Administrator').members.remove(u) + Role.singleton('system_administrator').members.remove(u) assert u not in project.read_role rbac.migrate_organization(apps, None) diff --git a/awx/main/tests/functional/test_rbac_user.py b/awx/main/tests/functional/test_rbac_user.py index 8e620771f5..c5959a2c32 100644 --- a/awx/main/tests/functional/test_rbac_user.py +++ b/awx/main/tests/functional/test_rbac_user.py @@ -13,7 +13,7 @@ def test_user_admin(user_project, project, user): joe = user(username, is_superuser = False) admin = user('admin', is_superuser = True) - sa = Role.singleton('System Administrator') + sa = Role.singleton('system_administrator') # this should happen automatically with our signal assert sa.members.filter(id=admin.id).exists() is True From ee97ef2fc8d0e486dcec8b1cf58234c8f8a64d0b Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Wed, 20 Apr 2016 23:01:28 -0400 Subject: [PATCH 4/8] Fix 0008 field addition orderings --- awx/main/migrations/0008_v300_rbac_changes.py | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/awx/main/migrations/0008_v300_rbac_changes.py b/awx/main/migrations/0008_v300_rbac_changes.py index a704d37425..6cddf8fa23 100644 --- a/awx/main/migrations/0008_v300_rbac_changes.py +++ b/awx/main/migrations/0008_v300_rbac_changes.py @@ -100,7 +100,6 @@ class Migration(migrations.Migration): ('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)), ('parents', models.ManyToManyField(related_name='children', to='main.Role')), ('implicit_parents', models.TextField(default=b'[]')), - ('ancestors', models.ManyToManyField(related_name='descendents', through='main.RoleAncestorEntry', to='main.Role')), ('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)), ('object_id', models.PositiveIntegerField(default=None, null=True)), @@ -125,6 +124,11 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'role_ancestors', }, ), + migrations.AddField( + model_name='role', + name='ancestors', + field=models.ManyToManyField(related_name='descendents', through='main.RoleAncestorEntry', to='main.Role'), + ), migrations.AlterIndexTogether( name='roleancestorentry', index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field')]), @@ -141,13 +145,13 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='credential', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'use_role', b'auditor_role', b'owner_role'], to='main.Role', null=b'True'), + name='use_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'owner_role'], to='main.Role', null=b'True'), ), migrations.AddField( model_name='credential', - name='use_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'owner_role'], to='main.Role', null=b'True'), + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'use_role', b'auditor_role', b'owner_role'], to='main.Role', null=b'True'), ), migrations.AddField( model_name='custominventoryscript', @@ -171,13 +175,13 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='group', - name='adhoc_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.adhoc_role', b'parents.adhoc_role', b'admin_role'], to='main.Role', null=b'True'), + name='admin_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', null=b'True'), ), migrations.AddField( model_name='group', - name='admin_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', null=b'True'), + name='adhoc_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.adhoc_role', b'parents.adhoc_role', b'admin_role'], to='main.Role', null=b'True'), ), migrations.AddField( model_name='group', @@ -189,26 +193,26 @@ class Migration(migrations.Migration): name='execute_role', field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.execute_role', b'parents.execute_role', b'adhoc_role'], to='main.Role', null=b'True'), ), - migrations.AddField( - model_name='group', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'execute_role', b'update_role', b'auditor_role', b'admin_role'], to='main.Role', null=b'True'), - ), migrations.AddField( model_name='group', name='update_role', field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.update_role', b'parents.update_role', b'admin_role'], to='main.Role', null=b'True'), ), migrations.AddField( - model_name='inventory', - name='adhoc_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), + model_name='group', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'execute_role', b'update_role', b'auditor_role', b'admin_role'], to='main.Role', null=b'True'), ), migrations.AddField( model_name='inventory', name='admin_role', field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.admin_role', to='main.Role', null=b'True'), ), + migrations.AddField( + model_name='inventory', + name='adhoc_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), + ), migrations.AddField( model_name='inventory', name='auditor_role', @@ -219,11 +223,6 @@ class Migration(migrations.Migration): name='execute_role', field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'adhoc_role', to='main.Role', null=b'True'), ), - migrations.AddField( - model_name='inventory', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'auditor_role', b'execute_role', b'update_role', b'use_role', b'admin_role'], to='main.Role', null=b'True'), - ), migrations.AddField( model_name='inventory', name='update_role', @@ -234,6 +233,11 @@ class Migration(migrations.Migration): name='use_role', field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'), ), + migrations.AddField( + model_name='inventory', + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'auditor_role', b'execute_role', b'update_role', b'use_role', b'admin_role'], to='main.Role', null=b'True'), + ), migrations.AddField( model_name='jobtemplate', name='admin_role', @@ -291,13 +295,13 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='project', - name='read_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'member_role', b'auditor_role', b'scm_update_role'], to='main.Role', null=b'True'), + name='scm_update_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'), ), migrations.AddField( model_name='project', - name='scm_update_role', - field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'), + name='read_role', + field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'member_role', b'auditor_role', b'scm_update_role'], to='main.Role', null=b'True'), ), migrations.AddField( model_name='team', From 0c6dcb2337b131b5071303fa2147411bc9cad460 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Thu, 21 Apr 2016 16:32:16 -0400 Subject: [PATCH 5/8] Optimized our simultaneous role ancestry rebuilding method --- awx/main/migrations/0008_v300_rbac_changes.py | 2 +- awx/main/models/rbac.py | 189 ++++++++---------- 2 files changed, 89 insertions(+), 102 deletions(-) diff --git a/awx/main/migrations/0008_v300_rbac_changes.py b/awx/main/migrations/0008_v300_rbac_changes.py index 6cddf8fa23..896e81558a 100644 --- a/awx/main/migrations/0008_v300_rbac_changes.py +++ b/awx/main/migrations/0008_v300_rbac_changes.py @@ -131,7 +131,7 @@ class Migration(migrations.Migration): ), migrations.AlterIndexTogether( name='roleancestorentry', - index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field')]), + index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field'), ('ancestor', 'descendent')]), ), migrations.AddField( model_name='credential', diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index c2cdca2f92..03ea2d8e6c 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -164,10 +164,20 @@ class Role(models.Model): global role_descriptions return role_descriptions[self.role_field] - - @staticmethod def _simultaneous_ancestry_rebuild(role_ids_to_rebuild): + # The ancestry table + # ================================================= + # + # The role ancestors table denormalizes the parental relations + # between all roles in the system. If you have role A which is a + # parent of B which is a parent of C, then the ancestors table will + # contain a row noting that B is a descendent of A, and two rows for + # denoting that C is a descendent of both A and B. In addition to + # storing entries for each descendent relationship, we also store an + # entry that states that C is a 'descendent' of itself, C. This makes + # usage of this table simple in our queries as it enables us to do + # straight joins where we would have to do unions otherwise. # # The simple version of what this function is doing # ================================================= @@ -205,37 +215,18 @@ class Role(models.Model): # # SQL Breakdown # ============= - # The Role ancestors has three columns, (id, from_role_id, to_role_id) - # - # id: Unqiue row ID - # from_role_id: Descendent role ID - # to_role_id: Ancestor role ID - # - # *NOTE* In addition to mapping roles to parents, there also - # always exists must exist an entry where - # - # from_role_id == role_id == to_role_id - # - # this makes our joins simple when we go to derive permissions or - # accessible objects. - # - # # We operate under the assumption that our parent's ancestor list is # correct, thus we can always compute what our ancestor list should # be by taking the union of our parent's ancestor lists and adding - # our self reference entry from_role_id == role_id == to_role_id + # our self reference entry where ancestor_id = descendent_id # - # The inner query for the two SQL statements compute this union, - # the union of the parent's ancestors and the self referncing entry, - # for all roles in the current set of roles to rebuild. + # The DELETE query deletes all entries in the ancestor table that + # should no longer be there (as determined by the NOT EXISTS query, + # which checks to see if the ancestor is still an ancestor of one + # or more of our parents) # - # The DELETE query uses this to select all entries on disk for the - # roles we're dealing with, and removes the entries that are not in - # this list. - # - # The INSERT query uses this to select all entries in the list that - # are not in the database yet, and inserts all of the missing - # records. + # The INSERT query computes the list of what our ancestor maps should + # be, and inserts any missing entries. # # Once complete, we select all of the children for the roles we are # working with, this list becomes the new role list we are working @@ -258,7 +249,6 @@ class Role(models.Model): roles_needing_rebuilding.update(set(role_ids_to_rebuild)) return - cursor = connection.cursor() loop_ct = 0 @@ -271,88 +261,84 @@ class Role(models.Model): # SQLlite has a 1M sql statement limit.. since the django sqllite # driver isn't letting us pass in the ids through the preferred # parameter binding system, this function exists to obey this. - # est max 12 bytes per number, used up to 3 times in a query, + # est max 12 bytes per number, used up to 2 times in a query, # minus 4k of padding for the other parts of the query, leads us - # to the magic number of 20748, or 20500 for a nice round number + # to the magic number of 41496, or 40000 for a nice round number def split_ids_for_sqlite(role_ids): - for i in xrange(0, len(role_ids), 20500): - yield role_ids[i:i + 20500] + for i in xrange(0, len(role_ids), 40000): + yield role_ids[i:i + 40000] - while role_ids_to_rebuild: - if loop_ct > 100: - raise Exception('Ancestry role rebuilding error: infinite loop detected') - loop_ct += 1 + with transaction.atomic(): + while role_ids_to_rebuild: + if loop_ct > 100: + raise Exception('Role ancestry rebuilding error: infinite loop detected') + loop_ct += 1 - delete_ct = 0 - for ids in split_ids_for_sqlite(role_ids_to_rebuild): - sql_params['ids'] = ','.join(str(x) for x in ids) - cursor.execute(''' - DELETE FROM %(ancestors_table)s - WHERE descendent_id IN (%(ids)s) - AND - id NOT IN ( - SELECT %(ancestors_table)s.id FROM ( - SELECT parents.from_role_id from_id, ancestors.ancestor_id to_id - FROM %(parents_table)s as parents - LEFT JOIN %(ancestors_table)s as ancestors - ON (parents.to_role_id = ancestors.descendent_id) - WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL + delete_ct = 0 + for ids in split_ids_for_sqlite(role_ids_to_rebuild): + sql_params['ids'] = ','.join(str(x) for x in ids) + cursor.execute(''' + DELETE FROM %(ancestors_table)s + WHERE descendent_id IN (%(ids)s) + AND descendent_id != ancestor_id + AND NOT EXISTS ( + SELECT 1 + FROM %(parents_table)s as parents + INNER JOIN %(ancestors_table)s as inner_ancestors + ON (parents.to_role_id = inner_ancestors.descendent_id) + WHERE parents.from_role_id = %(ancestors_table)s.descendent_id + AND %(ancestors_table)s.ancestor_id = inner_ancestors.ancestor_id + ) + ''' % sql_params) - UNION + delete_ct += cursor.rowcount - SELECT id from_id, id to_id from %(roles_table)s WHERE id IN (%(ids)s) - ) new_ancestry_list - LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id - AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id) - WHERE %(ancestors_table)s.id IS NOT NULL + insert_ct = 0 + for ids in split_ids_for_sqlite(role_ids_to_rebuild): + sql_params['ids'] = ','.join(str(x) for x in ids) + cursor.execute(''' + INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id) + SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM ( + SELECT roles.id from_id, + ancestors.ancestor_id to_id, + roles.role_field, + COALESCE(roles.content_type_id, 0) content_type_id, + COALESCE(roles.object_id, 0) object_id + FROM %(roles_table)s as roles + INNER JOIN %(parents_table)s as parents + ON (parents.from_role_id = roles.id) + INNER JOIN %(ancestors_table)s as ancestors + ON (parents.to_role_id = ancestors.descendent_id) + WHERE roles.id IN (%(ids)s) + + UNION + + SELECT id from_id, + id to_id, + role_field, + COALESCE(content_type_id, 0) content_type_id, + COALESCE(object_id, 0) object_id + from %(roles_table)s WHERE id IN (%(ids)s) + ) new_ancestry_list + WHERE NOT EXISTS ( + SELECT 1 FROM %(ancestors_table)s + WHERE %(ancestors_table)s.descendent_id = new_ancestry_list.from_id + AND %(ancestors_table)s.ancestor_id = new_ancestry_list.to_id ) - ''' % sql_params) - delete_ct += cursor.rowcount - insert_ct = 0 - for ids in split_ids_for_sqlite(role_ids_to_rebuild): - sql_params['ids'] = ','.join(str(x) for x in ids) - cursor.execute(''' - INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id) - SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM ( - SELECT parents.from_role_id from_id, - ancestors.ancestor_id to_id, - roles.role_field, - COALESCE(roles.content_type_id, 0) content_type_id, - COALESCE(roles.object_id, 0) object_id - FROM %(parents_table)s as parents - INNER JOIN %(roles_table)s as roles ON (parents.from_role_id = roles.id) - LEFT OUTER JOIN %(ancestors_table)s as ancestors - ON (parents.to_role_id = ancestors.descendent_id) - WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL - - UNION - - SELECT id from_id, - id to_id, - role_field, - COALESCE(content_type_id, 0) content_type_id, - COALESCE(object_id, 0) object_id - from %(roles_table)s WHERE id IN (%(ids)s) - ) new_ancestry_list - LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id - AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id) - WHERE %(ancestors_table)s.id IS NULL - ''' % sql_params) - insert_ct += cursor.rowcount - - if insert_ct == 0 and delete_ct == 0: - break - - new_role_ids_to_rebuild = set() - for ids in split_ids_for_sqlite(role_ids_to_rebuild): - sql_params['ids'] = ','.join(str(x) for x in ids) - new_role_ids_to_rebuild.update(set(Role.objects.distinct() - .filter(id__in=ids, children__id__isnull=False) - .values_list('children__id', flat=True))) - role_ids_to_rebuild = list(new_role_ids_to_rebuild) + ''' % sql_params) + insert_ct += cursor.rowcount + if insert_ct == 0 and delete_ct == 0: + break + new_role_ids_to_rebuild = set() + for ids in split_ids_for_sqlite(role_ids_to_rebuild): + sql_params['ids'] = ','.join(str(x) for x in ids) + # get all children for the roles we're operating on + cursor.execute('SELECT DISTINCT from_role_id FROM %(parents_table)s WHERE to_role_id IN (%(ids)s)' % sql_params) + new_role_ids_to_rebuild.update([row[0] for row in cursor.fetchall()]) + role_ids_to_rebuild = list(new_role_ids_to_rebuild) @staticmethod @@ -376,6 +362,7 @@ class RoleAncestorEntry(models.Model): index_together = [ ("ancestor", "content_type_id", "object_id"), # used by get_roles_on_resource ("ancestor", "content_type_id", "role_field"), # used by accessible_objects + ("ancestor", "descendent"), # used by _simultaneous_ancestry_rebuild in the NOT EXISTS clauses. ] descendent = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+') From 569f61ed30d358cce51041ebeef8299354101e2a Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Thu, 21 Apr 2016 17:04:09 -0400 Subject: [PATCH 6/8] Fixed swapped transaction/rebuild statements --- tools/data_generators/rbac_dummy_data_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/data_generators/rbac_dummy_data_generator.py b/tools/data_generators/rbac_dummy_data_generator.py index 5a0df82da5..cf26cb3da3 100755 --- a/tools/data_generators/rbac_dummy_data_generator.py +++ b/tools/data_generators/rbac_dummy_data_generator.py @@ -114,8 +114,8 @@ class Rollback(Exception): try: - with batch_role_ancestor_rebuilding(): - with transaction.atomic(): + with transaction.atomic(): + with batch_role_ancestor_rebuilding(): admin, _ = User.objects.get_or_create(username = 'admin', is_superuser=True) org_admin, _ = User.objects.get_or_create(username = 'org_admin') org_member, _ = User.objects.get_or_create(username = 'org_member') From 17120ffe4f3b96892a42c153fd9c98ea93156fe7 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Thu, 21 Apr 2016 17:05:00 -0400 Subject: [PATCH 7/8] Futher optimze role rebuilding to be aware of whether we are adding or removing parentage --- awx/main/fields.py | 4 +- awx/main/models/rbac.py | 159 +++++++++++++++++++++------------------- awx/main/signals.py | 13 +++- 3 files changed, 93 insertions(+), 83 deletions(-) diff --git a/awx/main/fields.py b/awx/main/fields.py index 568126c536..ccc2068514 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -200,7 +200,7 @@ class ImplicitRoleField(models.ForeignKey): updates[role.role_field] = role.id role_ids.append(role.id) type(instance).objects.filter(pk=instance.pk).update(**updates) - Role_._simultaneous_ancestry_rebuild(role_ids) + Role_.rebuild_role_ancestor_list(role_ids, []) # Update parentage if necessary for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'): @@ -256,4 +256,4 @@ class ImplicitRoleField(models.ForeignKey): Role_ = get_current_apps().get_model('main', 'Role') child_ids = [x for x in Role_.parents.through.objects.filter(to_role_id__in=role_ids).distinct().values_list('from_role_id', flat=True)] Role_.objects.filter(id__in=role_ids).delete() - Role_._simultaneous_ancestry_rebuild(child_ids) + Role_.rebuild_role_ancestor_list([], child_ids) diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 03ea2d8e6c..64e748a20a 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -82,20 +82,19 @@ def batch_role_ancestor_rebuilding(allow_nesting=False): try: setattr(tls, 'batch_role_rebuilding', True) if not batch_role_rebuilding: - setattr(tls, 'roles_needing_rebuilding', set()) + setattr(tls, 'additions', set()) + setattr(tls, 'removals', set()) yield finally: setattr(tls, 'batch_role_rebuilding', batch_role_rebuilding) if not batch_role_rebuilding: - rebuild_set = getattr(tls, 'roles_needing_rebuilding') + additions = getattr(tls, 'additions') + removals = getattr(tls, 'removals') with transaction.atomic(): - Role._simultaneous_ancestry_rebuild(list(rebuild_set)) - - #for role in Role.objects.filter(id__in=list(rebuild_set)).all(): - # # TODO: We can reduce this to one rebuild call with our new upcoming rebuild method.. do this - # role.rebuild_role_ancestor_list() - delattr(tls, 'roles_needing_rebuilding') + Role.rebuild_role_ancestor_list(list(additions), list(removals)) + delattr(tls, 'additions') + delattr(tls, 'removals') class Role(models.Model): @@ -125,7 +124,7 @@ class Role(models.Model): def save(self, *args, **kwargs): super(Role, self).save(*args, **kwargs) - self.rebuild_role_ancestor_list() + self.rebuild_role_ancestor_list([self.id], []) def get_absolute_url(self): return reverse('api:role_detail', args=(self.pk,)) @@ -143,17 +142,6 @@ class Role(models.Model): object_id=accessor.id) return self.ancestors.filter(pk__in=roles).exists() - def rebuild_role_ancestor_list(self): - ''' - Updates our `ancestors` map to accurately reflect all of the ancestors for a role - - You should never need to call this. Signal handlers should be calling - this method when the role hierachy changes automatically. - - Note that this method relies on any parents' ancestor list being correct. - ''' - Role._simultaneous_ancestry_rebuild([self.id]) - @property def name(self): global role_names @@ -165,7 +153,13 @@ class Role(models.Model): return role_descriptions[self.role_field] @staticmethod - def _simultaneous_ancestry_rebuild(role_ids_to_rebuild): + def rebuild_role_ancestor_list(additions, removals): + ''' + Updates our `ancestors` map to accurately reflect all of the ancestors for a role + + You should never need to call this. Signal handlers should be calling + this method when the role hierachy changes automatically. + ''' # The ancestry table # ================================================= # @@ -238,15 +232,15 @@ class Role(models.Model): # # - if len(role_ids_to_rebuild) == 0: + if len(additions) == 0 and len(removals) == 0: return global tls batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False) if batch_role_rebuilding: - roles_needing_rebuilding = getattr(tls, 'roles_needing_rebuilding') - roles_needing_rebuilding.update(set(role_ids_to_rebuild)) + getattr(tls, 'additions').update(set(additions)) + getattr(tls, 'removals').update(set(removals)) return cursor = connection.cursor() @@ -268,77 +262,88 @@ class Role(models.Model): for i in xrange(0, len(role_ids), 40000): yield role_ids[i:i + 40000] + with transaction.atomic(): - while role_ids_to_rebuild: + while len(additions) > 0 or len(removals) > 0: if loop_ct > 100: raise Exception('Role ancestry rebuilding error: infinite loop detected') loop_ct += 1 delete_ct = 0 - for ids in split_ids_for_sqlite(role_ids_to_rebuild): - sql_params['ids'] = ','.join(str(x) for x in ids) - cursor.execute(''' - DELETE FROM %(ancestors_table)s - WHERE descendent_id IN (%(ids)s) - AND descendent_id != ancestor_id - AND NOT EXISTS ( - SELECT 1 - FROM %(parents_table)s as parents - INNER JOIN %(ancestors_table)s as inner_ancestors - ON (parents.to_role_id = inner_ancestors.descendent_id) - WHERE parents.from_role_id = %(ancestors_table)s.descendent_id - AND %(ancestors_table)s.ancestor_id = inner_ancestors.ancestor_id - ) - ''' % sql_params) + if len(removals) > 0: + for ids in split_ids_for_sqlite(removals): + sql_params['ids'] = ','.join(str(x) for x in ids) + cursor.execute(''' + DELETE FROM %(ancestors_table)s + WHERE descendent_id IN (%(ids)s) + AND descendent_id != ancestor_id + AND NOT EXISTS ( + SELECT 1 + FROM %(parents_table)s as parents + INNER JOIN %(ancestors_table)s as inner_ancestors + ON (parents.to_role_id = inner_ancestors.descendent_id) + WHERE parents.from_role_id = %(ancestors_table)s.descendent_id + AND %(ancestors_table)s.ancestor_id = inner_ancestors.ancestor_id + ) + ''' % sql_params) - delete_ct += cursor.rowcount + delete_ct += cursor.rowcount insert_ct = 0 - for ids in split_ids_for_sqlite(role_ids_to_rebuild): - sql_params['ids'] = ','.join(str(x) for x in ids) - cursor.execute(''' - INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id) - SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM ( - SELECT roles.id from_id, - ancestors.ancestor_id to_id, - roles.role_field, - COALESCE(roles.content_type_id, 0) content_type_id, - COALESCE(roles.object_id, 0) object_id - FROM %(roles_table)s as roles - INNER JOIN %(parents_table)s as parents - ON (parents.from_role_id = roles.id) - INNER JOIN %(ancestors_table)s as ancestors - ON (parents.to_role_id = ancestors.descendent_id) - WHERE roles.id IN (%(ids)s) + if len(additions) > 0: + for ids in split_ids_for_sqlite(additions): + sql_params['ids'] = ','.join(str(x) for x in ids) + cursor.execute(''' + INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id) + SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM ( + SELECT roles.id from_id, + ancestors.ancestor_id to_id, + roles.role_field, + COALESCE(roles.content_type_id, 0) content_type_id, + COALESCE(roles.object_id, 0) object_id + FROM %(roles_table)s as roles + INNER JOIN %(parents_table)s as parents + ON (parents.from_role_id = roles.id) + INNER JOIN %(ancestors_table)s as ancestors + ON (parents.to_role_id = ancestors.descendent_id) + WHERE roles.id IN (%(ids)s) - UNION + UNION - SELECT id from_id, - id to_id, - role_field, - COALESCE(content_type_id, 0) content_type_id, - COALESCE(object_id, 0) object_id - from %(roles_table)s WHERE id IN (%(ids)s) - ) new_ancestry_list - WHERE NOT EXISTS ( - SELECT 1 FROM %(ancestors_table)s - WHERE %(ancestors_table)s.descendent_id = new_ancestry_list.from_id - AND %(ancestors_table)s.ancestor_id = new_ancestry_list.to_id - ) + SELECT id from_id, + id to_id, + role_field, + COALESCE(content_type_id, 0) content_type_id, + COALESCE(object_id, 0) object_id + from %(roles_table)s WHERE id IN (%(ids)s) + ) new_ancestry_list + WHERE NOT EXISTS ( + SELECT 1 FROM %(ancestors_table)s + WHERE %(ancestors_table)s.descendent_id = new_ancestry_list.from_id + AND %(ancestors_table)s.ancestor_id = new_ancestry_list.to_id + ) - ''' % sql_params) - insert_ct += cursor.rowcount + ''' % sql_params) + insert_ct += cursor.rowcount if insert_ct == 0 and delete_ct == 0: break - new_role_ids_to_rebuild = set() - for ids in split_ids_for_sqlite(role_ids_to_rebuild): + new_additions = set() + for ids in split_ids_for_sqlite(additions): sql_params['ids'] = ','.join(str(x) for x in ids) # get all children for the roles we're operating on cursor.execute('SELECT DISTINCT from_role_id FROM %(parents_table)s WHERE to_role_id IN (%(ids)s)' % sql_params) - new_role_ids_to_rebuild.update([row[0] for row in cursor.fetchall()]) - role_ids_to_rebuild = list(new_role_ids_to_rebuild) + new_additions.update([row[0] for row in cursor.fetchall()]) + additions = list(new_additions) + + new_removals = set() + for ids in split_ids_for_sqlite(removals): + sql_params['ids'] = ','.join(str(x) for x in ids) + # get all children for the roles we're operating on + cursor.execute('SELECT DISTINCT from_role_id FROM %(parents_table)s WHERE to_role_id IN (%(ids)s)' % sql_params) + new_removals.update([row[0] for row in cursor.fetchall()]) + removals = list(new_removals) @staticmethod @@ -362,7 +367,7 @@ class RoleAncestorEntry(models.Model): index_together = [ ("ancestor", "content_type_id", "object_id"), # used by get_roles_on_resource ("ancestor", "content_type_id", "role_field"), # used by accessible_objects - ("ancestor", "descendent"), # used by _simultaneous_ancestry_rebuild in the NOT EXISTS clauses. + ("ancestor", "descendent"), # used by rebuild_role_ancestor_list in the NOT EXISTS clauses. ] descendent = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+') diff --git a/awx/main/signals.py b/awx/main/signals.py index ec1e80869b..068efb4ece 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -108,12 +108,17 @@ def emit_update_inventory_on_created_or_deleted(sender, **kwargs): def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwargs): 'When a role parent is added or removed, update our role hierarchy list' - if action in ['post_add', 'post_remove', 'post_clear']: + if action == 'post_add': if reverse: - for id in pk_set: - model.objects.get(id=id).rebuild_role_ancestor_list() + model.rebuild_role_ancestor_list(list(pk_set), []) else: - instance.rebuild_role_ancestor_list() + model.rebuild_role_ancestor_list([instance.id], []) + + if action in ['post_remove', 'post_clear']: + if reverse: + model.rebuild_role_ancestor_list([], list(pk_set)) + else: + model.rebuild_role_ancestor_list([], [instance.id]) def sync_superuser_status_to_rbac(instance, **kwargs): 'When the is_superuser flag is changed on a user, reflect that in the membership of the System Admnistrator role' From b1e140d83da1616d75979649f3baacf8cf580c3d Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Fri, 22 Apr 2016 10:14:43 -0400 Subject: [PATCH 8/8] flake8 fixes --- awx/main/fields.py | 1 - 1 file changed, 1 deletion(-) diff --git a/awx/main/fields.py b/awx/main/fields.py index ccc2068514..d08bbdfb14 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -18,7 +18,6 @@ from django.db.models.fields.related import ( ReverseManyRelatedObjectsDescriptor, ) from django.utils.encoding import smart_text -from django.utils.timezone import now # AWX from awx.main.models.rbac import batch_role_ancestor_rebuilding