From 134b60dbed5914ad13f1456a4cfbd3304c61ca9d Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Mon, 11 Jul 2016 14:06:40 -0400 Subject: [PATCH 1/5] Switch to explicit checks for system auditor for all applicable get_queryset calls Solves #2918 and probably a couple other corner cases where orphan situations could happen --- awx/main/access.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index f47198e4b0..ae1fb20f34 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -139,7 +139,7 @@ class BaseAccess(object): self.user = user def get_queryset(self): - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return self.model.objects.all() else: return self.model.objects.none() @@ -221,7 +221,7 @@ class UserAccess(BaseAccess): model = User def get_queryset(self): - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return User.objects.all() if tower_settings.ORG_ADMINS_CAN_SEE_ALL_USERS and \ @@ -718,7 +718,7 @@ class ProjectAccess(BaseAccess): model = Project def get_queryset(self): - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return self.model.objects.all() qs = self.model.accessible_objects(self.user, 'read_role') return qs.select_related('modified_by', 'credential', 'current_job', 'last_job').all() @@ -752,7 +752,7 @@ class ProjectUpdateAccess(BaseAccess): model = ProjectUpdate def get_queryset(self): - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return self.model.objects.all() qs = ProjectUpdate.objects.distinct() qs = qs.select_related('created_by', 'modified_by', 'project') @@ -788,7 +788,7 @@ class JobTemplateAccess(BaseAccess): model = JobTemplate def get_queryset(self): - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: qs = self.model.objects.all() else: qs = self.model.accessible_objects(self.user, 'read_role') @@ -979,7 +979,7 @@ class JobAccess(BaseAccess): qs = qs.select_related('created_by', 'modified_by', 'job_template', 'inventory', 'project', 'credential', 'cloud_credential', 'job_template') qs = qs.prefetch_related('unified_job_template') - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs.all() qs_jt = qs.filter( @@ -1086,7 +1086,7 @@ class AdHocCommandAccess(BaseAccess): qs = self.model.objects.distinct() qs = qs.select_related('created_by', 'modified_by', 'inventory', 'credential') - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs.all() inventory_qs = Inventory.accessible_objects(self.user, 'read_role') @@ -1147,7 +1147,7 @@ class AdHocCommandEventAccess(BaseAccess): qs = self.model.objects.distinct() qs = qs.select_related('ad_hoc_command', 'host') - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs.all() ad_hoc_command_qs = self.user.get_queryset(AdHocCommand) host_qs = self.user.get_queryset(Host) @@ -1173,7 +1173,7 @@ class JobHostSummaryAccess(BaseAccess): def get_queryset(self): qs = self.model.objects qs = qs.select_related('job', 'job__job_template', 'host') - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs.all() job_qs = self.user.get_queryset(Job) host_qs = self.user.get_queryset(Host) @@ -1205,7 +1205,7 @@ class JobEventAccess(BaseAccess): event_data__icontains='"ansible_job_id": "', event_data__contains='"module_name": "async_status"') - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs.all() job_qs = self.user.get_queryset(Job) @@ -1318,7 +1318,7 @@ class ScheduleAccess(BaseAccess): qs = self.model.objects.all() qs = qs.select_related('created_by', 'modified_by') qs = qs.prefetch_related('unified_job_template') - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs.all() job_template_qs = self.user.get_queryset(JobTemplate) inventory_source_qs = self.user.get_queryset(InventorySource) @@ -1369,7 +1369,7 @@ class NotificationTemplateAccess(BaseAccess): def get_queryset(self): qs = self.model.objects.all() - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs return self.model.objects.filter(organization__in=Organization.accessible_objects(self.user, 'admin_role').all()) @@ -1413,7 +1413,7 @@ class NotificationAccess(BaseAccess): def get_queryset(self): qs = self.model.objects.all() - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return qs return self.model.objects.filter(notification_template__organization__in=Organization.accessible_objects(self.user, 'admin_role')) @@ -1430,7 +1430,7 @@ class LabelAccess(BaseAccess): model = Label def get_queryset(self): - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return self.model.objects.all() return self.model.objects.filter( organization__in=Organization.accessible_objects(self.user, 'read_role') @@ -1493,9 +1493,7 @@ class ActivityStreamAccess(BaseAccess): 'inventory_update', 'credential', 'team', 'project', 'project_update', 'permission', 'job_template', 'job', 'ad_hoc_command', 'notification_template', 'notification', 'label', 'role') - if self.user.is_superuser: - return qs.all() - if self.user in Role.singleton('system_auditor'): + if self.user.is_superuser or self.user.is_system_auditor: return qs.all() inventory_set = Inventory.accessible_objects(self.user, 'read_role') @@ -1543,7 +1541,7 @@ class CustomInventoryScriptAccess(BaseAccess): model = CustomInventoryScript def get_queryset(self): - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return self.model.objects.distinct().all() return self.model.accessible_objects(self.user, 'read_role').all() @@ -1599,7 +1597,7 @@ class RoleAccess(BaseAccess): def can_read(self, obj): if not obj: return False - if self.user.is_superuser: + if self.user.is_superuser or self.user.is_system_auditor: return True if obj.object_id: From 6de5cceb8f3f34dc52426a43010cbe87e615eabb Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Mon, 11 Jul 2016 14:28:26 -0400 Subject: [PATCH 2/5] More is_system_auditor checks in views.py --- awx/api/views.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index 4ad0a9cc58..d452758ce6 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1401,7 +1401,7 @@ class OrganizationCredentialList(SubListCreateAPIView): user_visible = Credential.accessible_objects(self.request.user, 'read_role').all() org_set = Credential.accessible_objects(organization.admin_role, 'read_role').all() - if self.request.user.is_superuser: + if self.request.user.is_superuser or self.request.user.is_system_auditor: return org_set return org_set & user_visible @@ -2591,7 +2591,7 @@ class SystemJobTemplateList(ListAPIView): serializer_class = SystemJobTemplateSerializer def get(self, request, *args, **kwargs): - if not request.user.is_superuser: + if not request.user.is_superuser and not request.user.is_system_auditor: raise PermissionDenied("Superuser privileges needed.") return super(SystemJobTemplateList, self).get(request, *args, **kwargs) @@ -3321,7 +3321,7 @@ class SystemJobList(ListCreateAPIView): serializer_class = SystemJobListSerializer def get(self, request, *args, **kwargs): - if not request.user.is_superuser: + if not request.user.is_superuser and not request.user.is_system_auditor: raise PermissionDenied("Superuser privileges needed.") return super(SystemJobList, self).get(request, *args, **kwargs) @@ -3625,8 +3625,6 @@ class RoleList(ListAPIView): new_in_300 = True def get_queryset(self): - if self.request.user.is_superuser: - return Role.objects.all() return Role.visible_roles(self.request.user) From 4c67c50373f5f0474aeee855960312c94761910b Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Mon, 11 Jul 2016 14:42:01 -0400 Subject: [PATCH 3/5] Allow system auditors to see notification templates and management jobs in the UI --- awx/ui/client/src/app.js | 1 + awx/ui/client/src/login/loginModal/loginModal.controller.js | 1 + awx/ui/client/src/setup-menu/setup-menu.partial.html | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 2229f4ca77..ab39dd8415 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -880,6 +880,7 @@ var tower = angular.module('Tower', [ } // If browser refresh, set the user_is_superuser value $rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser'); + $rootScope.user_is_system_auditor = Authorization.getUserInfo('is_system_auditor'); // state the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket) if(!_.contains($location.$$url, '/login')){ ConfigService.getConfig().then(function(){ diff --git a/awx/ui/client/src/login/loginModal/loginModal.controller.js b/awx/ui/client/src/login/loginModal/loginModal.controller.js index c87aafba41..9bd5132ab1 100644 --- a/awx/ui/client/src/login/loginModal/loginModal.controller.js +++ b/awx/ui/client/src/login/loginModal/loginModal.controller.js @@ -137,6 +137,7 @@ export default ['$log', '$cookieStore', '$compile', '$window', '$rootScope', $rootScope.sessionTimer = timer; $rootScope.$emit('OpenSocket'); $rootScope.user_is_superuser = data.results[0].is_superuser; + $rootScope.user_is_system_auditor = data.results[0].is_system_auditor; scope.$emit('AuthorizationGetLicense'); }); }) diff --git a/awx/ui/client/src/setup-menu/setup-menu.partial.html b/awx/ui/client/src/setup-menu/setup-menu.partial.html index ae1cdb0ee2..fc9a04d4ed 100644 --- a/awx/ui/client/src/setup-menu/setup-menu.partial.html +++ b/awx/ui/client/src/setup-menu/setup-menu.partial.html @@ -24,7 +24,7 @@ Add passwords, SSH keys, etc. for Tower to use when launching jobs against machines, or when syncing inventories or projects.

- +

Management Jobs

Manage the cleanup of old job history, activity streams, data marked for deletion, and system tracking info. @@ -37,7 +37,7 @@

+ ng-if="orgAdmin || user_is_system_auditor || user_is_superuser">

Notifications

Create templates for sending notifications with Email, HipChat, Slack, and SMS. From ec37703ce85ed6d0bd30b71852e30342d883b59a Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Mon, 11 Jul 2016 14:44:36 -0400 Subject: [PATCH 4/5] Allow SA's to read all notification templates --- awx/main/access.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awx/main/access.py b/awx/main/access.py index ae1fb20f34..38d50ba1c9 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1375,6 +1375,8 @@ class NotificationTemplateAccess(BaseAccess): @check_superuser def can_read(self, obj): + if self.user.is_superuser or self.user.is_system_auditor: + return True if obj.organization is not None: return self.user in obj.organization.admin_role return False From fd461a9768f939f28e1cac75429186da4c8df1d7 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Mon, 11 Jul 2016 15:05:35 -0400 Subject: [PATCH 5/5] Remove redundant check --- awx/main/access.py | 1 - 1 file changed, 1 deletion(-) diff --git a/awx/main/access.py b/awx/main/access.py index 38d50ba1c9..85d4a9d980 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1373,7 +1373,6 @@ class NotificationTemplateAccess(BaseAccess): return qs return self.model.objects.filter(organization__in=Organization.accessible_objects(self.user, 'admin_role').all()) - @check_superuser def can_read(self, obj): if self.user.is_superuser or self.user.is_system_auditor: return True