mirror of
https://github.com/ansible/awx.git
synced 2026-01-18 13:11:19 -03: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:
parent
5ef8a48600
commit
bc49627203
@ -104,6 +104,10 @@ LOGGING['handlers']['syslog'] = {
|
||||
# 'formatter': 'simple',
|
||||
#}
|
||||
|
||||
# Enable the following lines to turn on lots of permissions-related logging.
|
||||
#LOGGING['loggers']['awx.main.access']['propagate'] = True
|
||||
#LOGGING['loggers']['awx.main.permissions']['propagate'] = True
|
||||
|
||||
# Define additional environment variables to be passed to subprocess started by
|
||||
# the celery task.
|
||||
#AWX_TASK_ENV['FOO'] = 'BAR'
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -31,9 +31,23 @@ class ListAPIView(generics.ListAPIView):
|
||||
def get_queryset(self):
|
||||
return self.request.user.get_queryset(self.model)
|
||||
|
||||
def get_description_vars(self):
|
||||
return {
|
||||
'model_verbose_name': unicode(self.model._meta.verbose_name),
|
||||
'model_verbose_name_plural': unicode(self.model._meta.verbose_name_plural),
|
||||
}
|
||||
|
||||
def get_description(self, html=False):
|
||||
s = 'Use a GET request to retrieve a list of %(model_verbose_name_plural)s.'
|
||||
return s % self.get_description_vars()
|
||||
|
||||
class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
|
||||
# Base class for a list view that allows creating new objects.
|
||||
pass
|
||||
|
||||
def get_description(self, html=False):
|
||||
s = 'Use a GET request to retrieve a list of %(model_verbose_name_plural)s.'
|
||||
s2 = 'Use a POST request with required %(model_verbose_name)s fields to create a new %(model_verbose_name)s.'
|
||||
return '\n\n'.join([s, s2]) % self.get_description_vars()
|
||||
|
||||
class SubListAPIView(ListAPIView):
|
||||
# Base class for a read-only sublist view.
|
||||
@ -47,6 +61,18 @@ class SubListAPIView(ListAPIView):
|
||||
# to view sublist):
|
||||
# parent_access = 'admin'
|
||||
|
||||
def get_description_vars(self):
|
||||
d = super(SubListAPIView, self).get_description_vars()
|
||||
d.update({
|
||||
'parent_model_verbose_name': unicode(self.parent_model._meta.verbose_name),
|
||||
'parent_model_verbose_name_plural': unicode(self.parent_model._meta.verbose_name_plural),
|
||||
})
|
||||
return d
|
||||
|
||||
def get_description(self, html=False):
|
||||
s = 'Use a GET request to retrieve a list of %(model_verbose_name_plural)s associated with the selected %(parent_model_verbose_name)s.'
|
||||
return s % self.get_description_vars()
|
||||
|
||||
def get_parent_object(self):
|
||||
parent_filter = {
|
||||
self.lookup_field: self.kwargs.get(self.lookup_field, None),
|
||||
@ -70,13 +96,23 @@ class SubListAPIView(ListAPIView):
|
||||
sublist_qs = getattr(parent, self.relationship).distinct()
|
||||
return qs & sublist_qs
|
||||
|
||||
class SubListCreateAPIView(SubListAPIView, generics.ListCreateAPIView):
|
||||
class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||
# Base class for a sublist view that allows for creating subobjects and
|
||||
# attaching/detaching them from the parent.
|
||||
|
||||
# In addition to SubListAPIView properties, subclasses may define:
|
||||
# inject_primary_key_on_post_as = 'field_on_model_referring_to_parent'
|
||||
# severable = True/False
|
||||
# parent_key = 'field_on_model_referring_to_parent'
|
||||
|
||||
def get_description(self, html=False):
|
||||
s = 'Use a GET request to retrieve a list of %(model_verbose_name_plural)s associated with the selected %(parent_model_verbose_name)s.'
|
||||
s2 = 'Use a POST request with required %(model_verbose_name)s fields to create a new %(model_verbose_name)s.'
|
||||
if getattr(self, 'parent_key', None):
|
||||
s3 = 'Use a POST request with an `id` field and `disassociate` set to delete the associated %(model_verbose_name)s.'
|
||||
s4 = ''
|
||||
else:
|
||||
s3 = 'Use a POST request with only an `id` field to associate an existing %(model_verbose_name)s with this %(parent_model_verbose_name)s.'
|
||||
s4 = 'Use a POST request with an `id` field and `disassociate` set to remove the %(model_verbose_name)s from this %(parent_model_verbose_name)s without deleting the %(model_verbose_name)s.'
|
||||
return '\n\n'.join(filter(None, [s, s2, s3, s4])) % self.get_description_vars()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# If the object ID was not specified, it probably doesn't exist in the
|
||||
@ -84,13 +120,6 @@ class SubListCreateAPIView(SubListAPIView, generics.ListCreateAPIView):
|
||||
# inject it's primary key into the object because we are posting to a
|
||||
# subcollection. Use all the normal access control mechanisms.
|
||||
|
||||
inject_primary_key = getattr(self, 'inject_primary_key_on_post_as', None)
|
||||
|
||||
if inject_primary_key is None:
|
||||
# view didn't specify a way to get the pk from the URL, so not even trying
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST,
|
||||
data=dict(msg='object cannot be created'))
|
||||
|
||||
# Make a copy of the data provided (since it's readonly) in order to
|
||||
# inject additional data.
|
||||
if hasattr(request.DATA, 'dict'):
|
||||
@ -98,8 +127,10 @@ class SubListCreateAPIView(SubListAPIView, generics.ListCreateAPIView):
|
||||
else:
|
||||
data = request.DATA
|
||||
|
||||
# add the key to the post data using the pk from the URL
|
||||
data[inject_primary_key] = kwargs['pk']
|
||||
# add the parent key to the post data using the pk from the URL
|
||||
parent_key = getattr(self, 'parent_key', None)
|
||||
if parent_key:
|
||||
data[parent_key] = self.kwargs['pk']
|
||||
|
||||
# attempt to deserialize the object
|
||||
serializer = self.serializer_class(data=data)
|
||||
@ -160,7 +191,7 @@ class SubListCreateAPIView(SubListAPIView, generics.ListCreateAPIView):
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
parent = self.get_parent_object()
|
||||
severable = getattr(self, 'severable', True)
|
||||
parent_key = getattr(self, 'parent_key', None)
|
||||
relationship = getattr(parent, self.relationship)
|
||||
|
||||
try:
|
||||
@ -172,11 +203,12 @@ class SubListCreateAPIView(SubListAPIView, generics.ListCreateAPIView):
|
||||
if not request.user.can_access(self.parent_model, 'unattach', parent, sub, self.relationship):
|
||||
raise PermissionDenied()
|
||||
|
||||
if severable:
|
||||
relationship.remove(sub)
|
||||
else:
|
||||
# resource is just a ForeignKey, can't remove it from the set, just set it inactive
|
||||
if parent_key:
|
||||
# sub object has a ForeignKey to the parent, so we can't remove it
|
||||
# from the set, only mark it as inactive.
|
||||
sub.mark_inactive()
|
||||
else:
|
||||
relationship.remove(sub)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@ -186,7 +218,6 @@ class SubListCreateAPIView(SubListAPIView, generics.ListCreateAPIView):
|
||||
else:
|
||||
return self.attach(request, *args, **kwargs)
|
||||
|
||||
|
||||
class RetrieveAPIView(generics.RetrieveAPIView):
|
||||
pass
|
||||
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Django
|
||||
from django.core.exceptions import FieldError
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
|
||||
class DefaultFilterBackend(BaseFilterBackend):
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
|
||||
terms = {}
|
||||
order_by = None
|
||||
|
||||
# Filtering by is_active/active that was previously in BaseList.
|
||||
qs = queryset
|
||||
for field in queryset.model._meta.fields:
|
||||
@ -19,25 +20,34 @@ class DefaultFilterBackend(BaseFilterBackend):
|
||||
elif field.name == 'active':
|
||||
qs = qs.filter(active=True)
|
||||
|
||||
for key, value in request.QUERY_PARAMS.items():
|
||||
# Apply filters and ordering specified via QUERY_PARAMS.
|
||||
try:
|
||||
|
||||
if key in [ 'page', 'page_size', 'format' ]:
|
||||
continue
|
||||
filters = {}
|
||||
order_by = None
|
||||
|
||||
if key in ('order', 'order_by'):
|
||||
order_by = value
|
||||
continue
|
||||
for key, value in request.QUERY_PARAMS.items():
|
||||
|
||||
key2 = key
|
||||
if key2.endswith("__int"):
|
||||
key2 = key.replace("__int","")
|
||||
value = int(value)
|
||||
if key in ('page', 'page_size', 'format'):
|
||||
continue
|
||||
|
||||
terms[key2] = value
|
||||
if key in ('order', 'order_by'):
|
||||
order_by = value
|
||||
continue
|
||||
|
||||
qs = qs.filter(**terms)
|
||||
if key.endswith('__int'):
|
||||
key = key.replace('__int', '')
|
||||
value = int(value)
|
||||
|
||||
if order_by:
|
||||
qs = qs.order_by(order_by)
|
||||
filters[key] = value
|
||||
|
||||
qs = qs.filter(**filters)
|
||||
|
||||
if order_by:
|
||||
qs = qs.order_by(order_by)
|
||||
|
||||
except (FieldError, ValueError), e:
|
||||
# Handle invalid field names or values and return a 400.
|
||||
raise ParseError(*e.args)
|
||||
|
||||
return qs
|
||||
|
||||
@ -898,7 +898,7 @@ class JobHostSummary(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = [('job', 'host')]
|
||||
verbose_name_plural = _('Job Host Summaries')
|
||||
verbose_name_plural = _('job host summaries')
|
||||
ordering = ('-pk',)
|
||||
|
||||
job = models.ForeignKey(
|
||||
|
||||
@ -124,13 +124,15 @@ class BaseTestMixin(object):
|
||||
self.normal_password = 'normal'
|
||||
self.other_username = 'other'
|
||||
self.other_password = 'other'
|
||||
self.nobody_username = 'nobody'
|
||||
self.nobody_password = 'nobody'
|
||||
|
||||
self.super_django_user = self.make_user(self.super_username, self.super_password, super_user=True)
|
||||
|
||||
if not just_super_user:
|
||||
|
||||
self.normal_django_user = self.make_user(self.normal_username, self.normal_password, super_user=False)
|
||||
self.other_django_user = self.make_user(self.other_username, self.other_password, super_user=False)
|
||||
self.nobody_django_user = self.make_user(self.nobody_username, self.nobody_password, super_user=False)
|
||||
|
||||
def get_super_credentials(self):
|
||||
return (self.super_username, self.super_password)
|
||||
@ -141,6 +143,10 @@ class BaseTestMixin(object):
|
||||
def get_other_credentials(self):
|
||||
return (self.other_username, self.other_password)
|
||||
|
||||
def get_nobody_credentials(self):
|
||||
# here is a user without any permissions...
|
||||
return (self.nobody_username, self.nobody_password)
|
||||
|
||||
def get_invalid_credentials(self):
|
||||
return ('random', 'combination')
|
||||
|
||||
@ -239,6 +245,39 @@ class BaseTestMixin(object):
|
||||
data = self.get(collection_url, expect=200, auth=auth)
|
||||
return [item['url'] for item in data['results']]
|
||||
|
||||
def check_invalid_auth(self, url, data=None, methods=None):
|
||||
'''
|
||||
Check various methods of accessing the given URL with invalid
|
||||
authentication credentials.
|
||||
'''
|
||||
data = data or {}
|
||||
methods = methods or ('options', 'head', 'get')
|
||||
for auth in [(None,), ('invalid', 'password')]:
|
||||
with self.current_user(*auth):
|
||||
for method in methods:
|
||||
f = getattr(self, method)
|
||||
if method in ('post', 'put', 'patch'):
|
||||
f(url, data, expect=401)
|
||||
else:
|
||||
f(url, expect=401)
|
||||
|
||||
def check_get_list(self, url, user, qs, fields=None, expect=200):
|
||||
'''
|
||||
Check that the given list view URL returns results for the given user
|
||||
that match the given queryset.
|
||||
'''
|
||||
with self.current_user(user):
|
||||
self.options(url, expect=expect)
|
||||
self.head(url, expect=expect)
|
||||
response = self.get(url, expect=expect)
|
||||
if expect != 200:
|
||||
return
|
||||
self.check_pagination_and_size(response, qs.count())
|
||||
self.check_list_ids(response, qs)
|
||||
if fields:
|
||||
for obj in response['results']:
|
||||
self.assertTrue(set(obj.keys()) <= set(fields))
|
||||
|
||||
class BaseTest(BaseTestMixin, django.test.TestCase):
|
||||
'''
|
||||
Base class for unit tests.
|
||||
|
||||
@ -33,15 +33,137 @@ class InventoryTest(BaseTest):
|
||||
permission_type = 'read'
|
||||
)
|
||||
|
||||
# and make one more user that won't be a part of any org, just for negative-access testing
|
||||
def test_get_inventory_list(self):
|
||||
url = reverse('main:inventory_list')
|
||||
qs = Inventory.objects.filter(active=True).distinct()
|
||||
|
||||
self.nobody_django_user = User.objects.create(username='nobody')
|
||||
self.nobody_django_user.set_password('nobody')
|
||||
self.nobody_django_user.save()
|
||||
# Check list view with invalid authentication.
|
||||
self.check_invalid_auth(url)
|
||||
|
||||
def get_nobody_credentials(self):
|
||||
# here is a user without any permissions...
|
||||
return ('nobody', 'nobody')
|
||||
# a super user can list all inventories
|
||||
self.check_get_list(url, self.super_django_user, qs)
|
||||
|
||||
# an org admin can list inventories but is filtered to what he adminsters
|
||||
normal_qs = qs.filter(organization__admins__in=[self.normal_django_user])
|
||||
self.check_get_list(url, self.normal_django_user, normal_qs)
|
||||
|
||||
# a user who is on a team who has a read permissions on an inventory can see filtered inventories
|
||||
other_qs = qs.filter(permissions__user__in=[self.other_django_user])
|
||||
self.check_get_list(url, self.other_django_user, other_qs)
|
||||
|
||||
# a regular user not part of anything cannot see any inventories
|
||||
nobody_qs = qs.none()
|
||||
self.check_get_list(url, self.nobody_django_user, nobody_qs)
|
||||
|
||||
def test_post_inventory_list(self):
|
||||
url = reverse('main:inventory_list')
|
||||
|
||||
# Check post to list view with invalid authentication.
|
||||
new_inv_0 = dict(name='inventory-c', description='baz', organization=self.organizations[0].pk)
|
||||
self.check_invalid_auth(url, new_inv_0, methods=('post',))
|
||||
|
||||
# a super user can create inventory
|
||||
new_inv_1 = dict(name='inventory-c', description='baz', organization=self.organizations[0].pk)
|
||||
new_id = max(Inventory.objects.values_list('pk', flat=True)) + 1
|
||||
with self.current_user(self.super_django_user):
|
||||
data = self.post(url, data=new_inv_1, expect=201)
|
||||
self.assertEquals(data['id'], new_id)
|
||||
|
||||
# an org admin of any org can create inventory, if it is one of his organizations
|
||||
# the organization parameter is required!
|
||||
new_inv_incomplete = dict(name='inventory-d', description='baz')
|
||||
new_inv_not_my_org = dict(name='inventory-d', description='baz', organization=self.organizations[2].pk)
|
||||
new_inv_my_org = dict(name='inventory-d', description='baz', organization=self.organizations[0].pk)
|
||||
with self.current_user(self.normal_django_user):
|
||||
data = self.post(url, data=new_inv_incomplete, expect=400)
|
||||
data = self.post(url, data=new_inv_not_my_org, expect=403)
|
||||
data = self.post(url, data=new_inv_my_org, expect=201)
|
||||
|
||||
# a regular user cannot create inventory
|
||||
new_inv_denied = dict(name='inventory-e', description='glorp', organization=self.organizations[0].pk)
|
||||
with self.current_user(self.other_django_user):
|
||||
data = self.post(url, data=new_inv_denied, expect=403)
|
||||
|
||||
def test_get_inventory_detail(self):
|
||||
url_a = reverse('main:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('main:inventory_detail', args=(self.inventory_b.pk,))
|
||||
|
||||
# Check detail view with invalid authentication.
|
||||
self.check_invalid_auth(url_a)
|
||||
self.check_invalid_auth(url_b)
|
||||
|
||||
# a super user can get inventory records
|
||||
with self.current_user(self.super_django_user):
|
||||
data = self.get(url_a, expect=200)
|
||||
self.assertEquals(data['name'], 'inventory-a')
|
||||
|
||||
# an org admin can get inventory records for his orgs only
|
||||
with self.current_user(self.normal_django_user):
|
||||
data = self.get(url_a, expect=200)
|
||||
self.assertEquals(data['name'], 'inventory-a')
|
||||
data = self.get(url_b, expect=403)
|
||||
|
||||
# a user who is on a team who has read permissions on an inventory can see inventory records
|
||||
with self.current_user(self.other_django_user):
|
||||
data = self.get(url_a, expect=403)
|
||||
data = self.get(url_b, expect=200)
|
||||
self.assertEquals(data['name'], 'inventory-b')
|
||||
|
||||
# a regular user cannot read any inventory records
|
||||
with self.current_user(self.nobody_django_user):
|
||||
data = self.get(url_a, expect=403)
|
||||
data = self.get(url_b, expect=403)
|
||||
|
||||
def test_put_inventory_detail(self):
|
||||
url_a = reverse('main:inventory_detail', args=(self.inventory_a.pk,))
|
||||
url_b = reverse('main:inventory_detail', args=(self.inventory_b.pk,))
|
||||
|
||||
# Check put to detail view with invalid authentication.
|
||||
self.check_invalid_auth(url_a, methods=('put',))
|
||||
self.check_invalid_auth(url_b, methods=('put',))
|
||||
|
||||
# a super user can update inventory records
|
||||
with self.current_user(self.super_django_user):
|
||||
data = self.get(url_a, expect=200)
|
||||
data['name'] = 'inventory-a-update1'
|
||||
self.put(url_a, data, expect=200)
|
||||
data = self.get(url_b, expect=200)
|
||||
data['name'] = 'inventory-b-update1'
|
||||
self.put(url_b, data, expect=200)
|
||||
|
||||
# an org admin can update inventory records for his orgs only.
|
||||
with self.current_user(self.normal_django_user):
|
||||
data = self.get(url_a, expect=200)
|
||||
data['name'] = 'inventory-a-update2'
|
||||
self.put(url_a, data, expect=200)
|
||||
self.put(url_b, data, expect=403)
|
||||
|
||||
# a user who is on a team who has read permissions on an inventory can
|
||||
# see inventory records, but not update.
|
||||
with self.current_user(self.other_django_user):
|
||||
data = self.get(url_b, expect=200)
|
||||
data['name'] = 'inventory-b-update3'
|
||||
self.put(url_b, data, expect=403)
|
||||
|
||||
# a regular user cannot update any inventory records
|
||||
with self.current_user(self.nobody_django_user):
|
||||
self.put(url_a, {}, expect=403)
|
||||
self.put(url_b, {}, expect=403)
|
||||
|
||||
# a superuser can reassign an inventory to another organization.
|
||||
with self.current_user(self.super_django_user):
|
||||
data = self.get(url_b, expect=200)
|
||||
self.assertEqual(data['organization'], self.organizations[1].pk)
|
||||
data['organization'] = self.organizations[0].pk
|
||||
self.put(url_b, data, expect=200)
|
||||
|
||||
# a normal user can't reassign an inventory to an organization where
|
||||
# he isn't an admin.
|
||||
with self.current_user(self.normal_django_user):
|
||||
data = self.get(url_a, expect=200)
|
||||
self.assertEqual(data['organization'], self.organizations[0].pk)
|
||||
data['organization'] = self.organizations[1].pk
|
||||
self.put(url_a, data, expect=403)
|
||||
|
||||
def test_main_line(self):
|
||||
|
||||
@ -53,57 +175,57 @@ class InventoryTest(BaseTest):
|
||||
groups = reverse('main:group_list')
|
||||
|
||||
# a super user can list inventories
|
||||
data = self.get(inventories, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEquals(data['count'], 2)
|
||||
#data = self.get(inventories, expect=200, auth=self.get_super_credentials())
|
||||
#self.assertEquals(data['count'], 2)
|
||||
|
||||
# an org admin can list inventories but is filtered to what he adminsters
|
||||
data = self.get(inventories, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['count'], 1)
|
||||
#data = self.get(inventories, expect=200, auth=self.get_normal_credentials())
|
||||
#self.assertEquals(data['count'], 1)
|
||||
|
||||
# a user who is on a team who has a read permissions on an inventory can see filtered inventories
|
||||
data = self.get(inventories, expect=200, auth=self.get_other_credentials())
|
||||
self.assertEquals(data['count'], 1)
|
||||
#data = self.get(inventories, expect=200, auth=self.get_other_credentials())
|
||||
#self.assertEquals(data['count'], 1)
|
||||
|
||||
# a regular user not part of anything cannot see any inventories
|
||||
data = self.get(inventories, expect=200, auth=self.get_nobody_credentials())
|
||||
self.assertEquals(data['count'], 0)
|
||||
#data = self.get(inventories, expect=200, auth=self.get_nobody_credentials())
|
||||
#self.assertEquals(data['count'], 0)
|
||||
|
||||
# a super user can get inventory records
|
||||
data = self.get(inventories_1, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEquals(data['name'], 'inventory-a')
|
||||
#data = self.get(inventories_1, expect=200, auth=self.get_super_credentials())
|
||||
#self.assertEquals(data['name'], 'inventory-a')
|
||||
|
||||
# an org admin can get inventory records
|
||||
data = self.get(inventories_1, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data['name'], 'inventory-a')
|
||||
#data = self.get(inventories_1, expect=200, auth=self.get_normal_credentials())
|
||||
#self.assertEquals(data['name'], 'inventory-a')
|
||||
|
||||
# a user who is on a team who has read permissions on an inventory can see inventory records
|
||||
data = self.get(inventories_1, expect=403, auth=self.get_other_credentials())
|
||||
data = self.get(inventories_2, expect=200, auth=self.get_other_credentials())
|
||||
self.assertEquals(data['name'], 'inventory-b')
|
||||
#data = self.get(inventories_1, expect=403, auth=self.get_other_credentials())
|
||||
#data = self.get(inventories_2, expect=200, auth=self.get_other_credentials())
|
||||
#self.assertEquals(data['name'], 'inventory-b')
|
||||
|
||||
# a regular user cannot read any inventory records
|
||||
data = self.get(inventories_1, expect=403, auth=self.get_nobody_credentials())
|
||||
data = self.get(inventories_2, expect=403, auth=self.get_nobody_credentials())
|
||||
#data = self.get(inventories_1, expect=403, auth=self.get_nobody_credentials())
|
||||
#data = self.get(inventories_2, expect=403, auth=self.get_nobody_credentials())
|
||||
|
||||
# a super user can create inventory
|
||||
new_inv_1 = dict(name='inventory-c', description='baz', organization=self.organizations[0].pk)
|
||||
new_id = max(Inventory.objects.values_list('pk', flat=True)) + 1
|
||||
data = self.post(inventories, data=new_inv_1, expect=201, auth=self.get_super_credentials())
|
||||
self.assertEquals(data['id'], new_id)
|
||||
#new_inv_1 = dict(name='inventory-c', description='baz', organization=self.organizations[0].pk)
|
||||
#new_id = max(Inventory.objects.values_list('pk', flat=True)) + 1
|
||||
#data = self.post(inventories, data=new_inv_1, expect=201, auth=self.get_super_credentials())
|
||||
#self.assertEquals(data['id'], new_id)
|
||||
|
||||
# an org admin of any org can create inventory, if it is one of his organizations
|
||||
# the organization parameter is required!
|
||||
new_inv_incomplete = dict(name='inventory-d', description='baz')
|
||||
data = self.post(inventories, data=new_inv_incomplete, expect=400, auth=self.get_normal_credentials())
|
||||
new_inv_not_my_org = dict(name='inventory-d', description='baz', organization=self.organizations[2].pk)
|
||||
#new_inv_incomplete = dict(name='inventory-d', description='baz')
|
||||
#data = self.post(inventories, data=new_inv_incomplete, expect=400, auth=self.get_normal_credentials())
|
||||
#new_inv_not_my_org = dict(name='inventory-d', description='baz', organization=self.organizations[2].pk)
|
||||
|
||||
data = self.post(inventories, data=new_inv_not_my_org, expect=403, auth=self.get_normal_credentials())
|
||||
new_inv_my_org = dict(name='inventory-d', description='baz', organization=self.organizations[0].pk)
|
||||
data = self.post(inventories, data=new_inv_my_org, expect=201, auth=self.get_normal_credentials())
|
||||
#data = self.post(inventories, data=new_inv_not_my_org, expect=403, auth=self.get_normal_credentials())
|
||||
#new_inv_my_org = dict(name='inventory-d', description='baz', organization=self.organizations[0].pk)
|
||||
#data = self.post(inventories, data=new_inv_my_org, expect=201, auth=self.get_normal_credentials())
|
||||
|
||||
# a regular user cannot create inventory
|
||||
new_inv_denied = dict(name='inventory-e', description='glorp', organization=self.organizations[0].pk)
|
||||
data = self.post(inventories, data=new_inv_denied, expect=403, auth=self.get_other_credentials())
|
||||
#new_inv_denied = dict(name='inventory-e', description='glorp', organization=self.organizations[0].pk)
|
||||
#data = self.post(inventories, data=new_inv_denied, expect=403, auth=self.get_other_credentials())
|
||||
|
||||
# a super user can add hosts (but inventory ID is required)
|
||||
inv = Inventory.objects.create(
|
||||
|
||||
@ -12,7 +12,7 @@ import uuid
|
||||
from django.contrib.auth.models import User as DjangoUser
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
import django.test
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
@ -427,57 +427,49 @@ class BaseJobTestMixin(BaseTestMixin):
|
||||
super(BaseJobTestMixin, self).setUp()
|
||||
self.populate()
|
||||
|
||||
def _test_invalid_creds(self, url, data=None, methods=None):
|
||||
data = data or {}
|
||||
methods = methods or ('options', 'head', 'get')
|
||||
for auth in [(None,), ('invalid', 'password')]:
|
||||
with self.current_user(*auth):
|
||||
for method in methods:
|
||||
f = getattr(self, method)
|
||||
if method in ('post', 'put', 'patch'):
|
||||
f(url, data, expect=401)
|
||||
else:
|
||||
f(url, expect=401)
|
||||
|
||||
class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
|
||||
JOB_TEMPLATE_FIELDS = ('id', 'url', 'related', 'summary_fields', 'created',
|
||||
'name', 'description', 'job_type', 'inventory',
|
||||
'project', 'playbook', 'credential', 'forks',
|
||||
'limit', 'verbosity', 'extra_vars', 'job_tags',
|
||||
'host_config_key',)
|
||||
|
||||
def test_get_job_template_list(self):
|
||||
url = reverse('main:job_template_list')
|
||||
qs = JobTemplate.objects.distinct()
|
||||
fields = self.JOB_TEMPLATE_FIELDS
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
self.check_invalid_auth(url)
|
||||
|
||||
# sue's credentials (superuser) == 200, full list
|
||||
with self.current_user(self.user_sue):
|
||||
self.options(url)
|
||||
self.head(url)
|
||||
response = self.get(url)
|
||||
qs = JobTemplate.objects.all()
|
||||
self.check_pagination_and_size(response, qs.count())
|
||||
self.check_list_ids(response, qs)
|
||||
# Sue's credentials (superuser) == 200, full list
|
||||
self.check_get_list(url, self.user_sue, qs, fields)
|
||||
|
||||
# Alex's credentials (admin of all orgs) == 200, full list
|
||||
self.check_get_list(url, self.user_alex, qs, fields)
|
||||
|
||||
# FIXME: Check individual job template result fields.
|
||||
|
||||
# alex's credentials (admin of all orgs) == 200, full list
|
||||
with self.current_user(self.user_alex):
|
||||
self.options(url)
|
||||
self.head(url)
|
||||
response = self.get(url)
|
||||
qs = JobTemplate.objects.all()
|
||||
self.check_pagination_and_size(response, qs.count())
|
||||
self.check_list_ids(response, qs)
|
||||
|
||||
# bob's credentials (admin of eng, user of ops) == 200, all from
|
||||
# Bob's credentials (admin of eng, user of ops) == 200, all from
|
||||
# engineering and operations.
|
||||
with self.current_user(self.user_bob):
|
||||
self.options(url)
|
||||
self.head(url)
|
||||
response = self.get(url)
|
||||
qs = JobTemplate.objects.filter(
|
||||
inventory__organization__in=[self.org_eng, self.org_ops],
|
||||
bob_qs = qs.filter(
|
||||
Q(project__organizations__admins__in=[self.user_bob]) |
|
||||
Q(project__teams__users__in=[self.user_bob]),
|
||||
)
|
||||
#self.check_pagination_and_size(response, qs.count())
|
||||
#self.check_list_ids(response, qs)
|
||||
self.check_get_list(url, self.user_bob, bob_qs, fields)
|
||||
|
||||
# Chuck's credentials (admin of eng) == 200, all from engineering.
|
||||
chuck_qs = qs.filter(
|
||||
Q(project__organizations__admins__in=[self.user_chuck]) |
|
||||
Q(project__teams__users__in=[self.user_chuck]),
|
||||
)
|
||||
self.check_get_list(url, self.user_chuck, chuck_qs, fields)
|
||||
|
||||
# Doug's credentials (user of eng) == 200, none?.
|
||||
doug_qs = qs.filter(
|
||||
Q(project__organizations__admins__in=[self.user_doug]) |
|
||||
Q(project__teams__users__in=[self.user_doug]),
|
||||
)
|
||||
self.check_get_list(url, self.user_doug, doug_qs, fields)
|
||||
|
||||
# FIXME: Check with other credentials.
|
||||
|
||||
@ -492,7 +484,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
)
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, data, methods=('post',))
|
||||
self.check_invalid_auth(url, data, methods=('post',))
|
||||
|
||||
# sue can always add job templates.
|
||||
with self.current_user(self.user_sue):
|
||||
@ -541,7 +533,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
url = reverse('main:job_template_detail', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
self.check_invalid_auth(url)
|
||||
|
||||
# sue can read the job template detail.
|
||||
with self.current_user(self.user_sue):
|
||||
@ -562,7 +554,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
url = reverse('main:job_template_detail', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, methods=('put',))# 'patch'))
|
||||
self.check_invalid_auth(url, methods=('put',))# 'patch'))
|
||||
|
||||
# sue can update the job template detail.
|
||||
with self.current_user(self.user_sue):
|
||||
@ -579,7 +571,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
url = reverse('main:job_template_jobs_list', args=(jt.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
self.check_invalid_auth(url)
|
||||
|
||||
# sue can read the job template job list.
|
||||
with self.current_user(self.user_sue):
|
||||
@ -601,7 +593,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TestCase):
|
||||
)
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, data, methods=('post',))
|
||||
self.check_invalid_auth(url, data, methods=('post',))
|
||||
|
||||
# sue can create a new job from the template.
|
||||
with self.current_user(self.user_sue):
|
||||
@ -615,7 +607,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
url = reverse('main:job_list')
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
self.check_invalid_auth(url)
|
||||
|
||||
# sue's credentials (superuser) == 200, full list
|
||||
with self.current_user(self.user_sue):
|
||||
@ -641,7 +633,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
)
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, data, methods=('post',))
|
||||
self.check_invalid_auth(url, data, methods=('post',))
|
||||
|
||||
# sue can create a new job without a template.
|
||||
with self.current_user(self.user_sue):
|
||||
@ -663,7 +655,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
url = reverse('main:job_detail', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
self.check_invalid_auth(url)
|
||||
|
||||
# sue can read the job detail.
|
||||
with self.current_user(self.user_sue):
|
||||
@ -679,7 +671,7 @@ class JobTest(BaseJobTestMixin, django.test.TestCase):
|
||||
url = reverse('main:job_detail', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url, methods=('put',))# 'patch'))
|
||||
self.check_invalid_auth(url, methods=('put',))# 'patch'))
|
||||
|
||||
# sue can update the job detail only if the job is new.
|
||||
self.assertEqual(job.status, 'new')
|
||||
@ -780,8 +772,8 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
url = reverse('main:job_start', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
self._test_invalid_creds(url, methods=('post',))
|
||||
self.check_invalid_auth(url)
|
||||
self.check_invalid_auth(url, methods=('post',))
|
||||
|
||||
# Sue can start a job (when passwords are already saved) as long as the
|
||||
# status is new. Reverse list so "new" will be last.
|
||||
@ -867,8 +859,8 @@ class JobStartCancelTest(BaseJobTestMixin, django.test.LiveServerTestCase):
|
||||
url = reverse('main:job_cancel', args=(job.pk,))
|
||||
|
||||
# Test with no auth and with invalid login.
|
||||
self._test_invalid_creds(url)
|
||||
self._test_invalid_creds(url, methods=('post',))
|
||||
self.check_invalid_auth(url)
|
||||
self.check_invalid_auth(url, methods=('post',))
|
||||
|
||||
# sue can cancel the job, but only when it is pending or running.
|
||||
for status in [x[0] for x in Job.STATUS_CHOICES]:
|
||||
|
||||
@ -84,14 +84,6 @@ class ProjectsTest(BaseTest):
|
||||
self.team1.users.add(self.normal_django_user)
|
||||
self.team2.users.add(self.other_django_user)
|
||||
|
||||
self.nobody_django_user = User.objects.create(username='nobody')
|
||||
self.nobody_django_user.set_password('nobody')
|
||||
self.nobody_django_user.save()
|
||||
|
||||
def get_nobody_credentials(self):
|
||||
# here is a user without any permissions...
|
||||
return ('nobody', 'nobody')
|
||||
|
||||
def test_playbooks(self):
|
||||
def write_test_file(project, name, content):
|
||||
full_path = os.path.join(project.get_project_path(), name)
|
||||
@ -395,11 +387,13 @@ class ProjectsTest(BaseTest):
|
||||
self.get(url, expect=401, auth=self.get_invalid_credentials())
|
||||
self.get(url, expect=403, auth=self.get_nobody_credentials())
|
||||
other.organizations.add(Organization.objects.get(pk=self.organizations[1].pk))
|
||||
other.save()
|
||||
# Normal user can only see some teams that other user is a part of,
|
||||
# since normal user is not an admin of that organization.
|
||||
my_teams1 = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEqual(my_teams1['count'], 1)
|
||||
# Other user should be able to see all his own teams.
|
||||
my_teams2 = self.get(url, expect=200, auth=self.get_other_credentials())
|
||||
self.assertEqual(my_teams1['count'], 2)
|
||||
self.assertEqual(my_teams1, my_teams2)
|
||||
self.assertEqual(my_teams2['count'], 2)
|
||||
|
||||
# =====================================================================
|
||||
# USER PROJECTS
|
||||
@ -511,14 +505,14 @@ class ProjectsTest(BaseTest):
|
||||
team_url = reverse('main:team_credentials_list', args=(cred_put_t['team'],))
|
||||
self.post(team_url, data=cred_put_t, expect=204, auth=self.get_normal_credentials())
|
||||
|
||||
# can remove credentials from a user (via disassociate)
|
||||
# can remove credentials from a user (via disassociate) - this will delete the credential.
|
||||
cred_put_u['disassociate'] = 1
|
||||
url = cred_put_u['url']
|
||||
user_url = reverse('main:user_credentials_list', args=(cred_put_u['user'],))
|
||||
self.post(user_url, data=cred_put_u, expect=204, auth=self.get_normal_credentials())
|
||||
|
||||
# can delete a credential directly -- probably won't be used too often
|
||||
data = self.delete(url, expect=204, auth=self.get_other_credentials())
|
||||
#data = self.delete(url, expect=204, auth=self.get_other_credentials())
|
||||
data = self.delete(url, expect=404, auth=self.get_other_credentials())
|
||||
|
||||
# =====================================================================
|
||||
|
||||
@ -142,7 +142,7 @@ class UsersTest(BaseTest):
|
||||
def test_user_list_filtered(self):
|
||||
url = reverse('main:user_list')
|
||||
data3 = self.get(url, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEquals(data3['count'], 3)
|
||||
self.assertEquals(data3['count'], 4)
|
||||
data2 = self.get(url, expect=200, auth=self.get_normal_credentials())
|
||||
self.assertEquals(data2['count'], 2)
|
||||
data1 = self.get(url, expect=200, auth=self.get_other_credentials())
|
||||
|
||||
@ -170,6 +170,7 @@ try:
|
||||
# Support for get_description method on views compatible with 2.2.x.
|
||||
if hasattr(cls, 'get_description') and callable(cls.get_description):
|
||||
desc = cls().get_description(html=html)
|
||||
cls = type(cls.__name__, (object,), {'__doc__': desc})
|
||||
elif hasattr(cls, 'view_description'):
|
||||
if callable(cls.view_description):
|
||||
view_desc = cls.view_description()
|
||||
|
||||
54
awx/main/utils.py
Normal file
54
awx/main/utils.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Python
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Django
|
||||
from django.conf import settings
|
||||
from django.shortcuts import _get_queryset
|
||||
|
||||
# Django REST Framework
|
||||
from rest_framework.exceptions import ParseError, PermissionDenied
|
||||
|
||||
__all__ = ['get_object_or_400', 'get_object_or_403', 'camelcase_to_underscore']
|
||||
|
||||
def get_object_or_400(klass, *args, **kwargs):
|
||||
'''
|
||||
Return a single object from the given model or queryset based on the query
|
||||
params, otherwise raise an exception that will return in a 400 response.
|
||||
'''
|
||||
queryset = _get_queryset(klass)
|
||||
try:
|
||||
return queryset.get(*args, **kwargs)
|
||||
except queryset.model.DoesNotExist, e:
|
||||
raise ParseError(*e.args)
|
||||
except queryset.model.MultipleObjectsReturned, e:
|
||||
raise ParseError(*e.args)
|
||||
|
||||
def get_object_or_403(klass, *args, **kwargs):
|
||||
'''
|
||||
Return a single object from the given model or queryset based on the query
|
||||
params, otherwise raise an exception that will return in a 403 response.
|
||||
'''
|
||||
queryset = _get_queryset(klass)
|
||||
try:
|
||||
return queryset.get(*args, **kwargs)
|
||||
except queryset.model.DoesNotExist, e:
|
||||
raise PermissionDenied(*e.args)
|
||||
except queryset.model.MultipleObjectsReturned, e:
|
||||
raise PermissionDenied(*e.args)
|
||||
|
||||
def camelcase_to_underscore(s):
|
||||
'''
|
||||
Convert CamelCase names to lowercase_with_underscore.
|
||||
'''
|
||||
s = re.sub(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', s)
|
||||
return s.lower().strip('_')
|
||||
|
||||
class RequireDebugTrueOrTest(logging.Filter):
|
||||
'''
|
||||
Logging filter to output when in DEBUG mode or running tests.
|
||||
'''
|
||||
|
||||
def filter(self, record):
|
||||
return settings.DEBUG or 'test' in sys.argv
|
||||
@ -32,6 +32,7 @@ from awx.main.base_views import *
|
||||
from awx.main.models import *
|
||||
from awx.main.permissions import *
|
||||
from awx.main.serializers import *
|
||||
from awx.main.utils import *
|
||||
|
||||
def handle_error(request, status=404):
|
||||
context = {}
|
||||
@ -189,7 +190,6 @@ class OrganizationUsersList(SubListCreateAPIView):
|
||||
serializer_class = UserSerializer
|
||||
parent_model = Organization
|
||||
relationship = 'users'
|
||||
inject_primary_key_on_post_as = 'organization'
|
||||
|
||||
class OrganizationAdminsList(SubListCreateAPIView):
|
||||
|
||||
@ -197,8 +197,6 @@ class OrganizationAdminsList(SubListCreateAPIView):
|
||||
serializer_class = UserSerializer
|
||||
parent_model = Organization
|
||||
relationship = 'admins'
|
||||
inject_primary_key_on_post_as = 'organization'
|
||||
|
||||
|
||||
class OrganizationProjectsList(SubListCreateAPIView):
|
||||
|
||||
@ -206,7 +204,6 @@ class OrganizationProjectsList(SubListCreateAPIView):
|
||||
serializer_class = ProjectSerializer
|
||||
parent_model = Organization
|
||||
relationship = 'projects'
|
||||
inject_primary_key_on_post_as = 'organization'
|
||||
|
||||
class OrganizationTeamsList(SubListCreateAPIView):
|
||||
|
||||
@ -214,8 +211,7 @@ class OrganizationTeamsList(SubListCreateAPIView):
|
||||
serializer_class = TeamSerializer
|
||||
parent_model = Organization
|
||||
relationship = 'teams'
|
||||
inject_primary_key_on_post_as = 'organization'
|
||||
severable = False
|
||||
parent_key = 'organization'
|
||||
|
||||
class TeamList(ListCreateAPIView):
|
||||
|
||||
@ -233,8 +229,6 @@ class TeamUsersList(SubListCreateAPIView):
|
||||
serializer_class = UserSerializer
|
||||
parent_model = Team
|
||||
relationship = 'users'
|
||||
inject_primary_key_on_post_as = 'team'
|
||||
severable = True
|
||||
|
||||
class TeamPermissionsList(SubListCreateAPIView):
|
||||
|
||||
@ -242,7 +236,7 @@ class TeamPermissionsList(SubListCreateAPIView):
|
||||
serializer_class = PermissionSerializer
|
||||
parent_model = Team
|
||||
relationship = 'permissions'
|
||||
inject_primary_key_on_post_as = 'team'
|
||||
parent_key = 'team'
|
||||
|
||||
def get_queryset(self):
|
||||
# FIXME
|
||||
@ -261,8 +255,6 @@ class TeamProjectsList(SubListCreateAPIView):
|
||||
serializer_class = ProjectSerializer
|
||||
parent_model = Team
|
||||
relationship = 'projects'
|
||||
inject_primary_key_on_post_as = 'team'
|
||||
severable = True
|
||||
|
||||
class TeamCredentialsList(SubListCreateAPIView):
|
||||
|
||||
@ -270,7 +262,7 @@ class TeamCredentialsList(SubListCreateAPIView):
|
||||
serializer_class = CredentialSerializer
|
||||
parent_model = Team
|
||||
relationship = 'credentials'
|
||||
inject_primary_key_on_post_as = 'team'
|
||||
parent_key = 'team'
|
||||
|
||||
class ProjectList(ListCreateAPIView):
|
||||
|
||||
@ -293,7 +285,6 @@ class ProjectOrganizationsList(SubListCreateAPIView):
|
||||
serializer_class = OrganizationSerializer
|
||||
parent_model = Project
|
||||
relationship = 'organizations'
|
||||
inject_primary_key_on_post_as = 'project' # Not correct, but needed for the post to work?
|
||||
|
||||
def get_queryset(self):
|
||||
# FIXME
|
||||
@ -308,7 +299,6 @@ class ProjectTeamsList(SubListCreateAPIView):
|
||||
serializer_class = TeamSerializer
|
||||
parent_model = Project
|
||||
relationship = 'teams'
|
||||
inject_primary_key_on_post_as = 'project' # Not correct, but needed for the post to work?
|
||||
|
||||
def get_queryset(self):
|
||||
project = Project.objects.get(pk=self.kwargs['pk'])
|
||||
@ -347,6 +337,7 @@ class UserTeamsList(SubListAPIView):
|
||||
serializer_class = TeamSerializer
|
||||
parent_model = User
|
||||
relationship = 'teams'
|
||||
parent_access = 'read'
|
||||
|
||||
class UserPermissionsList(SubListCreateAPIView):
|
||||
|
||||
@ -354,7 +345,8 @@ class UserPermissionsList(SubListCreateAPIView):
|
||||
serializer_class = PermissionSerializer
|
||||
parent_model = User
|
||||
relationship = 'permissions'
|
||||
inject_primary_key_on_post_as = 'user'
|
||||
parent_key = 'user'
|
||||
parent_access = 'read'
|
||||
|
||||
class UserProjectsList(SubListAPIView):
|
||||
|
||||
@ -362,6 +354,7 @@ class UserProjectsList(SubListAPIView):
|
||||
serializer_class = ProjectSerializer
|
||||
parent_model = User
|
||||
relationship = 'projects'
|
||||
parent_access = 'read'
|
||||
|
||||
def get_queryset(self):
|
||||
parent = self.get_parent_object()
|
||||
@ -375,7 +368,8 @@ class UserCredentialsList(SubListCreateAPIView):
|
||||
serializer_class = CredentialSerializer
|
||||
parent_model = User
|
||||
relationship = 'credentials'
|
||||
inject_primary_key_on_post_as = 'user'
|
||||
parent_key = 'user'
|
||||
parent_access = 'read'
|
||||
|
||||
class UserOrganizationsList(SubListAPIView):
|
||||
|
||||
@ -383,6 +377,7 @@ class UserOrganizationsList(SubListAPIView):
|
||||
serializer_class = OrganizationSerializer
|
||||
parent_model = User
|
||||
relationship = 'organizations'
|
||||
parent_access = 'read'
|
||||
|
||||
class UserAdminOfOrganizationsList(SubListAPIView):
|
||||
|
||||
@ -390,6 +385,7 @@ class UserAdminOfOrganizationsList(SubListAPIView):
|
||||
serializer_class = OrganizationSerializer
|
||||
parent_model = User
|
||||
relationship = 'admin_of_organizations'
|
||||
parent_access = 'read'
|
||||
|
||||
class UserDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
@ -399,8 +395,9 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
|
||||
def update_filter(self, request, *args, **kwargs):
|
||||
''' make sure non-read-only fields that can only be edited by admins, are only edited by admins '''
|
||||
obj = User.objects.get(pk=kwargs['pk'])
|
||||
can_change = request.user.can_access(User, 'change', obj, request.DATA)
|
||||
can_admin = request.user.can_access(User, 'admin', obj, request.DATA)
|
||||
if not can_admin or can_admin == 'partial':
|
||||
if can_change and not can_admin:
|
||||
admin_only_edit_fields = ('last_name', 'first_name', 'username',
|
||||
'is_active', 'is_superuser')
|
||||
changed = {}
|
||||
@ -412,10 +409,10 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
|
||||
if changed:
|
||||
raise PermissionDenied('Cannot change %s' % ', '.join(changed.keys()))
|
||||
|
||||
if 'password' in request.DATA and request.DATA['password']:
|
||||
obj.set_password(request.DATA['password'])
|
||||
new_password = request.DATA.get('password', '')
|
||||
if can_change and new_password:
|
||||
obj.set_password(new_password)
|
||||
obj.save()
|
||||
request.DATA.pop('password')
|
||||
|
||||
class CredentialList(ListAPIView):
|
||||
|
||||
@ -459,8 +456,7 @@ class InventoryHostsList(SubListCreateAPIView):
|
||||
parent_model = Inventory
|
||||
relationship = 'hosts'
|
||||
parent_access = 'read'
|
||||
inject_primary_key_on_post_as = 'inventory'
|
||||
severable = False
|
||||
parent_key = 'inventory'
|
||||
|
||||
class HostGroupsList(SubListCreateAPIView):
|
||||
''' the list of groups a host is directly a member of '''
|
||||
@ -470,7 +466,6 @@ class HostGroupsList(SubListCreateAPIView):
|
||||
parent_model = Host
|
||||
relationship = 'groups'
|
||||
parent_access = 'read'
|
||||
inject_primary_key_on_post_as = 'host'
|
||||
|
||||
class HostAllGroupsList(SubListAPIView):
|
||||
''' the list of all groups of which the host is directly or indirectly a member '''
|
||||
@ -500,7 +495,6 @@ class GroupChildrenList(SubListCreateAPIView):
|
||||
parent_model = Group
|
||||
relationship = 'children'
|
||||
parent_access = 'read'
|
||||
inject_primary_key_on_post_as = 'parent'
|
||||
|
||||
class GroupHostsList(SubListCreateAPIView):
|
||||
''' the list of hosts directly below a group '''
|
||||
@ -510,7 +504,6 @@ class GroupHostsList(SubListCreateAPIView):
|
||||
parent_model = Group
|
||||
relationship = 'hosts'
|
||||
parent_access = 'read'
|
||||
inject_primary_key_on_post_as = 'group'
|
||||
|
||||
class GroupAllHostsList(SubListAPIView):
|
||||
''' the list of all hosts below a group, even including subgroups '''
|
||||
@ -540,8 +533,7 @@ class InventoryGroupsList(SubListCreateAPIView):
|
||||
parent_model = Inventory
|
||||
relationship = 'groups'
|
||||
parent_access = 'read'
|
||||
inject_primary_key_on_post_as = 'inventory'
|
||||
severable = False
|
||||
parent_key = 'inventory'
|
||||
|
||||
class InventoryRootGroupsList(SubListCreateAPIView):
|
||||
|
||||
@ -550,8 +542,7 @@ class InventoryRootGroupsList(SubListCreateAPIView):
|
||||
parent_model = Inventory
|
||||
relationship = 'groups'
|
||||
parent_access = 'read'
|
||||
inject_primary_key_on_post_as = 'inventory'
|
||||
severable = False
|
||||
parent_key = 'inventory'
|
||||
|
||||
def get_queryset(self):
|
||||
parent = self.get_parent_object()
|
||||
@ -790,8 +781,7 @@ class JobTemplateJobsList(SubListCreateAPIView):
|
||||
serializer_class = JobSerializer
|
||||
parent_model = JobTemplate
|
||||
relationship = 'jobs'
|
||||
inject_primary_key_on_post_as = 'job_template'
|
||||
severable = False
|
||||
parent_key = 'job_template'
|
||||
|
||||
class JobList(ListCreateAPIView):
|
||||
|
||||
@ -955,7 +945,6 @@ class JobJobEventsList(BaseJobEventsList):
|
||||
# in URL patterns and reverse URL lookups, converting CamelCase names to
|
||||
# lowercase_with_underscore (e.g. MyView.as_view() becomes my_view).
|
||||
this_module = sys.modules[__name__]
|
||||
camelcase_to_underscore = lambda str: re.sub(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', str).lower().strip('_')
|
||||
for attr, value in locals().items():
|
||||
if isinstance(value, type) and issubclass(value, APIView):
|
||||
name = camelcase_to_underscore(attr)
|
||||
|
||||
@ -278,6 +278,9 @@ LOGGING = {
|
||||
'require_debug_true': {
|
||||
'()': 'awx.main.compat.RequireDebugTrue',
|
||||
},
|
||||
'require_debug_true_or_test': {
|
||||
'()': 'awx.main.utils.RequireDebugTrueOrTest',
|
||||
},
|
||||
},
|
||||
'formatters': {
|
||||
'simple': {
|
||||
@ -287,7 +290,7 @@ LOGGING = {
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'filters': ['require_debug_true'],
|
||||
'filters': ['require_debug_true_or_test'],
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
},
|
||||
@ -328,12 +331,10 @@ LOGGING = {
|
||||
},
|
||||
'awx.main.permissions': {
|
||||
'handlers': ['null'],
|
||||
# Comment the line below to show lots of permissions logging.
|
||||
'propagate': False,
|
||||
},
|
||||
'awx.main.access': {
|
||||
'handlers': ['null'],
|
||||
# Comment the line below to show lots of permissions logging.
|
||||
'propagate': False,
|
||||
},
|
||||
}
|
||||
|
||||
@ -106,6 +106,11 @@ LOGGING['handlers']['syslog'] = {
|
||||
# 'formatter': 'simple',
|
||||
#}
|
||||
|
||||
# Enable the following lines to turn on lots of permissions-related logging.
|
||||
#LOGGING['loggers']['awx.main.access']['propagate'] = True
|
||||
#LOGGING['loggers']['awx.main.permissions']['propagate'] = True
|
||||
|
||||
# Define additional environment variables to be passed to subprocess started by
|
||||
# the celery task.
|
||||
#AWX_TASK_ENV['FOO'] = 'BAR'
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user