Added a ResourceMixin to be added to any model that is a "Resource"

Also added initial permissions checking and accessible object methods to
the mixin
This commit is contained in:
Akita Noek 2016-01-29 13:18:32 -05:00
parent 6dad0406b8
commit 5b50ebb8da
6 changed files with 155 additions and 35 deletions

View File

@ -11,15 +11,16 @@ 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.fields import ImplicitRoleField
from awx.main.constants import CLOUD_PROVIDERS
from awx.main.utils import decrypt_field
from awx.main.models.base import * # noqa
from awx.main.models.mixins import ResourceMixin
__all__ = ['Credential']
class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
'''
A credential contains information about how to talk to a remote resource
Usually this is a SSH key location, and possibly an unlock password.
@ -154,12 +155,6 @@ 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=[

View File

@ -18,11 +18,12 @@ from django.utils.timezone import now
# AWX
from awx.main.constants import CLOUD_PROVIDERS
from awx.main.fields import AutoOneToOneField, ImplicitResourceField, ImplicitRoleField
from awx.main.fields import AutoOneToOneField, ImplicitRoleField
from awx.main.managers import HostManager
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.models.mixins import ResourceMixin
from awx.main.utils import ignore_inventory_computed_fields, _inventory_updates
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'CustomInventoryScript']
@ -30,7 +31,7 @@ __all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', '
logger = logging.getLogger('awx.main.models.inventory')
class Inventory(CommonModel):
class Inventory(CommonModel, ResourceMixin):
'''
an inventory source contains lists and hosts.
'''
@ -92,9 +93,6 @@ 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',
@ -468,7 +466,7 @@ class Host(CommonModelNameNotUnique):
# Use .job_events.all() to get events affecting this host.
class Group(CommonModelNameNotUnique):
class Group(CommonModelNameNotUnique, ResourceMixin):
'''
A group containing managed hosts. A group or host may belong to multiple
groups.
@ -538,9 +536,6 @@ 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',
@ -1096,7 +1091,7 @@ class InventorySourceOptions(BaseModel):
return ','.join(choices)
class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
class InventorySource(UnifiedJobTemplate, InventorySourceOptions, ResourceMixin):
class Meta:
app_label = 'main'
@ -1123,12 +1118,6 @@ 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=[

View File

@ -26,7 +26,9 @@ 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
from awx.main.fields import ImplicitRoleField
from awx.main.models.mixins import ResourceMixin
logger = logging.getLogger('awx.main.models.jobs')
@ -150,7 +152,7 @@ class JobOptions(BaseModel):
else:
return []
class JobTemplate(UnifiedJobTemplate, JobOptions):
class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
'''
A job template is a reusable job definition for applying a project (with
playbook) to an inventory source with a given credential.
@ -179,7 +181,6 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
blank=True,
default={},
)
resource = ImplicitResourceField()
admin_role = ImplicitRoleField(
role_name='Job Template Administrator',
parent_role='project.admin_role',

136
awx/main/models/mixins.py Normal file
View File

@ -0,0 +1,136 @@
# Django
from django.db import models
from django.db import connection
# AWX
from awx.main.models.rbac import RolePermission, Role, RoleHierarchy
from awx.main.fields import ImplicitResourceField
__all__ = 'ResourceMixin'
class ResourceMixin(models.Model):
class Meta:
abstract = True
resource = ImplicitResourceField()
@classmethod
def accessible_objects(cls, user, permissions):
'''
Use instead of `MyModel.objects` when you want to only consider
resources that a user has specific permissions for. For example:
MyModel.accessible_objects(user, {'read': True}).filter(name__istartswith='bar');
NOTE: This should only be used for list type things. If you have a
specific resource you want to check permissions on, it is more
performant to resolve the resource in question then call
`myresource.get_permissions(user)`.
'''
perm_clause = ''
aggregates = ''
for perm in permissions:
if not perm_clause:
perm_clause = 'WHERE '
else:
perm_clause += ' AND '
perm_clause += '"%s" = %d' % (perm, int(permissions[perm]))
aggregates += ', MAX("%s") as "%s"' % (perm, perm)
return cls.objects.extra(
where=[
'''
%(table_name)s.resource_id in (
SELECT resource_id FROM (
SELECT %(rbac_permission)s.resource_id %(aggregates)s
FROM %(rbac_role)s_members
LEFT JOIN %(rbac_role_hierachy)s
ON (%(rbac_role_hierachy)s.ancestor_id = %(rbac_role)s_members.role_id)
LEFT JOIN %(rbac_permission)s
ON (%(rbac_permission)s.role_id = %(rbac_role_hierachy)s.role_id)
WHERE %(rbac_role)s_members.user_id=%(user_id)d
GROUP BY %(rbac_permission)s.resource_id
) summarized_permissions
%(perm_clause)s
)
'''
%
{
'table_name': cls._meta.db_table,
'aggregates': aggregates,
'user_id': user.id,
'perm_clause': perm_clause,
'rbac_role': Role._meta.db_table,
'rbac_permission': RolePermission._meta.db_table,
'rbac_role_hierachy': RoleHierarchy._meta.db_table
}
]
)
def get_permissions(self, user):
'''
Returns a dict (or None) of the permissions a user has for a given
resource.
Note: Each field in the dict is the `or` of all respective permissions
that have been granted to the roles that are applicable for the given
user.
In example, if a user has been granted read access through a permission
on one role and write access through a permission on a separate role,
the returned dict will denote that the user has both read and write
access.
'''
with connection.cursor() as cursor:
cursor.execute(
'''
SELECT
MAX("create") as "create",
MAX("read") as "read",
MAX("write") as "write",
MAX("update") as "update",
MAX("delete") as "delete",
MAX("scm_update") as "scm_update",
MAX("execute") as "execute",
MAX("use") as "use"
FROM %(rbac_permission)s
LEFT JOIN %(rbac_role_hierachy)s
ON (%(rbac_permission)s.role_id = %(rbac_role_hierachy)s.role_id)
LEFT JOIN %(rbac_role)s_members
ON (
%(rbac_role)s_members.role_id = %(rbac_role_hierachy)s.ancestor_id
AND %(rbac_role)s_members.user_id = %(user_id)d
)
WHERE %(rbac_permission)s.resource_id=%(resource_id)s
GROUP BY %(rbac_role)s_members.user_id
'''
%
{
'user_id': user.id,
'resource_id': self.resource.id,
'rbac_role': Role._meta.db_table,
'rbac_permission': RolePermission._meta.db_table,
'rbac_role_hierachy': RoleHierarchy._meta.db_table
}
)
row = cursor.fetchone()
if row:
return dict(zip([x.name for x in cursor.description], row))
return None
def accessible_by(self, user, permissions):
'''
Returns true if the user has all of the specified permissions
'''
perms = self.get_permissions(user)
for k in permissions:
if k not in perms or perms[k] < permissions[k]:
return False
return True

View File

@ -16,14 +16,15 @@ from django.utils.timezone import now as tz_now
from django.utils.translation import ugettext_lazy as _
# AWX
from awx.main.fields import AutoOneToOneField, ImplicitResourceField, ImplicitRoleField
from awx.main.fields import AutoOneToOneField, ImplicitRoleField
from awx.main.models.base import * # noqa
from awx.main.models.mixins import ResourceMixin
from awx.main.conf import tower_settings
__all__ = ['Organization', 'Team', 'Permission', 'Profile', 'AuthToken']
class Organization(CommonModel):
class Organization(CommonModel, ResourceMixin):
'''
An organization is the basic unit of multi-tenancy divisions
'''
@ -51,7 +52,6 @@ class Organization(CommonModel):
blank=True,
related_name='organizations',
)
resource = ImplicitResourceField()
admin_role = ImplicitRoleField(
role_name='Organization Administrator',
resource_field='resource',
@ -77,7 +77,7 @@ class Organization(CommonModel):
super(Organization, self).mark_inactive(save=save)
class Team(CommonModelNameNotUnique):
class Team(CommonModelNameNotUnique, ResourceMixin):
'''
A team is a group of users that work on common projects.
'''
@ -104,7 +104,6 @@ class Team(CommonModelNameNotUnique):
blank=True,
related_name='teams',
)
resource = ImplicitResourceField()
admin_role = ImplicitRoleField(
role_name='Team Administrator',
parent_role='organization.admin_role',

View File

@ -21,8 +21,9 @@ from awx.lib.compat import slugify
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.models.mixins import ResourceMixin
from awx.main.utils import update_scm_url
from awx.main.fields import ImplicitResourceField, ImplicitRoleField
from awx.main.fields import ImplicitRoleField
__all__ = ['Project', 'ProjectUpdate']
@ -186,7 +187,7 @@ class ProjectOptions(models.Model):
return sorted(results, key=lambda x: smart_str(x).lower())
class Project(UnifiedJobTemplate, ProjectOptions):
class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
'''
A project represents a playbook git repo that can access a set of inventories
'''
@ -214,7 +215,6 @@ class Project(UnifiedJobTemplate, ProjectOptions):
default=0,
blank=True,
)
resource = ImplicitResourceField()
admin_role = ImplicitRoleField(
role_name='Project Administrator',
parent_role='organization.admin_role',