From 6dad0406b8fdbdde85c0751940b95c8d22d3c5ee Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Thu, 28 Jan 2016 16:58:19 -0500 Subject: [PATCH] Initial implicit role / resource field additions into models "Completes" #731 until we find out what I missed --- awx/main/fields.py | 56 ++++++++++++++++++++++++--------- awx/main/models/credential.py | 22 +++++++++++++ awx/main/models/inventory.py | 56 ++++++++++++++++++++++++++++++++- awx/main/models/jobs.py | 20 ++++++++++++ awx/main/models/organization.py | 39 ++++++++++++++++++++++- awx/main/models/projects.py | 34 ++++++++++++++++++++ awx/main/models/rbac.py | 3 +- 7 files changed, 213 insertions(+), 17 deletions(-) diff --git a/awx/main/fields.py b/awx/main/fields.py index 18c227c7bc..d6a7dcf4d4 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -45,7 +45,8 @@ class AutoOneToOneField(models.OneToOneField): def resolve_field(obj, field): for f in field.split('.'): - obj = getattr(obj, f) + if obj: + obj = getattr(obj, f) return obj class ResourceFieldDescriptor(ReverseSingleRelatedObjectDescriptor): @@ -61,7 +62,16 @@ class ResourceFieldDescriptor(ReverseSingleRelatedObjectDescriptor): return resource resource = Resource._default_manager.create() if self.parent_resource: - resource.parent = resolve_field(instance, self.parent_resource) + # Take first non null parent resource + parent = None + if type(self.parent_resource) is list: + for path in self.parent_resource: + parent = resolve_field(instance, path) + if parent: + break + else: + parent = resolve_field(instance, self.parent_resource) + resource.parent = parent resource.save() setattr(instance, self.field.name, resource) instance.save(update_fields=[self.field.name,]) @@ -102,25 +112,43 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor): if not self.role_name: raise FieldError('Implicit role missing `role_name`') - if not self.resource_field: - raise FieldError('Implicit role missing `resource_field` specification') - if not self.permissions: - raise FieldError('Implicit role missing `permissions`') role = Role._default_manager.create(name=self.role_name) role.save() if self.parent_role: - role.parents.add(resolve_field(instance, self.parent_role)) + # Add all non-null parent roles as parents + if type(self.parent_role) is list: + for path in self.parent_role: + parent = resolve_field(instance, path) + if parent: + role.parents.add(parent) + else: + parent = resolve_field(instance, self.parent_role) + if parent: + role.parents.add(parent) setattr(instance, self.field.name, role) instance.save(update_fields=[self.field.name,]) - permissions = RolePermission( - role=role, - resource=getattr(instance, self.resource_field) - ) - for k,v in self.permissions.items(): - setattr(permissions, k, v) - permissions.save() + if self.resource_field and self.permissions: + permissions = RolePermission( + role=role, + resource=getattr(instance, self.resource_field) + ) + + if 'all' in self.permissions and self.permissions['all']: + del self.permissions['all'] + self.permissions['create'] = True + self.permissions['read'] = True + self.permissions['write'] = True + self.permissions['update'] = True + self.permissions['delete'] = True + self.permissions['scm_update'] = True + self.permissions['use'] = True + self.permissions['execute'] = True + + for k,v in self.permissions.items(): + setattr(permissions, k, v) + permissions.save() return role diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index 82e0f576e1..3f4e6bf27e 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -11,6 +11,7 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS from django.core.urlresolvers import reverse # AWX +from awx.main.fields import ImplicitResourceField, ImplicitRoleField from awx.main.constants import CLOUD_PROVIDERS from awx.main.utils import decrypt_field from awx.main.models.base import * # noqa @@ -153,6 +154,27 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique): default='', help_text=_('Vault password (or "ASK" to prompt the user).'), ) + resource = ImplicitResourceField( + parent_resource=[ + 'user.resource', + 'team.resource' + ] + ) + owner_role = ImplicitRoleField( + role_name='Credential Owner', + parent_role=[ + 'user.user_role', + 'team.admin_role' + ], + resource_field='resource', + permissions = { 'all': True } + ) + usage_role = ImplicitRoleField( + role_name='Credential User', + resource_field='resource', + parent_role= 'team.member_role', + permissions = { 'usage': True } + ) @property def needs_ssh_password(self): diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 37b1dafc4b..a4c593e5d2 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -18,7 +18,7 @@ from django.utils.timezone import now # AWX from awx.main.constants import CLOUD_PROVIDERS -from awx.main.fields import AutoOneToOneField +from awx.main.fields import AutoOneToOneField, ImplicitResourceField, ImplicitRoleField from awx.main.managers import HostManager from awx.main.models.base import * # noqa from awx.main.models.jobs import Job @@ -92,6 +92,21 @@ class Inventory(CommonModel): editable=False, help_text=_('Number of external inventory sources in this inventory with failures.'), ) + resource = ImplicitResourceField( + parent_resource='organization.resource' + ) + admin_role = ImplicitRoleField( + role_name='Inventory Administrator', + parent_role='organization.admin_role', + resource_field='resource', + permissions = { 'all': True } + ) + auditor_role = ImplicitRoleField( + role_name='Inventory Auditor', + parent_role='organization.auditor_role', + resource_field='resource', + permissions = { 'read': True } + ) def get_absolute_url(self): return reverse('api:inventory_detail', args=(self.pk,)) @@ -523,6 +538,21 @@ class Group(CommonModelNameNotUnique): editable=False, help_text=_('Inventory source(s) that created or modified this group.'), ) + resource = ImplicitResourceField( + parent_resource='inventory.resource' + ) + admin_role = ImplicitRoleField( + role_name='Inventory Group Administrator', + parent_role='inventory.admin_role', + resource_field='resource', + permissions = { 'all': True } + ) + auditor_role = ImplicitRoleField( + role_name='Inventory Group Auditor', + parent_role='inventory.auditor_role', + resource_field='resource', + permissions = { 'read': True } + ) def __unicode__(self): return self.name @@ -1093,6 +1123,30 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): update_cache_timeout = models.PositiveIntegerField( default=0, ) + resource = ImplicitResourceField( + parent_resource=[ + 'group.resource', + 'inventory.resource' + ] + ) + admin_role = ImplicitRoleField( + role_name='Inventory Group Administrator', + parent_role=[ + 'group.admin_role', + 'inventory.admin_role', + ], + resource_field='resource', + permissions = { 'all': True } + ) + auditor_role = ImplicitRoleField( + role_name='Inventory Group Auditor', + parent_role=[ + 'group.auditor_role', + 'inventory.auditor_role', + ], + resource_field='resource', + permissions = { 'read': True } + ) @classmethod def _get_unified_job_class(cls): diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 833d20a9b4..019be64abd 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -26,6 +26,7 @@ from awx.main.utils import decrypt_field, ignore_inventory_computed_fields from awx.main.utils import emit_websocket_notification from awx.main.redact import PlainTextCleaner from awx.main.conf import tower_settings +from awx.main.fields import ImplicitResourceField, ImplicitRoleField logger = logging.getLogger('awx.main.models.jobs') @@ -178,6 +179,25 @@ class JobTemplate(UnifiedJobTemplate, JobOptions): blank=True, default={}, ) + resource = ImplicitResourceField() + admin_role = ImplicitRoleField( + role_name='Job Template Administrator', + parent_role='project.admin_role', + resource_field='resource', + permissions = { 'all': True } + ) + auditor_role = ImplicitRoleField( + role_name='Job Template Auditor', + parent_role='project.auditor_role', + resource_field='resource', + permissions = { 'read': True } + ) + executor_role = ImplicitRoleField( + role_name='Job Template Executor', + parent_role='project.auditor_role', + resource_field='resource', + permissions = { 'execute': True } + ) @classmethod def _get_unified_job_class(cls): diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index c22b907082..fc74aabd58 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -16,7 +16,7 @@ from django.utils.timezone import now as tz_now from django.utils.translation import ugettext_lazy as _ # AWX -from awx.main.fields import AutoOneToOneField +from awx.main.fields import AutoOneToOneField, ImplicitResourceField, ImplicitRoleField from awx.main.models.base import * # noqa from awx.main.conf import tower_settings @@ -42,11 +42,27 @@ class Organization(CommonModel): blank=True, related_name='admin_of_organizations', ) + + # TODO: This field is deprecated. In 3.0 all projects will have exactly one + # organization parent, the foreign key field representing that has been + # moved to the Project model. projects = models.ManyToManyField( 'Project', blank=True, related_name='organizations', ) + resource = ImplicitResourceField() + admin_role = ImplicitRoleField( + role_name='Organization Administrator', + resource_field='resource', + permissions = { 'all': True } + ) + auditor_role = ImplicitRoleField( + role_name='Organization Auditor', + resource_field='resource', + permissions = { 'read': True } + ) + def get_absolute_url(self): return reverse('api:organization_detail', args=(self.pk,)) @@ -88,6 +104,23 @@ class Team(CommonModelNameNotUnique): blank=True, related_name='teams', ) + resource = ImplicitResourceField() + admin_role = ImplicitRoleField( + role_name='Team Administrator', + parent_role='organization.admin_role', + resource_field='resource', + permissions = { 'all': True } + ) + auditor_role = ImplicitRoleField( + role_name='Team Auditor', + parent_role='organization.auditor_role', + resource_field='resource', + permissions = { 'read': True } + ) + member_role = ImplicitRoleField( + role_name='Team Member', + parent_role='admin_role', + ) def get_absolute_url(self): return reverse('api:team_detail', args=(self.pk,)) @@ -103,6 +136,10 @@ class Team(CommonModelNameNotUnique): class Permission(CommonModelNameNotUnique): ''' A permission allows a user, project, or team to be able to use an inventory source. + + NOTE: This class is deprecated, permissions and access is to be handled by + our new RBAC system. This class should be able to be safely removed after a 3.0.0 + migration. - anoek 2016-01-28 ''' class Meta: diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 2fa6512ca0..172fd09d6f 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -22,6 +22,7 @@ from awx.main.models.base import * # noqa from awx.main.models.jobs import Job from awx.main.models.unified_jobs import * # noqa from awx.main.utils import update_scm_url +from awx.main.fields import ImplicitResourceField, ImplicitRoleField __all__ = ['Project', 'ProjectUpdate'] @@ -194,6 +195,14 @@ class Project(UnifiedJobTemplate, ProjectOptions): app_label = 'main' ordering = ('id',) + organization = models.ForeignKey( + 'Organization', + blank=False, + null=True, + on_delete=models.SET_NULL, + related_name='project_list', # TODO: this should eventually be refactored + # back to 'projects' - anoek 2016-01-28 + ) scm_delete_on_next_update = models.BooleanField( default=False, editable=False, @@ -205,6 +214,31 @@ class Project(UnifiedJobTemplate, ProjectOptions): default=0, blank=True, ) + resource = ImplicitResourceField() + admin_role = ImplicitRoleField( + role_name='Project Administrator', + parent_role='organization.admin_role', + resource_field='resource', + permissions = { 'all': True } + ) + auditor_role = ImplicitRoleField( + role_name='Project Auditor', + parent_role='organization.auditor_role', + resource_field='resource', + permissions = { 'read': True } + ) + member_role = ImplicitRoleField( + role_name='Project Member', + parent_role='admin', + resource_field='resource', + permissions = { 'usage': True } + ) + scm_update_role = ImplicitRoleField( + role_name='Project Updater', + parent_role='admin', + resource_field='resource', + permissions = { 'scm_update': True } + ) @classmethod def _get_unified_job_class(cls): diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index fb8a6f12ca..36db604997 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -11,7 +11,6 @@ from django.db.models.signals import pre_save, post_save, pre_delete, post_delet # AWX from awx.main.models.base import * # noqa -from awx.main.fields import * # noqa __all__ = ['Role', 'RolePermission', 'Resource', 'RoleHierarchy', 'ResourceHierarchy'] @@ -158,6 +157,8 @@ class RolePermission(CreatedModifiedModel): write = models.IntegerField(default = 0) update = models.IntegerField(default = 0) delete = models.IntegerField(default = 0) + execute = models.IntegerField(default = 0) scm_update = models.IntegerField(default = 0) + use = models.IntegerField(default = 0)