mirror of
https://github.com/ansible/awx.git
synced 2026-03-22 03:17:39 -02: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:
@@ -11,15 +11,16 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.fields import ImplicitResourceField, ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
from awx.main.constants import CLOUD_PROVIDERS
|
from awx.main.constants import CLOUD_PROVIDERS
|
||||||
from awx.main.utils import decrypt_field
|
from awx.main.utils import decrypt_field
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
|
from awx.main.models.mixins import ResourceMixin
|
||||||
|
|
||||||
__all__ = ['Credential']
|
__all__ = ['Credential']
|
||||||
|
|
||||||
|
|
||||||
class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
||||||
'''
|
'''
|
||||||
A credential contains information about how to talk to a remote resource
|
A credential contains information about how to talk to a remote resource
|
||||||
Usually this is a SSH key location, and possibly an unlock password.
|
Usually this is a SSH key location, and possibly an unlock password.
|
||||||
@@ -154,12 +155,6 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
|
|||||||
default='',
|
default='',
|
||||||
help_text=_('Vault password (or "ASK" to prompt the user).'),
|
help_text=_('Vault password (or "ASK" to prompt the user).'),
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField(
|
|
||||||
parent_resource=[
|
|
||||||
'user.resource',
|
|
||||||
'team.resource'
|
|
||||||
]
|
|
||||||
)
|
|
||||||
owner_role = ImplicitRoleField(
|
owner_role = ImplicitRoleField(
|
||||||
role_name='Credential Owner',
|
role_name='Credential Owner',
|
||||||
parent_role=[
|
parent_role=[
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ from django.utils.timezone import now
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.constants import CLOUD_PROVIDERS
|
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.managers import HostManager
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
from awx.main.models.jobs import Job
|
from awx.main.models.jobs import Job
|
||||||
from awx.main.models.unified_jobs import * # noqa
|
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
|
from awx.main.utils import ignore_inventory_computed_fields, _inventory_updates
|
||||||
|
|
||||||
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'CustomInventoryScript']
|
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'CustomInventoryScript']
|
||||||
@@ -30,7 +31,7 @@ __all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', '
|
|||||||
logger = logging.getLogger('awx.main.models.inventory')
|
logger = logging.getLogger('awx.main.models.inventory')
|
||||||
|
|
||||||
|
|
||||||
class Inventory(CommonModel):
|
class Inventory(CommonModel, ResourceMixin):
|
||||||
'''
|
'''
|
||||||
an inventory source contains lists and hosts.
|
an inventory source contains lists and hosts.
|
||||||
'''
|
'''
|
||||||
@@ -92,9 +93,6 @@ class Inventory(CommonModel):
|
|||||||
editable=False,
|
editable=False,
|
||||||
help_text=_('Number of external inventory sources in this inventory with failures.'),
|
help_text=_('Number of external inventory sources in this inventory with failures.'),
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField(
|
|
||||||
parent_resource='organization.resource'
|
|
||||||
)
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Inventory Administrator',
|
role_name='Inventory Administrator',
|
||||||
parent_role='organization.admin_role',
|
parent_role='organization.admin_role',
|
||||||
@@ -468,7 +466,7 @@ class Host(CommonModelNameNotUnique):
|
|||||||
# Use .job_events.all() to get events affecting this host.
|
# 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
|
A group containing managed hosts. A group or host may belong to multiple
|
||||||
groups.
|
groups.
|
||||||
@@ -538,9 +536,6 @@ class Group(CommonModelNameNotUnique):
|
|||||||
editable=False,
|
editable=False,
|
||||||
help_text=_('Inventory source(s) that created or modified this group.'),
|
help_text=_('Inventory source(s) that created or modified this group.'),
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField(
|
|
||||||
parent_resource='inventory.resource'
|
|
||||||
)
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Administrator',
|
role_name='Inventory Group Administrator',
|
||||||
parent_role='inventory.admin_role',
|
parent_role='inventory.admin_role',
|
||||||
@@ -1096,7 +1091,7 @@ class InventorySourceOptions(BaseModel):
|
|||||||
return ','.join(choices)
|
return ','.join(choices)
|
||||||
|
|
||||||
|
|
||||||
class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
class InventorySource(UnifiedJobTemplate, InventorySourceOptions, ResourceMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
@@ -1123,12 +1118,6 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
|||||||
update_cache_timeout = models.PositiveIntegerField(
|
update_cache_timeout = models.PositiveIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField(
|
|
||||||
parent_resource=[
|
|
||||||
'group.resource',
|
|
||||||
'inventory.resource'
|
|
||||||
]
|
|
||||||
)
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Administrator',
|
role_name='Inventory Group Administrator',
|
||||||
parent_role=[
|
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.utils import emit_websocket_notification
|
||||||
from awx.main.redact import PlainTextCleaner
|
from awx.main.redact import PlainTextCleaner
|
||||||
from awx.main.conf import tower_settings
|
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')
|
logger = logging.getLogger('awx.main.models.jobs')
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ class JobOptions(BaseModel):
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
class JobTemplate(UnifiedJobTemplate, JobOptions):
|
class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
||||||
'''
|
'''
|
||||||
A job template is a reusable job definition for applying a project (with
|
A job template is a reusable job definition for applying a project (with
|
||||||
playbook) to an inventory source with a given credential.
|
playbook) to an inventory source with a given credential.
|
||||||
@@ -179,7 +181,6 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
|
|||||||
blank=True,
|
blank=True,
|
||||||
default={},
|
default={},
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField()
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Job Template Administrator',
|
role_name='Job Template Administrator',
|
||||||
parent_role='project.admin_role',
|
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 _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# AWX
|
# 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.base import * # noqa
|
||||||
|
from awx.main.models.mixins import ResourceMixin
|
||||||
from awx.main.conf import tower_settings
|
from awx.main.conf import tower_settings
|
||||||
|
|
||||||
__all__ = ['Organization', 'Team', 'Permission', 'Profile', 'AuthToken']
|
__all__ = ['Organization', 'Team', 'Permission', 'Profile', 'AuthToken']
|
||||||
|
|
||||||
|
|
||||||
class Organization(CommonModel):
|
class Organization(CommonModel, ResourceMixin):
|
||||||
'''
|
'''
|
||||||
An organization is the basic unit of multi-tenancy divisions
|
An organization is the basic unit of multi-tenancy divisions
|
||||||
'''
|
'''
|
||||||
@@ -51,7 +52,6 @@ class Organization(CommonModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
related_name='organizations',
|
related_name='organizations',
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField()
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Organization Administrator',
|
role_name='Organization Administrator',
|
||||||
resource_field='resource',
|
resource_field='resource',
|
||||||
@@ -77,7 +77,7 @@ class Organization(CommonModel):
|
|||||||
super(Organization, self).mark_inactive(save=save)
|
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.
|
A team is a group of users that work on common projects.
|
||||||
'''
|
'''
|
||||||
@@ -104,7 +104,6 @@ class Team(CommonModelNameNotUnique):
|
|||||||
blank=True,
|
blank=True,
|
||||||
related_name='teams',
|
related_name='teams',
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField()
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Team Administrator',
|
role_name='Team Administrator',
|
||||||
parent_role='organization.admin_role',
|
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.base import * # noqa
|
||||||
from awx.main.models.jobs import Job
|
from awx.main.models.jobs import Job
|
||||||
from awx.main.models.unified_jobs import * # noqa
|
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.utils import update_scm_url
|
||||||
from awx.main.fields import ImplicitResourceField, ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
|
|
||||||
__all__ = ['Project', 'ProjectUpdate']
|
__all__ = ['Project', 'ProjectUpdate']
|
||||||
|
|
||||||
@@ -186,7 +187,7 @@ class ProjectOptions(models.Model):
|
|||||||
return sorted(results, key=lambda x: smart_str(x).lower())
|
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
|
A project represents a playbook git repo that can access a set of inventories
|
||||||
'''
|
'''
|
||||||
@@ -214,7 +215,6 @@ class Project(UnifiedJobTemplate, ProjectOptions):
|
|||||||
default=0,
|
default=0,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
resource = ImplicitResourceField()
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Project Administrator',
|
role_name='Project Administrator',
|
||||||
parent_role='organization.admin_role',
|
parent_role='organization.admin_role',
|
||||||
|
|||||||
Reference in New Issue
Block a user