mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
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:
parent
6dad0406b8
commit
5b50ebb8da
@ -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=[
|
||||
|
||||
@ -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=[
|
||||
|
||||
@ -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
136
awx/main/models/mixins.py
Normal 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
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user