diff --git a/awx/main/access.py b/awx/main/access.py index 37987818c8..4d91943fec 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -2218,6 +2218,7 @@ class NotificationTemplateAccess(BaseAccess): def filtered_queryset(self): return self.model.objects.filter( + Q(organization__in=Organization.objects.filter(notification_admin_role__members=self.user)) | Q(organization__in=self.user.admin_of_organizations) | Q(organization__in=self.user.auditor_of_organizations) ).distinct() @@ -2226,22 +2227,22 @@ class NotificationTemplateAccess(BaseAccess): if self.user.is_superuser or self.user.is_system_auditor: return True if obj.organization is not None: - if self.user in obj.organization.admin_role or self.user in obj.organization.auditor_role: + if self.user in obj.organization.notification_admin_role or self.user in obj.organization.auditor_role: return True return False @check_superuser def can_add(self, data): if not data: - return Organization.accessible_objects(self.user, 'admin_role').exists() - return self.check_related('organization', Organization, data, mandatory=True) + return Organization.accessible_objects(self.user, 'notification_admin_role').exists() + return self.check_related('organization', Organization, data, role_field='notification_admin_role', mandatory=True) @check_superuser def can_change(self, obj, data): if obj.organization is None: # only superusers are allowed to edit orphan notification templates return False - return self.check_related('organization', Organization, data, obj=obj, mandatory=True) + return self.check_related('organization', Organization, data, obj=obj, role_field='notification_admin_role', mandatory=True) def can_admin(self, obj, data): return self.can_change(obj, data) @@ -2253,7 +2254,7 @@ class NotificationTemplateAccess(BaseAccess): def can_start(self, obj, validate_license=True): if obj.organization is None: return False - return self.user in obj.organization.admin_role + return self.user in obj.organization.notification_admin_role class NotificationAccess(BaseAccess): @@ -2265,6 +2266,7 @@ class NotificationAccess(BaseAccess): def filtered_queryset(self): return self.model.objects.filter( + Q(notification_template__organization__in=Organization.objects.filter(notification_admin_role__members=self.user)) | Q(notification_template__organization__in=self.user.admin_of_organizations) | Q(notification_template__organization__in=self.user.auditor_of_organizations) ).distinct() diff --git a/awx/main/migrations/0020_declare_new_rbac_roles.py b/awx/main/migrations/0020_declare_new_rbac_roles.py index 438733af54..da151393ab 100644 --- a/awx/main/migrations/0020_declare_new_rbac_roles.py +++ b/awx/main/migrations/0020_declare_new_rbac_roles.py @@ -35,6 +35,11 @@ class Migration(migrations.Migration): name='workflow_admin_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='notification_admin_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.AlterField( model_name='credential', name='admin_role', @@ -63,6 +68,6 @@ 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'], related_name='+', to='main.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'], related_name='+', to='main.Role'), ), ] diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index 2898b8dc6a..b9a562fd3c 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -55,11 +55,16 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi workflow_admin_role = ImplicitRoleField( parent_role='admin_role', ) + notification_admin_role = ImplicitRoleField( + parent_role='admin_role', + ) auditor_role = ImplicitRoleField( parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, ) member_role = ImplicitRoleField( - parent_role=['admin_role', 'project_admin_role', 'inventory_admin_role', 'workflow_admin_role'] + parent_role=['admin_role', 'project_admin_role', + 'inventory_admin_role', 'workflow_admin_role', + 'notification_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 3a55de086c..010d0a624b 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -33,37 +33,39 @@ 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'), - 'project_admin_role' : _('Project Admin'), - 'inventory_admin_role' : _('Inventory Admin'), + 'system_administrator': _('System Administrator'), + 'system_auditor': _('System Auditor'), + 'adhoc_role': _('Ad Hoc'), + 'admin_role': _('Admin'), + 'project_admin_role': _('Project Admin'), + 'inventory_admin_role': _('Inventory Admin'), 'credential_admin_role': _('Credential Admin'), - 'workflow_admin_role' : _('Workflow Admin'), - 'auditor_role' : _('Auditor'), - 'execute_role' : _('Execute'), - 'member_role' : _('Member'), - 'read_role' : _('Read'), - 'update_role' : _('Update'), - 'use_role' : _('Use'), + 'workflow_admin_role': _('Workflow Admin'), + 'notification_admin_role': _('Notification Admin'), + 'auditor_role': _('Auditor'), + 'execute_role': _('Execute'), + 'member_role': _('Member'), + 'read_role': _('Read'), + 'update_role': _('Update'), + 'use_role': _('Use'), } role_descriptions = { - 'system_administrator' : _('Can manage all aspects of the system'), - 'system_auditor' : _('Can view all settings on the system'), - 'adhoc_role' : _('May run ad hoc commands on an inventory'), - 'admin_role' : _('Can manage all aspects of the %s'), - 'project_admin_role' : _('Can manage all projects of the %s'), - 'inventory_admin_role' : _('Can manage all inventories of the %s'), + 'system_administrator': _('Can manage all aspects of the system'), + 'system_auditor': _('Can view all settings on the system'), + 'adhoc_role': _('May run ad hoc commands on an inventory'), + 'admin_role': _('Can manage all aspects of the %s'), + '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'), - 'workflow_admin_role' : _('Can manage all workflows of the %s'), - 'auditor_role' : _('Can view all settings for the %s'), - 'execute_role' : _('May run the %s'), - 'member_role' : _('User is a member of the %s'), - 'read_role' : _('May view settings for the %s'), - 'update_role' : _('May update project or inventory or group using the configured source update system'), - 'use_role' : _('Can use the %s in a job template'), + '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'), + 'execute_role': _('May run the %s'), + 'member_role': _('User is a member of the %s'), + 'read_role': _('May view settings for the %s'), + 'update_role': _('May update project or inventory or group using the configured source update system'), + 'use_role': _('Can use the %s in a job template'), } diff --git a/awx/main/tests/functional/test_rbac_notifications.py b/awx/main/tests/functional/test_rbac_notifications.py index 80255da0d1..41fcbfc19c 100644 --- a/awx/main/tests/functional/test_rbac_notifications.py +++ b/awx/main/tests/functional/test_rbac_notifications.py @@ -32,6 +32,11 @@ def test_notification_template_get_queryset_orgadmin(notification_template, user notification_template.organization.admin_role.members.add(user('admin', False)) assert access.get_queryset().count() == 1 +@pytest.mark.django_db +def test_notification_template_get_queryset_notificationadmin(notification_template, user): + access = NotificationTemplateAccess(user('admin', False)) + notification_template.organization.notification_admin_role.members.add(user('admin', False)) + assert access.get_queryset().count() == 1 @pytest.mark.django_db def test_notification_template_get_queryset_org_auditor(notification_template, org_auditor): @@ -59,12 +64,13 @@ def test_notification_template_access_superuser(notification_template_factory): @pytest.mark.django_db -def test_notification_template_access_admin(organization_factory, notification_template_factory): +@pytest.mark.parametrize("role", ["present.admin_role:admin", "present.notification_admin_role:admin"]) +def test_notification_template_access_admin(role, organization_factory, notification_template_factory): other_objects = organization_factory('other') present_objects = organization_factory('present', users=['admin'], notification_templates=['test-notification'], - roles=['present.admin_role:admin']) + roles=[role]) notification_template = present_objects.notification_templates.test_notification other_org = other_objects.organization