From a5bc62d7d491ef706ffcc88f53ed887d6c85a153 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 May 2016 12:37:44 -0400 Subject: [PATCH 1/7] enforce use_role access on both inventory and project when adding a JT --- awx/main/access.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index cec0b7c2bf..68d9c94ded 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -773,7 +773,9 @@ class JobTemplateAccess(BaseAccess): inventory_pk = get_pk_from_dict(data, 'inventory') inventory = Inventory.objects.filter(id=inventory_pk) if not inventory.exists() and not data.get('ask_inventory_on_launch', False): - return False # Does this make sense? Maybe should check read access + return False + if inventory.exists() and not self.user in inventory[0].use_role: + return False project_pk = get_pk_from_dict(data, 'project') if 'job_type' in data and data['job_type'] == PERM_INVENTORY_SCAN: @@ -786,10 +788,8 @@ class JobTemplateAccess(BaseAccess): # If the user has admin access to the project (as an org admin), should # be able to proceed without additional checks. project = get_object_or_400(Project, pk=project_pk) - if self.user in project.admin_role: - return True - return self.user in project.admin_role and self.user in inventory.read_role + return self.user in project.use_role def can_start(self, obj, validate_license=True): # Check license. From 5eb67411a44366ed90a6078f29f1977013c1a39c Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 May 2016 12:38:25 -0400 Subject: [PATCH 2/7] Rebuild role hierarchy after making changes in migrations Signals don't fire in migrations, so gotta do this step manually --- awx/main/migrations/0017_v300_prompting_migrations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awx/main/migrations/0017_v300_prompting_migrations.py b/awx/main/migrations/0017_v300_prompting_migrations.py index c5a1df0eb9..6bec778956 100644 --- a/awx/main/migrations/0017_v300_prompting_migrations.py +++ b/awx/main/migrations/0017_v300_prompting_migrations.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from awx.main.migrations import _rbac as rbac from awx.main.migrations import _ask_for_variables as ask_for_variables from awx.main.migrations import _migration_utils as migration_utils from django.db import migrations @@ -15,4 +16,5 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(migration_utils.set_current_apps_for_migrations), migrations.RunPython(ask_for_variables.migrate_credential), + migrations.RunPython(rbac.rebuild_role_hierarchy), ] From 5dbce56beb2502c31f8dd233a5db549d5ae7c465 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 May 2016 12:42:51 -0400 Subject: [PATCH 3/7] When migrating, grant read_role instead of use_role appropriately Only give read access to folks that didn't have explicit permissions to use a project in a job template before. --- awx/main/migrations/_rbac.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/awx/main/migrations/_rbac.py b/awx/main/migrations/_rbac.py index e16b3575d3..4429453b2c 100644 --- a/awx/main/migrations/_rbac.py +++ b/awx/main/migrations/_rbac.py @@ -215,11 +215,11 @@ def migrate_inventory(apps, schema_editor): Inventory = apps.get_model('main', 'Inventory') Permission = apps.get_model('main', 'Permission') - def role_from_permission(): + def role_from_permission(perm): if perm.permission_type == 'admin': return inventory.admin_role elif perm.permission_type == 'read': - return inventory.read_role + return inventory.use_role elif perm.permission_type == 'write': return inventory.update_role elif perm.permission_type == 'check' or perm.permission_type == 'run' or perm.permission_type == 'create': @@ -233,7 +233,7 @@ def migrate_inventory(apps, schema_editor): role = None execrole = None - role = role_from_permission() + role = role_from_permission(perm) if role is None: raise Exception(smart_text(u'Unhandled permission type for inventory: {}'.format( perm.permission_type))) @@ -320,24 +320,30 @@ def migrate_projects(apps, schema_editor): logger.warn(smart_text(u'adding Project({}) admin: {}'.format(project.name, project.created_by.username))) for team in project.deprecated_teams.all(): - team.member_role.children.add(project.use_role) + team.member_role.children.add(project.read_role) logger.info(smart_text(u'adding Team({}) access for Project({})'.format(team.name, project.name))) - if project.organization is not None: - for user in project.organization.deprecated_users.all(): - project.use_role.members.add(user) - logger.info(smart_text(u'adding Organization({}) member access to Project({})'.format(project.organization.name, project.name))) - for perm in Permission.objects.filter(project=project): - # All perms at this level just imply a user or team can read + if perm.permission_type == 'create': + role = project.use_role + else: + role = project.read_role + if perm.team: - perm.team.member_role.children.add(project.use_role) + perm.team.member_role.children.add(role) logger.info(smart_text(u'adding Team({}) access for Project({})'.format(perm.team.name, project.name))) if perm.user: - project.use_role.members.add(perm.user) + role.members.add(perm.user) logger.info(smart_text(u'adding User({}) access for Project({})'.format(perm.user.username, project.name))) + if project.organization is not None: + for user in project.organization.deprecated_users.all(): + if not (project.use_role.members.filter(pk=user.id).exists() or project.admin_role.members.filter(pk=user.id).exists()): + project.read_role.members.add(user) + logger.info(smart_text(u'adding Organization({}) member access to Project({})'.format(project.organization.name, project.name))) + + @log_migration def migrate_job_templates(apps, schema_editor): From 8aa4df1b781884e9fb3126a0be321e389038ca35 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 May 2016 12:45:22 -0400 Subject: [PATCH 4/7] Fix job template migrations again Eliminated some incorrect wtf filtering in permissions, and fixed up the credential access checks. --- awx/main/migrations/_rbac.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/awx/main/migrations/_rbac.py b/awx/main/migrations/_rbac.py index 4429453b2c..beabcf7c1d 100644 --- a/awx/main/migrations/_rbac.py +++ b/awx/main/migrations/_rbac.py @@ -409,7 +409,7 @@ def migrate_job_templates(apps, schema_editor): team_create_permissions = set( jt_permission_qs - .filter(permission_type__in=['create'] if jt.job_type == 'check' else ['create']) + .filter(permission_type__in=['create']) .values_list('team__id', flat=True) ) team_run_permissions = set( @@ -419,12 +419,12 @@ def migrate_job_templates(apps, schema_editor): ) user_create_permissions = set( jt_permission_qs - .filter(permission_type__in=['create'] if jt.job_type == 'check' else ['run']) + .filter(permission_type__in=['create']) .values_list('user__id', flat=True) ) user_run_permissions = set( jt_permission_qs - .filter(permission_type__in=['check', 'run'] if jt.job_type == 'check' else ['create']) + .filter(permission_type__in=['check', 'run'] if jt.job_type == 'check' else ['run']) .values_list('user__id', flat=True) ) @@ -452,17 +452,20 @@ def migrate_job_templates(apps, schema_editor): logger.info(smart_text(u'transfering execute access on JobTemplate({}) to Team({})'.format(jt.name, team.name))) for user in User.objects.filter(id__in=user_create_permissions).iterator(): + cred = jt.credential or jt.cloud_credential if (jt.inventory.id in user_inv_permissions[user.id] or any([jt.inventory.id in team_inv_permissions[team.id] for team in user.deprecated_teams.all()])) and \ - ((not jt.credential and not jt.cloud_credential) or - Credential.objects.filter(Q(deprecated_user=user) | Q(deprecated_team__deprecated_users=user), jobtemplates=jt).exists()): + (not cred or cred.deprecated_user == user or + (cred.deprecated_team and cred.deprecated_team.deprecated_users.filter(pk=user.id).exists())): jt.admin_role.members.add(user) logger.info(smart_text(u'transfering admin access on JobTemplate({}) to User({})'.format(jt.name, user.username))) for user in User.objects.filter(id__in=user_run_permissions).iterator(): + cred = jt.credential or jt.cloud_credential + if (jt.inventory.id in user_inv_permissions[user.id] or any([jt.inventory.id in team_inv_permissions[team.id] for team in user.deprecated_teams.all()])) and \ - ((not jt.credential and not jt.cloud_credential) or - Credential.objects.filter(Q(deprecated_user=user) | Q(deprecated_team__deprecated_users=user), jobtemplates=jt).exists()): + (not cred or cred.deprecated_user == user or + (cred.deprecated_team and cred.deprecated_team.deprecated_users.filter(pk=user.id).exists())): jt.execute_role.members.add(user) logger.info(smart_text(u'transfering execute access on JobTemplate({}) to User({})'.format(jt.name, user.username))) From 81f093b1c09528961c2fdf5e5b3458a9ae8689dc Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 May 2016 12:46:43 -0400 Subject: [PATCH 5/7] Opt to rebuild all roles in helper method This code was fine for the 0009 migrations since we introduced all roles at once, however with changes in 0017 we can't just patch from the roots, so this works generically without having to get fancy about identifying which roles we need to actually update. --- awx/main/migrations/_rbac.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/awx/main/migrations/_rbac.py b/awx/main/migrations/_rbac.py index beabcf7c1d..c45b1ba5b1 100644 --- a/awx/main/migrations/_rbac.py +++ b/awx/main/migrations/_rbac.py @@ -477,8 +477,6 @@ def rebuild_role_hierarchy(apps, schema_editor): start = time() roots = Role.objects \ .all() \ - .exclude(pk__in=Role.parents.through.objects.all() - .values_list('from_role_id', flat=True).distinct()) \ .values_list('id', flat=True) stop = time() logger.info('Found %d roots in %f seconds, rebuilding ancestry map' % (len(roots), stop - start)) From 899ff8ed3bedc164d09faead839c4cce64c58869 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 May 2016 13:19:55 -0400 Subject: [PATCH 6/7] flake8 --- awx/main/access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/access.py b/awx/main/access.py index 68d9c94ded..7e2289df42 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -774,7 +774,7 @@ class JobTemplateAccess(BaseAccess): inventory = Inventory.objects.filter(id=inventory_pk) if not inventory.exists() and not data.get('ask_inventory_on_launch', False): return False - if inventory.exists() and not self.user in inventory[0].use_role: + if inventory.exists() and self.user not in inventory[0].use_role: return False project_pk = get_pk_from_dict(data, 'project') From 19dcc06c9d02e5a96ffed74987b5c3fc6e144226 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 May 2016 21:10:43 -0400 Subject: [PATCH 7/7] Read permissions goes to read_role, not use_role Dork. --- awx/main/migrations/_rbac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/migrations/_rbac.py b/awx/main/migrations/_rbac.py index c45b1ba5b1..7eeb95579d 100644 --- a/awx/main/migrations/_rbac.py +++ b/awx/main/migrations/_rbac.py @@ -219,7 +219,7 @@ def migrate_inventory(apps, schema_editor): if perm.permission_type == 'admin': return inventory.admin_role elif perm.permission_type == 'read': - return inventory.use_role + return inventory.read_role elif perm.permission_type == 'write': return inventory.update_role elif perm.permission_type == 'check' or perm.permission_type == 'run' or perm.permission_type == 'create':