mirror of
https://github.com/ansible/awx.git
synced 2026-07-09 15:28:05 -02:30
Permissions-related updates and fixes, more tests, better handling of any user input that would previously generate a server error.
This commit is contained in:
@@ -13,6 +13,7 @@ from django.contrib.auth.models import User
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
# AWX
|
||||
from awx.main.utils import *
|
||||
from awx.main.models import *
|
||||
from awx.main.licenses import LicenseReader
|
||||
|
||||
@@ -140,15 +141,25 @@ class BaseAccess(object):
|
||||
return self.can_change(obj, None)
|
||||
|
||||
class UserAccess(BaseAccess):
|
||||
'''
|
||||
I can see user records when:
|
||||
- I'm a superuser.
|
||||
- I'm that user.
|
||||
- I'm their org admin.
|
||||
- I'm on a team with that user.
|
||||
I can change some fields for a user (mainly password) when I am that user.
|
||||
I can change all fields for a user (admin access) or delete when:
|
||||
- I'm a superuser.
|
||||
- I'm their org admin.
|
||||
'''
|
||||
|
||||
model = User
|
||||
|
||||
def get_queryset(self):
|
||||
# I can see user records when I'm a superuser, I'm that user, I'm
|
||||
# their org admin, or I'm on a team with that user.
|
||||
qs = self.model.objects.filter(is_active=True).distinct()
|
||||
if self.user.is_superuser:
|
||||
return self.model.objects.all()
|
||||
return self.model.objects.filter(is_active=True).filter(
|
||||
return qs
|
||||
return qs.filter(
|
||||
Q(pk=self.user.pk) |
|
||||
Q(organizations__in=self.user.admin_of_organizations.all()) |
|
||||
Q(teams__in=self.user.teams.all())
|
||||
@@ -167,31 +178,40 @@ class UserAccess(BaseAccess):
|
||||
|
||||
def can_change(self, obj, data):
|
||||
# A user can be changed if they are themselves, or by org admins or
|
||||
# superusers.
|
||||
# superusers. Change permission implies changing only certain fields
|
||||
# that a user should be able to edit for themselves.
|
||||
return bool(self.user == obj or self.can_admin(obj, data))
|
||||
|
||||
def can_admin(self, obj, data):
|
||||
# Admin implies changing all user fields.
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if self.user == obj:
|
||||
return 'partial'
|
||||
return bool(obj.organizations.filter(admins__in=[self.user]).count())
|
||||
|
||||
def can_delete(self, obj):
|
||||
if obj == self.user:
|
||||
# cannot delete yourself
|
||||
return False
|
||||
super_users = User.objects.filter(is_superuser=True)
|
||||
super_users = User.objects.filter(is_active=True, is_superuser=True)
|
||||
if obj.is_superuser and super_users.count() == 1:
|
||||
# cannot delete the last superuser
|
||||
# cannot delete the last active superuser
|
||||
return False
|
||||
return bool(self.user.is_superuser or
|
||||
obj.organizations.filter(admins__in=[self.user]).count())
|
||||
|
||||
class OrganizationAccess(BaseAccess):
|
||||
'''
|
||||
I can see organizations when:
|
||||
- I am a superuser.
|
||||
- I am an admin or user in that organization.
|
||||
I can change or delete organizations when:
|
||||
- I am a superuser.
|
||||
- I'm an admin of that organization.
|
||||
'''
|
||||
|
||||
model = Organization
|
||||
|
||||
def get_queryset(self):
|
||||
# I can see organizations when I am a superuser, or I am an admin or
|
||||
# user in that organization.
|
||||
qs = self.model.objects.distinct()
|
||||
if self.user.is_superuser:
|
||||
return qs
|
||||
@@ -203,146 +223,103 @@ class OrganizationAccess(BaseAccess):
|
||||
|
||||
def can_change(self, obj, data):
|
||||
return bool(self.user.is_superuser or
|
||||
obj.created_by == self.user or
|
||||
self.user in obj.admins.all())
|
||||
|
||||
def can_delete(self, obj):
|
||||
return self.can_change(obj, None)
|
||||
|
||||
class InventoryAccess(BaseAccess):
|
||||
'''
|
||||
I can see inventory when:
|
||||
- I'm a superuser.
|
||||
- I'm an org admin of the inventory's org.
|
||||
- I have read, write or admin permissions on it.
|
||||
I can change inventory when:
|
||||
- I'm a superuser.
|
||||
- I'm an org admin of the inventory's org.
|
||||
- I have write or admin permissions on it.
|
||||
I can delete inventory when:
|
||||
- I'm a superuser.
|
||||
- I'm an org admin of the inventory's org.
|
||||
- I have admin permissions on it.
|
||||
'''
|
||||
|
||||
model = Inventory
|
||||
|
||||
def get_queryset(self):
|
||||
# I can see inventory when I'm a superuser, an org admin of the
|
||||
# inventory, or I have permissions on it.
|
||||
base = Inventory.objects.distinct()
|
||||
def get_queryset(self, allowed=None):
|
||||
allowed = allowed or PERMISSION_TYPES_ALLOWING_INVENTORY_READ
|
||||
qs = Inventory.objects.filter(active=True).distinct()
|
||||
if self.user.is_superuser:
|
||||
return base.all()
|
||||
admin_of = base.filter(organization__admins__in = [ self.user ]).distinct()
|
||||
has_user_perms = base.filter(
|
||||
permissions__user__in = [ self.user ],
|
||||
permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||
return qs
|
||||
admin_of = qs.filter(organization__admins__in=[self.user]).distinct()
|
||||
has_user_perms = qs.filter(
|
||||
permissions__user__in=[self.user],
|
||||
permissions__permission_type__in=allowed,
|
||||
).distinct()
|
||||
has_team_perms = base.filter(
|
||||
permissions__team__in = self.user.teams.all(),
|
||||
permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||
has_team_perms = qs.filter(
|
||||
permissions__team__users__in=[self.user],
|
||||
permissions__permission_type__in=allowed,
|
||||
).distinct()
|
||||
return admin_of | has_user_perms | has_team_perms
|
||||
|
||||
def _has_permission_types(self, obj, allowed):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
by_org_admin = obj.organization.admins.filter(pk = self.user.pk).count()
|
||||
by_team_permission = obj.permissions.filter(
|
||||
team__in = self.user.teams.all(),
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
by_user_permission = obj.permissions.filter(
|
||||
user = self.user,
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
|
||||
result = (by_org_admin + by_team_permission + by_user_permission)
|
||||
return result > 0
|
||||
|
||||
def _has_any_inventory_permission_types(self, allowed):
|
||||
'''
|
||||
rather than checking for a permission on a specific inventory, return whether we have
|
||||
permissions on any inventory. This is primarily used to decide if the user can create
|
||||
host or group objects
|
||||
'''
|
||||
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
by_org_admin = self.user.organizations.filter(
|
||||
admins__in = [ self.user ]
|
||||
).count()
|
||||
by_team_permission = Permission.objects.filter(
|
||||
team__in = self.user.teams.all(),
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
by_user_permission = self.user.permissions.filter(
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
|
||||
result = (by_org_admin + by_team_permission + by_user_permission)
|
||||
return result > 0
|
||||
def has_permission_types(self, obj, allowed):
|
||||
return bool(obj and self.get_queryset(allowed).filter(pk=obj.pk).count())
|
||||
|
||||
def can_read(self, obj):
|
||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_READ)
|
||||
return self.has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_READ)
|
||||
|
||||
def can_add(self, data):
|
||||
if not 'organization' in data:
|
||||
return True
|
||||
# If no data is specified, just checking for generic add permission?
|
||||
if not data:
|
||||
return bool(self.user.is_superuser or self.user.admin_of_organizations.count())
|
||||
# Otherwise, verify that the user has access to change the parent
|
||||
# organization of this inventory.
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if not self.user.is_superuser:
|
||||
org = Organization.objects.get(pk=data['organization'])
|
||||
if self.user in org.admins.all():
|
||||
else:
|
||||
org = get_object_or_400(Organization, pk=data.get('organization', None))
|
||||
if self.user.can_access(Organization, 'change', org, None):
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_change(self, obj, data):
|
||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
# Verify that the user has access to the given organization.
|
||||
if data and 'organization' in data and not self.can_add(data):
|
||||
return False
|
||||
return self.has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
|
||||
def can_admin(self, obj, data):
|
||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_ADMIN)
|
||||
# Verify that the user has access to the given organization.
|
||||
if data and 'organization' in data and not self.can_add(data):
|
||||
return False
|
||||
return self.has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_ADMIN)
|
||||
|
||||
def can_delete(self, obj):
|
||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_ADMIN)
|
||||
|
||||
def can_attach(self, obj, sub_obj, relationship, data,
|
||||
skip_sub_obj_read_check=False):
|
||||
''' whether you can add sub_obj to obj using the relationship type in a subobject view '''
|
||||
#if not sub_obj.can_user_read(user, sub_obj):
|
||||
if sub_obj and not skip_sub_obj_read_check:
|
||||
if not self.user.can_access(type(sub_obj), 'read', sub_obj):
|
||||
return False
|
||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
|
||||
def can_unattach(self, obj, sub_obj, relationship):
|
||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
return self.can_admin(obj, None)
|
||||
|
||||
class HostAccess(BaseAccess):
|
||||
'''
|
||||
I can see hosts whenever I can see their inventory.
|
||||
I can change or delete hosts whenver I can change their inventory.
|
||||
'''
|
||||
|
||||
model = Host
|
||||
|
||||
def get_queryset(self):
|
||||
'''
|
||||
I can see hosts when:
|
||||
I'm a superuser,
|
||||
or an organization admin of an inventory they are in
|
||||
or when I have allowing read permissions via a user or team on an inventory they are in
|
||||
'''
|
||||
base = self.model.objects
|
||||
if self.user.is_superuser:
|
||||
return base.all()
|
||||
admin_of = base.filter(inventory__organization__admins__in = [ self.user ]).distinct()
|
||||
has_user_perms = base.filter(
|
||||
inventory__permissions__user__in = [ self.user ],
|
||||
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||
).distinct()
|
||||
has_team_perms = base.filter(
|
||||
inventory__permissions__team__in = self.user.teams.all(),
|
||||
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||
).distinct()
|
||||
return admin_of | has_user_perms | has_team_perms
|
||||
qs = self.model.objects.filter(active=True).distinct()
|
||||
inventories_qs = self.user.get_queryset(Inventory)
|
||||
return qs.filter(inventory__in=inventories_qs)
|
||||
|
||||
def can_read(self, obj):
|
||||
return self.user.can_access(Inventory, 'read', obj.inventory)
|
||||
return obj and self.user.can_access(Inventory, 'read', obj.inventory)
|
||||
|
||||
def can_add(self, data):
|
||||
|
||||
|
||||
if not 'inventory' in data:
|
||||
if not data or not 'inventory' in data:
|
||||
return False
|
||||
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
|
||||
# Checks for admin or change permission on inventory.
|
||||
permissions_ok = self.user.can_access(Inventory, 'change', inventory, None)
|
||||
if not permissions_ok:
|
||||
inventory = get_object_or_400(Inventory, pk=data.get('inventory', None))
|
||||
if not self.user.can_access(Inventory, 'change', inventory, None):
|
||||
return False
|
||||
|
||||
# Check to see if we have enough licenses
|
||||
@@ -361,57 +338,46 @@ class HostAccess(BaseAccess):
|
||||
raise PermissionDenied("license range of %s instances has been exceed" % instances)
|
||||
|
||||
def can_change(self, obj, data):
|
||||
# Prevent moving a host to a different inventory.
|
||||
if obj and data and obj.inventory.pk != data.get('inventory', None):
|
||||
raise PermissionDenied('Unable to change inventory on a host')
|
||||
# Checks for admin or change permission on inventory, controls whether
|
||||
# the user can edit variable data.
|
||||
return self.user.can_access(Inventory, 'change', obj.inventory, None)
|
||||
return obj and self.user.can_access(Inventory, 'change', obj.inventory, None)
|
||||
|
||||
class GroupAccess(BaseAccess):
|
||||
'''
|
||||
I can see groups whenever I can see their inventory.
|
||||
I can change or delete groups whenever I can change their inventory.
|
||||
'''
|
||||
|
||||
model = Group
|
||||
|
||||
def get_queryset(self):
|
||||
'''
|
||||
I can see groups when:
|
||||
I'm a superuser,
|
||||
or an organization admin of an inventory they are in
|
||||
or when I have allowing read permissions via a user or team on an inventory they are in
|
||||
'''
|
||||
base = Group.objects
|
||||
if self.user.is_superuser:
|
||||
return base.distinct()
|
||||
admin_of = base.filter(inventory__organization__admins__in = [ self.user ]).distinct()
|
||||
has_user_perms = base.filter(
|
||||
inventory__permissions__user__in = [ self.user ],
|
||||
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||
).distinct()
|
||||
has_team_perms = base.filter(
|
||||
inventory__permissions__team__in = self.user.teams.all(),
|
||||
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||
).distinct()
|
||||
return admin_of | has_user_perms | has_team_perms
|
||||
qs = self.model.objects.filter(active=True).distinct()
|
||||
inventories_qs = self.user.get_queryset(Inventory)
|
||||
return qs.filter(inventory__in=inventories_qs)
|
||||
|
||||
def can_read(self, obj):
|
||||
return self.user.can_access(Inventory, 'read', obj.inventory)
|
||||
return obj and self.user.can_access(Inventory, 'read', obj.inventory)
|
||||
|
||||
def can_add(self, data):
|
||||
if not 'inventory' in data:
|
||||
if not data or not 'inventory' in data:
|
||||
return False
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
# Checks for admin or change permission on inventory.
|
||||
inventory = get_object_or_400(Inventory, pk=data.get('inventory', None))
|
||||
return self.user.can_access(Inventory, 'change', inventory, None)
|
||||
|
||||
def can_change(self, obj, data):
|
||||
# Checks for admin or change permission on inventory, controls whether
|
||||
# the user can attach subgroups or edit variable data.
|
||||
return self.user.can_access(Inventory, 'change', obj.inventory, None)
|
||||
return obj and self.user.can_access(Inventory, 'change', obj.inventory, None)
|
||||
|
||||
def can_attach(self, obj, sub_obj, relationship, data,
|
||||
skip_sub_obj_read_check=False):
|
||||
if not self.can_change(obj, None):
|
||||
if not super(GroupAccess, self).can_attach(obj, sub_obj, relationship,
|
||||
data, skip_sub_obj_read_check):
|
||||
return False
|
||||
if sub_obj and not skip_sub_obj_read_check:
|
||||
if not self.user.can_access(type(sub_obj), 'read', sub_obj):
|
||||
return False
|
||||
|
||||
# Prevent group from being assigned as its own (grand)child.
|
||||
if type(obj) == type(sub_obj):
|
||||
@@ -425,22 +391,33 @@ class GroupAccess(BaseAccess):
|
||||
return True
|
||||
|
||||
class CredentialAccess(BaseAccess):
|
||||
'''
|
||||
I can see credentials when:
|
||||
- I'm a superuser.
|
||||
- It's a user credential and it's my credential.
|
||||
- It's a user credential and I'm an admin of an organization where that
|
||||
user is a member of admin of the organization.
|
||||
- It's a team credential and I'm an admin of the team's organization.
|
||||
- It's a team credential and I'm a member of the team.
|
||||
'''
|
||||
|
||||
model = Credential
|
||||
|
||||
def get_queryset(self):
|
||||
# I can see credentials when:
|
||||
# - It's a user credential and it's my credential.
|
||||
# - It's a user credential and I'm an admin of an organization
|
||||
# -
|
||||
# FIXME
|
||||
qs = self.model.objects.distinct()
|
||||
qs = self.model.objects.filter(active=True).distinct()
|
||||
if self.user.is_superuser:
|
||||
return qs
|
||||
return qs.filter(Q(user=self.user))
|
||||
orgs_as_admin = self.user.admin_of_organizations.all()
|
||||
return qs.filter(
|
||||
Q(user=self.user) |
|
||||
Q(user__organizations__in=orgs_as_admin) |
|
||||
Q(user__admin_of_organizations__in=orgs_as_admin) |
|
||||
Q(team__organization__in=orgs_as_admin) |
|
||||
Q(team__users__in=[self.user])
|
||||
)
|
||||
|
||||
def can_read(self, obj):
|
||||
return self.can_change(obj, None)
|
||||
return obj and self.can_change(obj, None)
|
||||
|
||||
def can_add(self, data):
|
||||
if self.user.is_superuser:
|
||||
@@ -476,7 +453,17 @@ class TeamAccess(BaseAccess):
|
||||
model = Team
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.distinct() # FIXME
|
||||
# I can see a team when:
|
||||
# - I'm a superuser.
|
||||
# - I'm an admin of the team's organization.
|
||||
# - I'm a member of that team.
|
||||
qs = self.model.objects.filter(active=True).distinct()
|
||||
if self.user.is_superuser:
|
||||
return qs
|
||||
return qs.filter(
|
||||
Q(organization__admins__in=[self.user]) |
|
||||
Q(users__in=[self.user])
|
||||
)
|
||||
|
||||
def can_add(self, data):
|
||||
if self.user.is_superuser:
|
||||
@@ -587,13 +574,16 @@ class JobTemplateAccess(BaseAccess):
|
||||
project's orgs, or if I'm in a team on the project. This does not mean
|
||||
I would be able to launch a job from the template or edit the template.
|
||||
'''
|
||||
# FIXME: Don't think this is quite right...
|
||||
qs = self.model.objects.all()
|
||||
if self.user.is_superuser:
|
||||
return qs.all()
|
||||
return qs.filter(active=True).filter(
|
||||
qs = qs.filter(active=True).filter(
|
||||
Q(project__organizations__admins__in=[self.user]) |
|
||||
Q(project__teams__users__in=[self.user])
|
||||
).distinct()
|
||||
#print qs.values_list('name', flat=True)
|
||||
return qs
|
||||
|
||||
def can_read(self, obj):
|
||||
# you can only see the job templates that you have permission to launch.
|
||||
|
||||
Reference in New Issue
Block a user