diff --git a/awx/main/migrations/0021_v330_declare_new_rbac_roles.py b/awx/main/migrations/0021_v330_declare_new_rbac_roles.py index e43d4cb46a..4714a0194c 100644 --- a/awx/main/migrations/0021_v330_declare_new_rbac_roles.py +++ b/awx/main/migrations/0021_v330_declare_new_rbac_roles.py @@ -20,6 +20,11 @@ class Migration(migrations.Migration): name='execute_role', field=awx.main.fields.ImplicitRoleField(null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=b'admin_role', related_name='+', to='main.Role'), ), + migrations.AddField( + model_name='organization', + name='job_template_admin_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=b'admin_role', related_name='+', to='main.Role'), + ), migrations.AddField( model_name='organization', name='credential_admin_role', @@ -73,7 +78,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='jobtemplate', name='admin_role', - field=awx.main.fields.ImplicitRoleField(null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=[b'project.organization.project_admin_role', b'inventory.organization.inventory_admin_role'], related_name='+', to='main.Role'), + field=awx.main.fields.ImplicitRoleField(editable=False, null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=[b'project.organization.job_template_admin_role', b'inventory.organization.job_template_admin_role'], related_name='+', to='main.Role'), ), migrations.AlterField( model_name='jobtemplate', @@ -83,6 +88,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='organization', name='member_role', - field=awx.main.fields.ImplicitRoleField(null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=[b'admin_role', b'project_admin_role', b'inventory_admin_role', b'workflow_admin_role', b'notification_admin_role', b'credential_admin_role', b'execute_role'], related_name='+', to='main.Role'), + field=awx.main.fields.ImplicitRoleField(editable=False, null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=[b'admin_role', b'execute_role', b'project_admin_role', b'inventory_admin_role', b'workflow_admin_role', b'notification_admin_role', b'credential_admin_role', b'job_template_admin_role'], related_name='+', to='main.Role'), ), + ] diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index fdb350d9bb..532dca10b4 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -278,7 +278,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour allows_field='credentials' ) admin_role = ImplicitRoleField( - parent_role=['project.organization.project_admin_role', 'inventory.organization.inventory_admin_role'] + parent_role=['project.organization.job_template_admin_role', 'inventory.organization.job_template_admin_role'] ) execute_role = ImplicitRoleField( parent_role=['admin_role', 'project.organization.execute_role', 'inventory.organization.execute_role'], diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index db406fd2ed..047678a1fc 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -60,13 +60,17 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi notification_admin_role = ImplicitRoleField( parent_role='admin_role', ) + job_template_admin_role = ImplicitRoleField( + parent_role='admin_role', + ) auditor_role = ImplicitRoleField( parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, ) member_role = ImplicitRoleField( parent_role=['admin_role', 'execute_role', 'project_admin_role', 'inventory_admin_role', 'workflow_admin_role', - 'notification_admin_role', 'credential_admin_role'] + 'notification_admin_role', 'credential_admin_role', + 'job_template_admin_role',] ) read_role = ImplicitRoleField( parent_role=['member_role', 'auditor_role'], diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 7ce8567e88..740aa8ebd2 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -40,6 +40,7 @@ role_names = { 'project_admin_role': _('Project Admin'), 'inventory_admin_role': _('Inventory Admin'), 'credential_admin_role': _('Credential Admin'), + 'job_template_admin_role': _('Job Template Admin'), 'workflow_admin_role': _('Workflow Admin'), 'notification_admin_role': _('Notification Admin'), 'auditor_role': _('Auditor'), @@ -58,6 +59,7 @@ role_descriptions = { 'project_admin_role': _('Can manage all projects of the %s'), 'inventory_admin_role': _('Can manage all inventories of the %s'), 'credential_admin_role': _('Can manage all credentials of the %s'), + 'job_template_admin_role': _('Can manage all job templates of the %s'), 'workflow_admin_role': _('Can manage all workflows of the %s'), 'notification_admin_role': _('Can manage all notifications of the %s'), 'auditor_role': _('Can view all settings for the %s'), diff --git a/awx/main/tests/functional/test_rbac_job_templates.py b/awx/main/tests/functional/test_rbac_job_templates.py index 0b9d0c46bd..7ccc5f4c5e 100644 --- a/awx/main/tests/functional/test_rbac_job_templates.py +++ b/awx/main/tests/functional/test_rbac_job_templates.py @@ -81,11 +81,14 @@ def test_job_template_access_use_level(jt_linked, rando): @pytest.mark.django_db -@pytest.mark.parametrize("role_names", [("admin_role",), ("inventory_admin_role", "project_admin_role")]) +@pytest.mark.parametrize("role_names", [("admin_role",), ("job_template_admin_role", "inventory_admin_role", "project_admin_role")]) def test_job_template_access_admin(role_names, jt_linked, rando): access = JobTemplateAccess(rando) # Appoint this user as admin of the organization #jt_linked.inventory.organization.admin_role.members.add(rando) + assert not access.can_read(jt_linked) + assert not access.can_delete(jt_linked) + for role_name in role_names: role = getattr(jt_linked.inventory.organization, role_name) role.members.add(rando)