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:
Chris Church
2013-07-22 21:20:28 -04:00
parent 5ef8a48600
commit bc49627203
15 changed files with 558 additions and 326 deletions

View File

@@ -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.