mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 02:19:58 -03:30
Split out RBAC and can_user_* methods from models into access.py. Moved list/item permissions checks from the base views into RBAC. Added serializers/views/tests for jobs REST API.
This commit is contained in:
parent
75ea4a1cda
commit
2a15d07221
548
lib/main/access.py
Normal file
548
lib/main/access.py
Normal file
@ -0,0 +1,548 @@
|
||||
import logging
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth.models import User
|
||||
from lib.main.models import *
|
||||
|
||||
__all__ = ['get_user_queryset', 'check_user_access']
|
||||
|
||||
logger = logging.getLogger('lib.main.access')
|
||||
|
||||
access_registry = {
|
||||
# <model_class>: [<access_class>, ...],
|
||||
# ...
|
||||
}
|
||||
|
||||
def register_access(model_class, access_class):
|
||||
access_classes = access_registry.setdefault(model_class, [])
|
||||
access_classes.append(access_class)
|
||||
|
||||
def get_user_queryset(user, model_class):
|
||||
'''
|
||||
Return a queryset for the given model_class containing only the instances
|
||||
that should be visible to the given user.
|
||||
'''
|
||||
querysets = []
|
||||
for access_class in access_registry.get(model_class, []):
|
||||
access_instance = access_class(user)
|
||||
querysets.append(access_instance.get_queryset())
|
||||
if not querysets:
|
||||
return model_class.objects.none()
|
||||
elif len(querysets) == 1:
|
||||
return querysets[0]
|
||||
else:
|
||||
queryset = model_class.objects.all()
|
||||
for qs in querysets:
|
||||
queryset = queryset.filter(pk__in=qs.values_list('pk', flat=True))
|
||||
return queryset
|
||||
|
||||
def check_user_access(user, model_class, action, *args, **kwargs):
|
||||
'''
|
||||
Return True if user can perform action against model_class with the
|
||||
provided parameters.
|
||||
'''
|
||||
for access_class in access_registry.get(model_class, []):
|
||||
access_instance = access_class(user)
|
||||
access_method = getattr(access_instance, 'can_%s' % action, None)
|
||||
if not access_method:
|
||||
continue
|
||||
result = access_method(*args, **kwargs)
|
||||
logger.debug('%s.%s %r returned %r', access_instance.__class__.__name__,
|
||||
access_method.__name__, args, result)
|
||||
if result:
|
||||
return result
|
||||
return False
|
||||
|
||||
class BaseAccess(object):
|
||||
|
||||
model = None
|
||||
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
|
||||
def get_queryset(self):
|
||||
if self.user.is_superuser:
|
||||
return self.model.objects.all()
|
||||
else:
|
||||
return self.model.objects.none()
|
||||
|
||||
def can_read(self, obj):
|
||||
return self.user.is_superuser
|
||||
|
||||
def can_add(self, data):
|
||||
return self.user.is_superuser
|
||||
|
||||
def can_change(self, obj, data):
|
||||
return self.user.is_superuser
|
||||
|
||||
def can_write(self, obj, data):
|
||||
# Alias for change.
|
||||
return self.can_change(obj, data)
|
||||
|
||||
def can_admin(self, obj, data):
|
||||
# Alias for can_change. Can be overridden if admin vs. user change
|
||||
# permissions need to be different.
|
||||
return self.can_change(obj, data)
|
||||
|
||||
def can_delete(self, obj):
|
||||
return self.user.is_superuser
|
||||
|
||||
def can_attach(self, obj, sub_obj, relationship, data,
|
||||
skip_sub_obj_read_check=False):
|
||||
if skip_sub_obj_read_check:
|
||||
return self.can_change(obj, None)
|
||||
else:
|
||||
return bool(self.can_change(obj, None) and
|
||||
check_user_access(self.user, type(sub_obj), 'read',
|
||||
sub_obj))
|
||||
|
||||
def can_unattach(self, obj, sub_obj, relationship):
|
||||
return self.can_change(obj, None)
|
||||
|
||||
class UserAccess(BaseAccess):
|
||||
|
||||
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.
|
||||
if self.user.is_superuser:
|
||||
return self.model.objects.all()
|
||||
return self.model.objects.filter(is_active=True).filter(
|
||||
Q(pk=self.user.pk) |
|
||||
Q(organizations__in=self.user.admin_of_organizations.all()) |
|
||||
Q(teams__in=self.request.user.teams.all())
|
||||
).distinct()
|
||||
|
||||
def can_read(self, obj):
|
||||
# A user can be read if they are on the same team or can be changed.
|
||||
matching_teams = self.user.teams.filter(users__in=[self.user]).count()
|
||||
return bool(matching_teams or self.can_change(obj, None))
|
||||
|
||||
def can_add(self, data):
|
||||
# TODO: reuse. make helper functions like "is user an org admin"
|
||||
# apply throughout permissions code
|
||||
return bool(self.user.is_superuser or
|
||||
self.user.admin_of_organizations.count())
|
||||
|
||||
def can_change(self, obj, data):
|
||||
# A user can be changed if they are themselves, or by org admins or
|
||||
# superusers.
|
||||
if self.user == obj:
|
||||
return 'partial'
|
||||
return bool(self.user.is_superuser or
|
||||
obj.organizations.filter(admins__in=[self.user]).count())
|
||||
|
||||
def can_delete(self, obj):
|
||||
return bool(self.user.is_superuser or
|
||||
obj.organizations.filter(admins__in=[self.user]).count())
|
||||
|
||||
class TagAccess(BaseAccess):
|
||||
|
||||
model = Tag
|
||||
|
||||
def can_read(self, obj):
|
||||
# anybody can read tags, we won't show much detail other than the names
|
||||
return True
|
||||
|
||||
def can_add(self, data):
|
||||
# anybody can make up tags
|
||||
return True
|
||||
|
||||
class OrganizationAccess(BaseAccess):
|
||||
|
||||
model = Organization
|
||||
|
||||
def can_read(self, obj):
|
||||
return bool(self.can_change(obj, None) or
|
||||
self.user in obj.users.all())
|
||||
|
||||
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):
|
||||
|
||||
model = Inventory
|
||||
|
||||
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 can_read(self, obj):
|
||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_READ)
|
||||
|
||||
def can_add(self, data):
|
||||
if not 'organization' in data:
|
||||
return True
|
||||
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():
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_change(self, obj, data):
|
||||
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)
|
||||
|
||||
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 check_user_access(self.user, 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)
|
||||
|
||||
class HostAccess(BaseAccess):
|
||||
|
||||
model = Host
|
||||
|
||||
def can_read(self, obj):
|
||||
return check_user_access(self.user, Inventory, 'read', obj.inventory)
|
||||
|
||||
def can_add(self, data):
|
||||
if not 'inventory' in data:
|
||||
return False
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
# Checks for admin or change permission on inventory.
|
||||
return check_user_access(self.user, Inventory, 'change', inventory, None)
|
||||
|
||||
class GroupAccess(BaseAccess):
|
||||
|
||||
model = Group
|
||||
|
||||
def can_read(self, obj):
|
||||
return check_user_access(self.user, Inventory, 'read', obj.inventory)
|
||||
|
||||
def can_add(self, data):
|
||||
if not 'inventory' in data:
|
||||
return False
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
# Checks for admin or change permission on inventory.
|
||||
return check_user_access(self.user, 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
|
||||
return check_user_access(self.user, Inventory, 'change', obj.inventory, None)
|
||||
|
||||
class VariableDataAccess(BaseAccess):
|
||||
|
||||
model = VariableData
|
||||
|
||||
def can_read(self, obj):
|
||||
if obj.host:
|
||||
inventory = obj.host.inventory
|
||||
elif obj.group:
|
||||
inventory = obj.group.inventory
|
||||
else:
|
||||
return False
|
||||
return check_user_access(self.user, Inventory, 'read', inventory)
|
||||
|
||||
def can_change(self, obj, data):
|
||||
if obj.host:
|
||||
inventory = obj.host.inventory
|
||||
elif obj.group:
|
||||
inventory = obj.group.inventory
|
||||
else:
|
||||
return False
|
||||
return check_user_access(self.user, Inventory, 'change', inventory)
|
||||
|
||||
def can_delete(self, obj):
|
||||
return False
|
||||
|
||||
class CredentialAccess(BaseAccess):
|
||||
|
||||
model = Credential
|
||||
|
||||
def can_read(self, obj):
|
||||
return self.can_change(obj, None)
|
||||
|
||||
def can_add(self, data):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if 'user' in data:
|
||||
user_obj = User.objects.get(pk=data['user'])
|
||||
return check_user_access(self.user, User, 'change', user_obj, None)
|
||||
if 'team' in data:
|
||||
team_obj = Team.objects.get(pk=data['team'])
|
||||
return check_user_access(self.user, Team, 'change', team_obj, None)
|
||||
|
||||
def can_change(self, obj, data):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if self.user == obj.user:
|
||||
return True
|
||||
if obj.user:
|
||||
if (obj.user.organizations.filter(admins__in = [self.user]).count()):
|
||||
return True
|
||||
if obj.team:
|
||||
if self.user in obj.team.organization.admins.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_delete(self, obj):
|
||||
if obj.user is None and obj.team is None:
|
||||
# unassociated credentials may be marked deleted by anyone
|
||||
return True
|
||||
return self.can_change(obj, None)
|
||||
|
||||
class TeamAccess(BaseAccess):
|
||||
|
||||
model = Team
|
||||
|
||||
def can_add(self, data):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if Organization.objects.filter(admins__in = [self.user]).count():
|
||||
# team assignment to organizations is handled elsewhere, this just creates
|
||||
# a blank team
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_read(self, obj):
|
||||
if self.can_change(obj, None):
|
||||
return True
|
||||
if obj.users.filter(pk__in = [ self.user.pk ]).count():
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_change(self, obj, data):
|
||||
# FIXME -- audit when this is called explicitly, if any
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if self.user in obj.organization.admins.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_delete(self, obj):
|
||||
return self.can_change(obj, None)
|
||||
|
||||
class ProjectAccess(BaseAccess):
|
||||
|
||||
model = Project
|
||||
|
||||
def can_read(self, obj):
|
||||
if self.can_change(obj, None):
|
||||
return True
|
||||
# and also if I happen to be on a team inside the project
|
||||
# FIXME: add this too
|
||||
return False
|
||||
|
||||
def can_change(self, obj, data):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if obj.created_by == self.user:
|
||||
return True
|
||||
organizations = Organization.objects.filter(admins__in = [ self.user ], projects__in = [ obj ])
|
||||
for org in organizations:
|
||||
if org in project.organizations():
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_delete(self, obj):
|
||||
return self.can_change(obj, None)
|
||||
|
||||
class PermissionAccess(BaseAccess):
|
||||
|
||||
model = Permission
|
||||
|
||||
def can_read(self, obj):
|
||||
# a permission can be seen by the assigned user or team
|
||||
# or anyone who can administrate that permission
|
||||
if obj.user and obj.user == self.user:
|
||||
return True
|
||||
if obj.team and obj.team.users.filter(pk = self.user.pk).count() > 0:
|
||||
return True
|
||||
return self.can_change(obj, None)
|
||||
|
||||
def can_change(self, obj, data):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
# a permission can be administrated by a super
|
||||
# or if a user permission, that an admin of a user's organization
|
||||
# or if a team permission, an admin of that team's organization
|
||||
if obj.user and obj.user.organizations.filter(admins__in = [self.user]).count() > 0:
|
||||
return True
|
||||
if obj.team and obj.team.organization.admins.filter(user=self.user).count() > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_delete(self, obj):
|
||||
return self.can_change(obj, None)
|
||||
|
||||
class JobTemplateAccess(BaseAccess):
|
||||
|
||||
model = JobTemplate
|
||||
|
||||
def get_queryset(self):
|
||||
'''
|
||||
I can see job templates when I am a superuser, or I am an admin of the
|
||||
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.
|
||||
'''
|
||||
qs = self.model.objects.all()
|
||||
if self.user.is_superuser:
|
||||
return qs.all()
|
||||
return qs.filter(active=True).filter(
|
||||
Q(project__organizations__admins__in=[self.user]) |
|
||||
Q(project__teams__users__in=[self.user])
|
||||
).distinct()
|
||||
|
||||
def can_read(self, obj):
|
||||
# you can only see the job templates that you have permission to launch.
|
||||
data = dict(
|
||||
inventory = obj.inventory.pk,
|
||||
project = obj.project.pk,
|
||||
job_type = obj.job_type
|
||||
)
|
||||
return self.can_add(data)
|
||||
|
||||
def can_add(self, data):
|
||||
'''
|
||||
a user can create a job template if they are a superuser, an org admin of any org
|
||||
that the project is a member, or if they have user or team based permissions tying
|
||||
the project to the inventory source for the given action.
|
||||
|
||||
users who are able to create deploy jobs can also make check (dry run) jobs
|
||||
'''
|
||||
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
if not data or '_method' in data: # FIXME: So the browseable API will work?
|
||||
return True
|
||||
project = Project.objects.get(pk=data['project'])
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
|
||||
admin_of_orgs = project.organizations.filter(admins__in = [ self.user ])
|
||||
if admin_of_orgs.count() > 0:
|
||||
return True
|
||||
job_type = data['job_type']
|
||||
|
||||
has_launch_permission = False
|
||||
user_permissions = Permission.objects.filter(inventory=inventory, project=project, user=self.user)
|
||||
for perm in user_permissions:
|
||||
if job_type == PERM_INVENTORY_CHECK:
|
||||
# if you have run permissions, you can also create check jobs
|
||||
has_launch_permission = True
|
||||
elif job_type == PERM_INVENTORY_DEPLOY and perm.permission_type == PERM_INVENTORY_DEPLOY:
|
||||
# you need explicit run permissions to make run jobs
|
||||
has_launch_permission = True
|
||||
team_permissions = Permission.objects.filter(inventory=inventory, project=project, team__users__in = [self.user])
|
||||
for perm in team_permissions:
|
||||
if job_type == PERM_INVENTORY_CHECK:
|
||||
# if you have run permissions, you can also create check jobs
|
||||
has_launch_permission = True
|
||||
elif job_type == PERM_INVENTORY_DEPLOY and perm.permission_type == PERM_INVENTORY_DEPLOY:
|
||||
# you need explicit run permissions to make run jobs
|
||||
has_launch_permission = True
|
||||
|
||||
if not has_launch_permission:
|
||||
return False
|
||||
|
||||
# make sure user owns the credentials they are using
|
||||
if data.has_key('credential'):
|
||||
has_credential = False
|
||||
credential = Credential.objects.get(pk=data['credential'])
|
||||
if credential.team and credential.team.users.filter(id = self.user.pk).count():
|
||||
has_credential = True
|
||||
if credential.user and credential.user == self.user:
|
||||
has_credential = True
|
||||
if not has_credential:
|
||||
return False
|
||||
|
||||
# shouldn't really matter with permissions given, but make sure the user
|
||||
# is also currently on the team in case they were added a per-user permission and then removed
|
||||
# from the project.
|
||||
if project.teams.filter(users__in = [ self.user ]).count():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def can_change(self, obj, data):
|
||||
'''
|
||||
'''
|
||||
return False # FIXME
|
||||
|
||||
class JobAccess(BaseAccess):
|
||||
|
||||
model = Job
|
||||
|
||||
def can_start(self, obj):
|
||||
return False # FIXME
|
||||
|
||||
def can_cancel(self, obj):
|
||||
return False # FIXME
|
||||
|
||||
class JobHostSummaryAccess(BaseAccess):
|
||||
|
||||
model = JobHostSummary
|
||||
|
||||
class JobEventAccess(BaseAccess):
|
||||
|
||||
model = JobEvent
|
||||
|
||||
register_access(User, UserAccess)
|
||||
register_access(Tag, TagAccess)
|
||||
register_access(Organization, OrganizationAccess)
|
||||
register_access(Inventory, InventoryAccess)
|
||||
register_access(Host, HostAccess)
|
||||
register_access(Group, GroupAccess)
|
||||
register_access(VariableData, VariableDataAccess)
|
||||
register_access(Credential, CredentialAccess)
|
||||
register_access(Team, TeamAccess)
|
||||
register_access(Project, ProjectAccess)
|
||||
register_access(Permission, PermissionAccess)
|
||||
register_access(JobTemplate, JobTemplateAccess)
|
||||
register_access(Job, JobAccess)
|
||||
register_access(JobHostSummary, JobHostSummaryAccess)
|
||||
register_access(JobEvent, JobEventAccess)
|
||||
@ -20,6 +20,7 @@ from lib.main.models import *
|
||||
from django.contrib.auth.models import User
|
||||
from lib.main.serializers import *
|
||||
from lib.main.rbac import *
|
||||
from lib.main.access import *
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import mixins
|
||||
from rest_framework import generics
|
||||
@ -34,23 +35,11 @@ import json as python_json
|
||||
|
||||
class BaseList(generics.ListCreateAPIView):
|
||||
|
||||
def list_permissions_check(self, request, obj=None):
|
||||
''' determines some early yes/no access decisions, pre-filtering '''
|
||||
#print '---', request.method, getattr(request, '_method', None)
|
||||
if request.method in ('OPTIONS', 'HEAD', 'GET'):
|
||||
return True
|
||||
if request.method == 'POST':
|
||||
if self.__class__.model in [ User ]:
|
||||
ok = request.user.is_superuser or (request.user.admin_of_organizations.count() > 0)
|
||||
if not ok:
|
||||
raise PermissionDenied()
|
||||
return True
|
||||
else:
|
||||
# audit all of these to check ownership/readability of subobjects
|
||||
if not self.__class__.model.can_user_add(request.user, self.request.DATA):
|
||||
raise PermissionDenied()
|
||||
return True
|
||||
return False#raise exceptions.NotImplementedError
|
||||
permission_classes = (CustomRbac,)
|
||||
|
||||
# Subclasses should define:
|
||||
# model = ModelClass
|
||||
# serializer_class = SerializerClass
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
@ -59,7 +48,7 @@ class BaseList(generics.ListCreateAPIView):
|
||||
qs = None
|
||||
if model == User:
|
||||
qs = base.filter(is_active=True)
|
||||
elif model in [ Tag, AuditTrail ]:
|
||||
elif model in [ Tag, AuditTrail, JobEvent ]:
|
||||
qs = base
|
||||
else:
|
||||
qs = self._get_queryset().filter(active=True)
|
||||
@ -70,22 +59,19 @@ class BaseList(generics.ListCreateAPIView):
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
|
||||
class BaseSubList(BaseList):
|
||||
|
||||
''' used for subcollections with an overriden post '''
|
||||
|
||||
def list_permissions_check(self, request, obj=None):
|
||||
''' determines some early yes/no access decisions, pre-filtering '''
|
||||
if request.method in ('OPTIONS', 'HEAD', 'GET'):
|
||||
return True
|
||||
if request.method == 'POST':
|
||||
# the can_user_attach methods will be called below
|
||||
return True
|
||||
raise exceptions.NotImplementedError
|
||||
|
||||
# Subclasses should define at least:
|
||||
# model = ModelClass
|
||||
# serializer_class = SerializerClass
|
||||
# parent_model = ModelClass
|
||||
# relationship = 'rel_name_from_parent_to_model'
|
||||
# And optionally:
|
||||
# postable = True/False
|
||||
# inject_primary_key_on_post_as = 'field_on_model_referring_to_parent'
|
||||
# severable = True/False
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
@ -93,8 +79,14 @@ class BaseSubList(BaseList):
|
||||
if not postable:
|
||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
# Make a copy of the data provided (since it's readonly) in order to
|
||||
# inject additional data.
|
||||
if hasattr(request.DATA, 'dict'):
|
||||
data = request.DATA.dict()
|
||||
else:
|
||||
data = request.DATA
|
||||
parent_id = kwargs['pk']
|
||||
sub_id = request.DATA.get('id', None)
|
||||
sub_id = data.get('id', None)
|
||||
main = self.__class__.parent_model.objects.get(pk=parent_id)
|
||||
severable = getattr(self.__class__, 'severable', True)
|
||||
|
||||
@ -103,7 +95,7 @@ class BaseSubList(BaseList):
|
||||
if sub_id:
|
||||
subs = self.__class__.model.objects.filter(pk=sub_id)
|
||||
else:
|
||||
if 'disassociate' in request.DATA:
|
||||
if 'disassociate' in data:
|
||||
raise PermissionDenied() # ID is required to disassociate
|
||||
else:
|
||||
|
||||
@ -117,20 +109,22 @@ class BaseSubList(BaseList):
|
||||
if inject_primary_key is not None:
|
||||
|
||||
# add the key to the post data using the pk from the URL
|
||||
request.DATA[inject_primary_key] = kwargs['pk']
|
||||
data[inject_primary_key] = kwargs['pk']
|
||||
|
||||
# attempt to deserialize the object
|
||||
ser = self.__class__.serializer_class(data=request.DATA)
|
||||
ser = self.__class__.serializer_class(data=data)
|
||||
if not ser.is_valid():
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data=ser.errors)
|
||||
|
||||
# ask the usual access control settings
|
||||
if self.__class__.model in [ User ]:
|
||||
if not UserHelper.can_user_add(request.user, ser.init_data):
|
||||
raise PermissionDenied()
|
||||
else:
|
||||
if not self.__class__.model.can_user_add(request.user, ser.init_data):
|
||||
raise PermissionDenied()
|
||||
#if self.__class__.model in [ User ]:
|
||||
# if not UserHelper.can_user_add(request.user, ser.init_data):
|
||||
# raise PermissionDenied()
|
||||
#else:
|
||||
# if not self.__class__.model.can_user_add(request.user, ser.init_data):
|
||||
# raise PermissionDenied()
|
||||
if not check_user_access(request.user, self.model, 'add', ser.init_data):
|
||||
raise PermissionDenied()
|
||||
|
||||
# save the object through the serializer, reload and returned the saved object deserialized
|
||||
obj = ser.save()
|
||||
@ -154,23 +148,26 @@ class BaseSubList(BaseList):
|
||||
# model so we have to cheat here. This may happen for other cases
|
||||
# where we are creating a user immediately on a subcollection
|
||||
# when that user does not already exist. Relations will work post-save.
|
||||
organization = Organization.objects.get(pk=request.DATA[inject_primary_key])
|
||||
organization = Organization.objects.get(pk=data[inject_primary_key])
|
||||
if not request.user.is_superuser:
|
||||
if not organization.admins.filter(pk=request.user.pk).count() > 0:
|
||||
raise PermissionDenied()
|
||||
else:
|
||||
raise exceptions.NotImplementedError()
|
||||
else:
|
||||
if not obj.__class__.can_user_read(request.user, obj):
|
||||
#if not obj.__class__.can_user_read(request.user, obj):
|
||||
if not check_user_access(request.user, type(obj), 'read', obj):
|
||||
raise PermissionDenied()
|
||||
if not self.__class__.parent_model.can_user_attach(request.user, main, obj, self.__class__.relationship, request.DATA):
|
||||
#if not self.__class__.parent_model.can_user_attach(request.user, main, obj, self.__class__.relationship, data):
|
||||
# If we just created a new object, we may not yet be able to read it because it's not yet associated with its parent model.
|
||||
if not check_user_access(request.user, self.parent_model, 'attach', main, obj, self.relationship, data, skip_sub_obj_read_check=True):
|
||||
raise PermissionDenied()
|
||||
|
||||
# FIXME: manual attachment code neccessary for users here, move this into the main code.
|
||||
# this is because users don't have FKs into what they are attaching. (also refactor)
|
||||
|
||||
if self.__class__.parent_model == Organization:
|
||||
organization = Organization.objects.get(pk=request.DATA[inject_primary_key])
|
||||
organization = Organization.objects.get(pk=data[inject_primary_key])
|
||||
import lib.main.views
|
||||
if self.__class__ == lib.main.views.OrganizationsUsersList:
|
||||
organization.users.add(obj)
|
||||
@ -178,10 +175,12 @@ class BaseSubList(BaseList):
|
||||
organization.admins.add(obj)
|
||||
|
||||
else:
|
||||
if not UserHelper.can_user_read(request.user, obj):
|
||||
#if not UserHelper.can_user_read(request.user, obj):
|
||||
if not check_user_access(request.user, type(obj), 'read', obj):
|
||||
raise PermissionDenied()
|
||||
# FIXME: should generalize this
|
||||
if not UserHelper.can_user_attach(request.user, main, obj, self.__class__.relationship, request.DATA):
|
||||
#if not UserHelper.can_user_attach(request.user, main, obj, self.__class__.relationship, data):
|
||||
if not check_user_access(request.user, self.parent_model, 'attach', main, obj, self.relationship, data):
|
||||
raise PermissionDenied()
|
||||
|
||||
return Response(status=status.HTTP_201_CREATED, data=ser.data)
|
||||
@ -199,13 +198,15 @@ class BaseSubList(BaseList):
|
||||
sub = subs[0]
|
||||
relationship = getattr(main, self.__class__.relationship)
|
||||
|
||||
if not 'disassociate' in request.DATA:
|
||||
if not 'disassociate' in data:
|
||||
if not request.user.is_superuser:
|
||||
if type(main) != User:
|
||||
if not self.__class__.parent_model.can_user_attach(request.user, main, sub, self.__class__.relationship, request.DATA):
|
||||
#if not self.__class__.parent_model.can_user_attach(request.user, main, sub, self.__class__.relationship, data):
|
||||
if not check_user_access(request.user, self.parent_model, 'attach', main, sub, self.relationship, data):
|
||||
raise PermissionDenied()
|
||||
else:
|
||||
if not UserHelper.can_user_attach(request.user, main, sub, self.__class__.relationship, request.DATA):
|
||||
#if not UserHelper.can_user_attach(request.user, main, sub, self.__class__.relationship, data):
|
||||
if not check_user_access(request.user, self.parent_model, 'attach', main, sub, self.relationship, data):
|
||||
raise PermissionDenied()
|
||||
|
||||
if sub in relationship.all():
|
||||
@ -214,10 +215,12 @@ class BaseSubList(BaseList):
|
||||
else:
|
||||
if not request.user.is_superuser:
|
||||
if type(main) != User:
|
||||
if not self.__class__.parent_model.can_user_unattach(request.user, main, sub, self.__class__.relationship):
|
||||
#if not self.__class__.parent_model.can_user_unattach(request.user, main, sub, self.__class__.relationship):
|
||||
if not check_user_access(request.user, self.parent_model, 'unattach', main, sub, self.relationship):
|
||||
raise PermissionDenied()
|
||||
else:
|
||||
if not UserHelper.can_user_unattach(request.user, main, sub, self.__class__.relationship):
|
||||
#if not UserHelper.can_user_unattach(request.user, main, sub, self.__class__.relationship):
|
||||
if not check_user_access(request.user, self.parent_model, 'unattach', main, sub, self.relationship):
|
||||
raise PermissionDenied()
|
||||
|
||||
|
||||
@ -240,11 +243,13 @@ class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
# somewhat lame that delete has to call it's own permissions check
|
||||
obj = self.model.objects.get(pk=kwargs['pk'])
|
||||
# FIXME: Why isn't the active check being caught earlier by RBAC?
|
||||
if getattr(obj, 'active', True) == False:
|
||||
raise Http404()
|
||||
if getattr(obj, 'is_active', True) == False:
|
||||
raise Http404()
|
||||
if not request.user.is_superuser and not self.delete_permissions_check(request, obj):
|
||||
#if not request.user.is_superuser and not self.delete_permissions_check(request, obj):
|
||||
if not check_user_access(request.user, self.model, 'delete', obj):
|
||||
raise PermissionDenied()
|
||||
if isinstance(obj, PrimordialModel):
|
||||
obj.name = "_deleted_%s_%s" % (str(datetime.time()), obj.name)
|
||||
@ -258,28 +263,6 @@ class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
raise Exception("InternalError: destroy() not implemented yet for %s" % obj)
|
||||
return HttpResponse(status=204)
|
||||
|
||||
def delete_permissions_check(self, request, obj):
|
||||
if isinstance(obj, PrimordialModel):
|
||||
return self.__class__.model.can_user_delete(request.user, obj)
|
||||
elif isinstance(obj, User):
|
||||
return UserHelper.can_user_delete(request.user, obj)
|
||||
raise PermissionDenied()
|
||||
|
||||
|
||||
def item_permissions_check(self, request, obj):
|
||||
|
||||
if request.method == 'GET':
|
||||
if type(obj) == User:
|
||||
return UserHelper.can_user_read(request.user, obj)
|
||||
else:
|
||||
return self.__class__.model.can_user_read(request.user, obj)
|
||||
elif request.method in [ 'PUT' ]:
|
||||
if type(obj) == User:
|
||||
return UserHelper.can_user_administrate(request.user, obj, request.DATA)
|
||||
else:
|
||||
return self.__class__.model.can_user_administrate(request.user, obj, request.DATA)
|
||||
return False
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
self.put_filter(request, *args, **kwargs)
|
||||
return super(BaseDetail, self).put(request, *args, **kwargs)
|
||||
@ -297,26 +280,16 @@ class VariableBaseDetail(BaseDetail):
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
raise PermissionDenied()
|
||||
|
||||
def delete_permissions_check(self, request, obj):
|
||||
raise PermissionDenied()
|
||||
|
||||
def item_permissions_check(self, request, obj):
|
||||
through_obj = self.__class__.parent_model.objects.get(pk = self.request.args['pk'])
|
||||
if request.method == 'GET':
|
||||
return self.__class__.parent_model.can_user_read(request.user, through_obj)
|
||||
elif request.method in [ 'PUT' ]:
|
||||
return self.__class__.parent_model.can_user_administrate(request.user, through_obj, request.DATA)
|
||||
return False
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
# FIXME: lots of overlap between put and get here, need to refactor
|
||||
|
||||
through_obj = self.__class__.parent_model.objects.get(pk=kwargs['pk'])
|
||||
|
||||
has_permission = Inventory._has_permission_types(request.user, through_obj.inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
|
||||
if not has_permission:
|
||||
raise PermissionDenied()
|
||||
#has_permission = Inventory._has_permission_types(request.user, through_obj.inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
#if not has_permission:
|
||||
# raise PermissionDenied()
|
||||
if not check_user_access(request.user, Inventory, 'change', through_obj.inventory, None):
|
||||
raise PermissionDenied
|
||||
|
||||
this_object = None
|
||||
|
||||
@ -354,8 +327,11 @@ class VariableBaseDetail(BaseDetail):
|
||||
setattr(through_obj, self.__class__.reverse_relationship, this_object)
|
||||
through_obj.save()
|
||||
|
||||
has_permission = Inventory._has_permission_types(request.user, through_obj.inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
if not has_permission:
|
||||
raise PermissionDenied()
|
||||
#has_permission = Inventory._has_permission_types(request.user, through_obj.inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
#if not has_permission:
|
||||
# raise PermissionDenied()
|
||||
if not check_user_access(request.user, Inventory, 'read', through_obj.inventory):
|
||||
raise PermissionDenied
|
||||
|
||||
return Response(status=status.HTTP_200_OK, data=python_json.loads(this_object.data))
|
||||
|
||||
|
||||
@ -81,7 +81,9 @@ class EditHelper(object):
|
||||
@classmethod
|
||||
def illegal_changes(cls, request, obj, model_class):
|
||||
''' have any illegal changes been made (for a PUT request)? '''
|
||||
can_admin = model_class.can_user_administrate(request.user, obj, request.DATA)
|
||||
from lib.main.access import *
|
||||
#can_admin = model_class.can_user_administrate(request.user, obj, request.DATA)
|
||||
can_admin = check_user_access(request.user, User, 'change', obj, request.DATA)
|
||||
if (not can_admin) or (can_admin == 'partial'):
|
||||
check_fields = model_class.admin_only_edit_fields
|
||||
changed = cls.fields_changed(check_fields, obj, request.DATA)
|
||||
@ -107,51 +109,6 @@ class UserHelper(object):
|
||||
# fields that the user themselves cannot edit, but are not actually read only
|
||||
admin_only_edit_fields = ('last_name', 'first_name', 'username', 'is_active', 'is_superuser')
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
''' a user can be administrated if they are themselves, or by org admins or superusers '''
|
||||
if user == obj:
|
||||
return 'partial'
|
||||
if user.is_superuser:
|
||||
return True
|
||||
matching_orgs = obj.organizations.filter(admins__in = [user]).count()
|
||||
return matching_orgs
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
''' a user can be read if they are on the same team or can be administrated '''
|
||||
matching_teams = user.teams.filter(users__in = [ user ]).count()
|
||||
return matching_teams or cls.can_user_administrate(user, obj, None)
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
matching_orgs = obj.organizations.filter(admins__in = [user]).count()
|
||||
return matching_orgs
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
# TODO: reuse. make helper functions like "is user an org admin"
|
||||
# apply throughout permissions code
|
||||
if user.is_superuser:
|
||||
return True
|
||||
return user.admin_of_organizations.count() > 0
|
||||
|
||||
@classmethod
|
||||
def can_user_attach(cls, user, obj, sub_obj, relationship_type, data):
|
||||
if type(sub_obj) != User:
|
||||
if not sub_obj.can_user_read(user, sub_obj):
|
||||
return False
|
||||
rc = cls.can_user_administrate(user, obj, None)
|
||||
if not rc:
|
||||
return False
|
||||
return sub_obj.__class__.can_user_read(user, sub_obj)
|
||||
|
||||
@classmethod
|
||||
def can_user_unattach(cls, user, obj, sub_obj, relationship_type):
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
class PrimordialModel(models.Model):
|
||||
'''
|
||||
common model for all object types that have these standard fields
|
||||
@ -165,6 +122,8 @@ class PrimordialModel(models.Model):
|
||||
description = models.TextField(blank=True, default='')
|
||||
created_by = models.ForeignKey('auth.User', on_delete=SET_NULL, null=True, related_name='%s(class)s_created', editable=False) # not blank=False on purpose for admin!
|
||||
creation_date = models.DateField(auto_now_add=True)
|
||||
#created = models.DateTimeField(auto_now_add=True)
|
||||
#modified = models.DateTimeField(auto_now=True)
|
||||
tags = models.ManyToManyField('Tag', related_name='%(class)s_by_tag', blank=True)
|
||||
audit_trail = models.ManyToManyField('AuditTrail', related_name='%(class)s_by_audit_trail', blank=True)
|
||||
active = models.BooleanField(default=True)
|
||||
@ -172,45 +131,6 @@ class PrimordialModel(models.Model):
|
||||
def __unicode__(self):
|
||||
return unicode("%s-%s"% (self.name, self.id))
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
# FIXME: do we want a seperate method to override put? This is kind of general purpose
|
||||
raise Exception("can_user_administrate needs to be implemented in model subclass")
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
raise Exception("can_user_delete needs to be implemented in model subclass")
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
raise Exception("can_user_read needs to be implemented in model subclass")
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
return user.is_superuser
|
||||
|
||||
@classmethod
|
||||
def can_user_attach(cls, user, obj, sub_obj, relationship_type, data):
|
||||
''' whether you can add sub_obj to obj using the relationship type in a subobject view '''
|
||||
if type(sub_obj) != User:
|
||||
if not sub_obj.can_user_read(user, sub_obj):
|
||||
return False
|
||||
rc = cls.can_user_administrate(user, obj, None)
|
||||
if not rc:
|
||||
return False
|
||||
|
||||
# in order to attach something you also be able to read what you are attaching
|
||||
if type(sub_obj) == User:
|
||||
# we already check that the user is an admin or org admin up in base_views.py
|
||||
# because the user doesn't have the attributes on it directly to tie it to the org
|
||||
return True
|
||||
else:
|
||||
return sub_obj.__class__.can_user_read(user, sub_obj)
|
||||
|
||||
@classmethod
|
||||
def can_user_unattach(cls, user, obj, sub_obj, relationship):
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
class CommonModel(PrimordialModel):
|
||||
''' a base model where the name is unique '''
|
||||
|
||||
@ -243,17 +163,6 @@ class Tag(models.Model):
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:tags_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
# anybody can make up tags
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
# anybody can read tags, we won't show much detail other than the names
|
||||
return True
|
||||
|
||||
|
||||
class AuditTrail(models.Model):
|
||||
'''
|
||||
changing any object records the change
|
||||
@ -286,28 +195,6 @@ class Organization(CommonModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:organizations_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
return user in obj.admins.all()
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
# FIXME: super user checks should be higher up so we don't have to repeat them
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if obj.created_by == user:
|
||||
return True
|
||||
rc = user in obj.admins.all()
|
||||
return rc
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
return cls.can_user_administrate(user,obj,None) or user in obj.users.all()
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@ -326,82 +213,6 @@ class Inventory(CommonModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:inventory_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def _has_permission_types(cls, user, obj, allowed):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
by_org_admin = obj.organization.admins.filter(pk = user.pk).count()
|
||||
by_team_permission = obj.permissions.filter(
|
||||
team__in = user.teams.all(),
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
by_user_permission = obj.permissions.filter(
|
||||
user = user,
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
|
||||
result = (by_org_admin + by_team_permission + by_user_permission)
|
||||
return result > 0
|
||||
|
||||
@classmethod
|
||||
def _has_any_inventory_permission_types(cls, user, 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 user.is_superuser:
|
||||
return True
|
||||
by_org_admin = user.organizations.filter(
|
||||
admins__in = [ user ]
|
||||
).count()
|
||||
by_team_permission = Permission.objects.filter(
|
||||
team__in = user.teams.all(),
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
by_user_permission = user.permissions.filter(
|
||||
permission_type__in = allowed
|
||||
).count()
|
||||
|
||||
result = (by_org_admin + by_team_permission + by_user_permission)
|
||||
return result > 0
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
if not 'organization' in data:
|
||||
return True
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if not user.is_superuser:
|
||||
org = Organization.objects.get(pk=data['organization'])
|
||||
if user in org.admins.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
return cls._has_permission_types(user, obj, PERMISSION_TYPES_ALLOWING_INVENTORY_ADMIN)
|
||||
|
||||
@classmethod
|
||||
def can_user_attach(cls, user, obj, sub_obj, relationship_type, data):
|
||||
''' 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):
|
||||
return False
|
||||
return cls._has_permission_types(user, obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
|
||||
@classmethod
|
||||
def can_user_unattach(cls, user, obj, sub_obj, relationship):
|
||||
return cls._has_permission_types(user, obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
return cls._has_permission_types(user, obj, PERMISSION_TYPES_ALLOWING_INVENTORY_READ)
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
return cls._has_permission_types(user, obj, PERMISSION_TYPES_ALLOWING_INVENTORY_ADMIN)
|
||||
|
||||
class Host(CommonModelNameNotUnique):
|
||||
'''
|
||||
A managed node
|
||||
@ -417,18 +228,6 @@ class Host(CommonModelNameNotUnique):
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
return Inventory.can_user_read(user, obj.inventory)
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
if not 'inventory' in data:
|
||||
return False
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
rc = Inventory._has_permission_types(user, inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
return rc
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:hosts_detail', args=(self.pk,))
|
||||
|
||||
@ -453,23 +252,6 @@ class Group(CommonModelNameNotUnique):
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
if not 'inventory' in data:
|
||||
return False
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
return Inventory._has_permission_types(user, inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
# here this controls whether the user can attach subgroups
|
||||
return Inventory._has_permission_types(user, obj.inventory, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
return Inventory.can_user_read(user, obj.inventory)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:groups_detail', args=(self.pk,))
|
||||
|
||||
@ -495,15 +277,6 @@ class VariableData(CommonModelNameNotUnique):
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:variable_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
''' a user can be read if they are on the same team or can be administrated '''
|
||||
if obj.host is not None:
|
||||
return Inventory.can_user_read(user, obj.host.inventory)
|
||||
if obj.group is not None:
|
||||
return Inventory.can_user_read(user, obj.group.inventory)
|
||||
return False
|
||||
|
||||
class Credential(CommonModelNameNotUnique):
|
||||
'''
|
||||
A credential contains information about how to talk to a remote set of hosts
|
||||
@ -571,44 +344,6 @@ class Credential(CommonModelNameNotUnique):
|
||||
def needs_sudo_password(self):
|
||||
return self.sudo_password == 'ASK'
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if user == obj.user:
|
||||
return True
|
||||
|
||||
if obj.user:
|
||||
if (obj.user.organizations.filter(admins__in = [user]).count()):
|
||||
return True
|
||||
if obj.team:
|
||||
if user in obj.team.organization.admins.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
if obj.user is None and obj.team is None:
|
||||
# unassociated credentials may be marked deleted by anyone
|
||||
return True
|
||||
return cls.can_user_administrate(user,obj,None)
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
''' a user can be read if they are on the same team or can be administrated '''
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if 'user' in data:
|
||||
user_obj = User.objects.get(pk=data['user'])
|
||||
return UserHelper.can_user_administrate(user, user_obj, data)
|
||||
if 'team' in data:
|
||||
team_obj = Team.objects.get(pk=data['team'])
|
||||
return Team.can_user_administrate(user, team_obj, data)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:credentials_detail', args=(self.pk,))
|
||||
|
||||
@ -627,37 +362,6 @@ class Team(CommonModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:teams_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
# FIXME -- audit when this is called explicitly, if any
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if user in obj.organization.admins.all():
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
if cls.can_user_administrate(user, obj, None):
|
||||
return True
|
||||
if obj.users.filter(pk__in = [ user.pk ]).count():
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if Organization.objects.filter(admins__in = [user]).count():
|
||||
# team assignment to organizations is handled elsewhere, this just creates
|
||||
# a blank team
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
class Project(CommonModel):
|
||||
'''
|
||||
A project represents a playbook git repo that can access a set of inventories
|
||||
@ -682,30 +386,6 @@ class Project(CommonModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:projects_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if obj.created_by == user:
|
||||
return True
|
||||
organizations = Organization.objects.filter(admins__in = [ user ], projects__in = [ obj ])
|
||||
for org in organizations:
|
||||
if org in project.organizations():
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
if cls.can_user_administrate(user, obj, None):
|
||||
return True
|
||||
# and also if I happen to be on a team inside the project
|
||||
# FIXME: add this too
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
@property
|
||||
def available_playbooks(self):
|
||||
playbooks = []
|
||||
@ -780,33 +460,6 @@ class Permission(CommonModelNameNotUnique):
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:permissions_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
# a permission can be administrated by a super
|
||||
# or if a user permission, that an admin of a user's organization
|
||||
# or if a team permission, an admin of that team's organization
|
||||
if obj.user and obj.user.organizations.filter(admins__in = [user]).count() > 0:
|
||||
return True
|
||||
if obj.team and obj.team.organization.admins.filter(user=user).count() > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
# a permission can be seen by the assigned user or team
|
||||
# or anyone who can administrate that permission
|
||||
if obj.user and obj.user == user:
|
||||
return True
|
||||
if obj.team and obj.team.users.filter(pk = user.pk).count() > 0:
|
||||
return True
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
@classmethod
|
||||
def can_user_delete(cls, user, obj):
|
||||
return cls.can_user_administrate(user, obj, None)
|
||||
|
||||
# TODO: other job types (later)
|
||||
|
||||
class JobTemplate(CommonModel):
|
||||
@ -892,85 +545,7 @@ class JobTemplate(CommonModel):
|
||||
return job
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:job_templates_detail', args=(self.pk,))
|
||||
|
||||
@classmethod
|
||||
def can_user_read(cls, user, obj):
|
||||
# you can only see the job templates that you have permission to launch.
|
||||
data = dict(
|
||||
inventory = obj.inventory.pk,
|
||||
project = obj.project.pk,
|
||||
job_type = obj.job_type
|
||||
)
|
||||
return cls.can_user_add(user, data)
|
||||
|
||||
@classmethod
|
||||
def can_user_administrate(cls, user, obj, data):
|
||||
'''
|
||||
'''
|
||||
|
||||
@classmethod
|
||||
def can_user_add(cls, user, data):
|
||||
'''
|
||||
a user can create a job template if they are a superuser, an org admin of any org
|
||||
that the project is a member, or if they have user or team based permissions tying
|
||||
the project to the inventory source for the given action.
|
||||
|
||||
users who are able to create deploy jobs can also make check (dry run) jobs
|
||||
'''
|
||||
|
||||
if user.is_superuser:
|
||||
return True
|
||||
if not data or '_method' in data: # FIXME: So the browseable API will work?
|
||||
return True
|
||||
project = Project.objects.get(pk=data['project'])
|
||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||
|
||||
admin_of_orgs = project.organizations.filter(admins__in = [ user ])
|
||||
if admin_of_orgs.count() > 0:
|
||||
return True
|
||||
job_type = data['job_type']
|
||||
|
||||
has_launch_permission = False
|
||||
user_permissions = Permission.objects.filter(inventory=inventory, project=project, user=user)
|
||||
for perm in user_permissions:
|
||||
if job_type == PERM_INVENTORY_CHECK:
|
||||
# if you have run permissions, you can also create check jobs
|
||||
has_launch_permission = True
|
||||
elif job_type == PERM_INVENTORY_DEPLOY and perm.permission_type == PERM_INVENTORY_DEPLOY:
|
||||
# you need explicit run permissions to make run jobs
|
||||
has_launch_permission = True
|
||||
team_permissions = Permission.objects.filter(inventory=inventory, project=project, team__users__in = [user])
|
||||
for perm in team_permissions:
|
||||
if job_type == PERM_INVENTORY_CHECK:
|
||||
# if you have run permissions, you can also create check jobs
|
||||
has_launch_permission = True
|
||||
elif job_type == PERM_INVENTORY_DEPLOY and perm.permission_type == PERM_INVENTORY_DEPLOY:
|
||||
# you need explicit run permissions to make run jobs
|
||||
has_launch_permission = True
|
||||
|
||||
if not has_launch_permission:
|
||||
return False
|
||||
|
||||
# make sure user owns the credentials they are using
|
||||
if data.has_key('credential'):
|
||||
has_credential = False
|
||||
credential = Credential.objects.get(pk=data['credential'])
|
||||
if credential.team and credential.team.users.filter(id = user.pk).count():
|
||||
has_credential = True
|
||||
if credential.user and credential.user == user:
|
||||
has_credential = True
|
||||
if not has_credential:
|
||||
return False
|
||||
|
||||
# shouldn't really matter with permissions given, but make sure the user
|
||||
# is also currently on the team in case they were added a per-user permission and then removed
|
||||
# from the project.
|
||||
if project.teams.filter(users__in = [ user ]).count():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
return reverse('main:job_template_detail', args=(self.pk,))
|
||||
|
||||
class Job(CommonModel):
|
||||
'''
|
||||
@ -1086,7 +661,7 @@ class Job(CommonModel):
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:jobs_detail', args=(self.pk,))
|
||||
return reverse('main:job_detail', args=(self.pk,))
|
||||
|
||||
@property
|
||||
def celery_task(self):
|
||||
@ -1248,6 +823,9 @@ class JobEvent(models.Model):
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('main:job_event_detail', args=(self.pk,))
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s @ %s' % (self.get_event_display(), self.created.isoformat())
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import logging
|
||||
from django.http import Http404
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import permissions
|
||||
from lib.main.access import *
|
||||
|
||||
logger = logging.getLogger('lib.main.rbac')
|
||||
|
||||
@ -26,6 +27,48 @@ logger = logging.getLogger('lib.main.rbac')
|
||||
|
||||
class CustomRbac(permissions.BasePermission):
|
||||
|
||||
def _check_options_permissions(self, request, view, obj=None):
|
||||
return self._check_get_permissions(request, view, obj)
|
||||
|
||||
def _check_head_permissions(self, request, view, obj=None):
|
||||
return self._check_get_permissions(request, view, obj)
|
||||
|
||||
def _check_get_permissions(self, request, view, obj=None):
|
||||
if hasattr(view, 'parent_model'):
|
||||
parent_obj = view.parent_model.objects.get(pk=view.kwargs['pk'])
|
||||
if not check_user_access(request.user, view.parent_model, 'read',
|
||||
parent_obj):
|
||||
return False
|
||||
if not obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'read', obj)
|
||||
|
||||
def _check_post_permissions(self, request, view, obj=None):
|
||||
if hasattr(view, 'parent_model'):
|
||||
parent_obj = view.parent_model.objects.get(pk=view.kwargs['pk'])
|
||||
#if not check_user_access(request.user, view.parent_model, 'change',
|
||||
# parent_obj, None):
|
||||
# return False
|
||||
# FIXME: attach/unattach
|
||||
return True
|
||||
else:
|
||||
if obj:
|
||||
return True
|
||||
return check_user_access(request.user, view.model, 'add', request.DATA)
|
||||
|
||||
def _check_put_permissions(self, request, view, obj=None):
|
||||
if not obj:
|
||||
return True # FIXME: For some reason this needs to return True
|
||||
# because it is first called with obj=None?
|
||||
return check_user_access(request.user, view.model, 'change', obj,
|
||||
request.DATA)
|
||||
|
||||
def _check_delete_permissions(self, request, view, obj=None):
|
||||
if not obj:
|
||||
return True # FIXME: For some reason this needs to return True
|
||||
# because it is first called with obj=None?
|
||||
return check_user_access(request.user, view.model, 'delete', obj)
|
||||
|
||||
def _check_permissions(self, request, view, obj=None):
|
||||
# Check that obj (if given) is active, otherwise raise a 404.
|
||||
active = getattr(obj, 'active', getattr(obj, 'is_active', True))
|
||||
@ -42,6 +85,15 @@ class CustomRbac(permissions.BasePermission):
|
||||
# Always allow superusers (as long as they are active).
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
# Check permissions for the given view and object, based on the request
|
||||
# method used.
|
||||
check_method = getattr(self, '_check_%s_permissions' % \
|
||||
request.method.lower(), None)
|
||||
result = check_method and check_method(request, view, obj)
|
||||
if not result:
|
||||
raise PermissionDenied()
|
||||
return result
|
||||
|
||||
# If no obj is given, check list permissions.
|
||||
if obj is None:
|
||||
if getattr(view, 'list_permissions_check', None):
|
||||
|
||||
@ -14,33 +14,91 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible Commander. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Django
|
||||
from django.contrib.auth.models import User
|
||||
from lib.main.models import *
|
||||
from rest_framework import serializers, pagination
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.serializers import json
|
||||
|
||||
# Django REST framework
|
||||
from rest_framework import serializers, pagination
|
||||
from rest_framework.templatetags.rest_framework import replace_query_param
|
||||
|
||||
# Ansible Commander
|
||||
from lib.main.models import *
|
||||
|
||||
BASE_FIELDS = ('id', 'url', 'related', 'creation_date', 'name', 'description')
|
||||
|
||||
class NextPageField(pagination.NextPageField):
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.has_next():
|
||||
return None
|
||||
page = value.next_page_number()
|
||||
request = self.context.get('request')
|
||||
url = request and request.get_full_path() or ''
|
||||
return replace_query_param(url, self.page_field, page)
|
||||
|
||||
class PreviousPageField(pagination.NextPageField):
|
||||
|
||||
def to_native(self, value):
|
||||
if not value.has_previous():
|
||||
return None
|
||||
page = value.previous_page_number()
|
||||
request = self.context.get('request')
|
||||
url = request and request.get_full_path() or ''
|
||||
return replace_query_param(url, self.page_field, page)
|
||||
|
||||
class PaginationSerializer(pagination.BasePaginationSerializer):
|
||||
'''
|
||||
Custom pagination serializer to output only URL path (without host/port).
|
||||
'''
|
||||
|
||||
count = serializers.Field(source='paginator.count')
|
||||
next = NextPageField(source='*')
|
||||
previous = PreviousPageField(source='*')
|
||||
|
||||
class BaseSerializer(serializers.ModelSerializer):
|
||||
pass
|
||||
|
||||
class OrganizationSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
url = serializers.SerializerMethodField('get_absolute_url')
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
# make certain fields read only
|
||||
creation_date = serializers.DateTimeField(read_only=True) # FIXME: is model Date or DateTime, fix model
|
||||
active = serializers.BooleanField(read_only=True)
|
||||
creation_date = serializers.SerializerMethodField('get_creation_date') # FIXME: is model Date or DateTime, fix model
|
||||
active = serializers.SerializerMethodField('get_active')
|
||||
|
||||
def get_absolute_url(self, obj):
|
||||
if isinstance(obj, User):
|
||||
return reverse('main:users_detail', args=(obj.pk,))
|
||||
else:
|
||||
return obj.get_absolute_url()
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict()
|
||||
if getattr(obj, 'created_by', None):
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
return res
|
||||
|
||||
def get_creation_date(self, obj):
|
||||
if isinstance(obj, User):
|
||||
return obj.date_joined.date()
|
||||
else:
|
||||
return obj.creation_date
|
||||
|
||||
def get_active(self, obj):
|
||||
if isinstance(obj, User):
|
||||
return obj.is_active
|
||||
else:
|
||||
return obj.active
|
||||
|
||||
class OrganizationSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = ('url', 'id', 'name', 'description', 'creation_date', 'related') # whitelist
|
||||
fields = BASE_FIELDS
|
||||
|
||||
def get_related(self, obj):
|
||||
''' related resource URLs '''
|
||||
|
||||
res = dict(
|
||||
res = super(OrganizationSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
audit_trail = reverse('main:organizations_audit_trail_list', args=(obj.pk,)),
|
||||
projects = reverse('main:organizations_projects_list', args=(obj.pk,)),
|
||||
inventories = reverse('main:organizations_inventories_list', args=(obj.pk,)),
|
||||
@ -48,181 +106,137 @@ class OrganizationSerializer(BaseSerializer):
|
||||
admins = reverse('main:organizations_admins_list', args=(obj.pk,)),
|
||||
tags = reverse('main:organizations_tags_list', args=(obj.pk,)),
|
||||
teams = reverse('main:organizations_teams_list', args=(obj.pk,)),
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
|
||||
))
|
||||
return res
|
||||
|
||||
class AuditTrailSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = AuditTrail
|
||||
fields = ('url', 'id', 'modified_by', 'delta', 'detail', 'comment')
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict()
|
||||
res = super(AuditTrailSerializer, self).get_related(obj)
|
||||
if obj.modified_by:
|
||||
res['modified_by'] = reverse('main:users_detail', args=(obj.modified_by.pk,))
|
||||
return res
|
||||
|
||||
class ProjectSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
available_playbooks = serializers.Field(source='available_playbooks')
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ('url', 'id', 'name', 'description', 'creation_date', 'local_path')#, 'default_playbook', 'scm_type')
|
||||
fields = BASE_FIELDS + ('local_path', 'available_playbooks')
|
||||
# 'default_playbook', 'scm_type')
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict(
|
||||
res = super(ProjectSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
organizations = reverse('main:projects_organizations_list', args=(obj.pk,)),
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
))
|
||||
return res
|
||||
|
||||
|
||||
class InventorySerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = Inventory
|
||||
fields = ('url', 'id', 'name', 'description', 'creation_date', 'related', 'organization')
|
||||
fields = BASE_FIELDS + ('organization',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict(
|
||||
res = super(InventorySerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
hosts = reverse('main:inventory_hosts_list', args=(obj.pk,)),
|
||||
groups = reverse('main:inventory_groups_list', args=(obj.pk,)),
|
||||
organization = reverse('main:organizations_detail', args=(obj.organization.pk,)),
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
))
|
||||
return res
|
||||
|
||||
class HostSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = Host
|
||||
fields = ('url', 'id', 'name', 'description', 'creation_date', 'related', 'inventory')
|
||||
fields = BASE_FIELDS + ('inventory',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict(
|
||||
res = super(HostSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
variable_data = reverse('main:hosts_variable_detail', args=(obj.pk,)),
|
||||
inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)),
|
||||
)
|
||||
job_events = reverse('main:host_job_event_list', args=(obj.pk,)),
|
||||
))
|
||||
# NICE TO HAVE: possible reverse resource to show what groups the host is in
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
return res
|
||||
|
||||
class GroupSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ('url', 'id', 'name', 'description', 'creation_date', 'inventory')
|
||||
fields = BASE_FIELDS + ('inventory',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict(
|
||||
res = super(GroupSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
variable_data = reverse('main:groups_variable_detail', args=(obj.pk,)),
|
||||
hosts = reverse('main:groups_hosts_list', args=(obj.pk,)),
|
||||
children = reverse('main:groups_children_list', args=(obj.pk,)),
|
||||
all_hosts = reverse('main:groups_all_hosts_list', args=(obj.pk,)),
|
||||
inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)),
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
))
|
||||
return res
|
||||
|
||||
class TeamSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = ('url', 'id', 'related', 'name', 'description', 'organization', 'creation_date')
|
||||
|
||||
# FIXME: TODO: include related collections but also related FK urls
|
||||
fields = BASE_FIELDS + ('organization',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict(
|
||||
res = super(TeamSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
projects = reverse('main:teams_projects_list', args=(obj.pk,)),
|
||||
users = reverse('main:teams_users_list', args=(obj.pk,)),
|
||||
credentials = reverse('main:teams_credentials_list', args=(obj.pk,)),
|
||||
organization = reverse('main:organizations_detail', args=(obj.organization.pk,)),
|
||||
permissions = reverse('main:teams_permissions_list', args=(obj.pk,)),
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
))
|
||||
return res
|
||||
|
||||
class PermissionSerializer(BaseSerializer):
|
||||
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = Permission
|
||||
fields = ( 'url', 'id', 'user', 'team', 'name', 'description', 'creation_date',
|
||||
'project', 'inventory', 'permission_type' )
|
||||
fields = BASE_FIELDS + ('user', 'team', 'project', 'inventory',
|
||||
'permission_type',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict()
|
||||
res = super(PermissionSerializer, self).get_related(obj)
|
||||
if obj.user:
|
||||
res['user'] = reverse('main:users_detail', args=(obj.user.pk,))
|
||||
if obj.team:
|
||||
res['team'] = reverse('main:teams_detail', args=(obj.team.pk,))
|
||||
if self.project:
|
||||
if obj.project:
|
||||
res['project'] = reverse('main:projects_detail', args=(obj.project.pk,))
|
||||
if self.inventory:
|
||||
if obj.inventory:
|
||||
res['inventory'] = reverse('main:inventory_detail', args=(obj.inventory.pk,))
|
||||
if self.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
return res
|
||||
|
||||
class CredentialSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
# FIXME: may want to make some of these filtered based on user accessing
|
||||
|
||||
class Meta:
|
||||
model = Credential
|
||||
fields = (
|
||||
'url', 'id', 'related', 'name', 'description', 'creation_date',
|
||||
'ssh_username', 'ssh_password', 'ssh_key_data', 'ssh_key_unlock',
|
||||
'sudo_username', 'sudo_password', 'user', 'team',
|
||||
)
|
||||
fields = BASE_FIELDS + ('ssh_username', 'ssh_password', 'ssh_key_data',
|
||||
'ssh_key_unlock', 'sudo_username',
|
||||
'sudo_password', 'user', 'team',)
|
||||
|
||||
def get_related(self, obj):
|
||||
# FIXME: no related collections, do want to add user and team if defined
|
||||
res = dict(
|
||||
)
|
||||
res = super(CredentialSerializer, self).get_related(obj)
|
||||
if obj.user:
|
||||
res['user'] = reverse('main:users_detail', args=(obj.user.pk,))
|
||||
res['user'] = reverse('main:users_detail', args=(obj.user.pk,))
|
||||
if obj.team:
|
||||
res['team'] = reverse('main:teams_detail', args=(obj.team.pk,))
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
res['team'] = reverse('main:teams_detail', args=(obj.team.pk,))
|
||||
return res
|
||||
|
||||
def validate(self, attrs):
|
||||
@ -237,97 +251,122 @@ class CredentialSerializer(BaseSerializer):
|
||||
|
||||
class UserSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.SerializerMethodField('get_absolute_url_override')
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('url', 'id', 'username', 'first_name', 'last_name', 'email', 'is_active', 'is_superuser', 'related')
|
||||
fields = ('id', 'url', 'related', 'creation_date', 'username',
|
||||
'first_name', 'last_name', 'email', 'is_active',
|
||||
'is_superuser',)
|
||||
|
||||
def get_related(self, obj):
|
||||
return dict(
|
||||
res = super(UserSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
teams = reverse('main:users_teams_list', args=(obj.pk,)),
|
||||
organizations = reverse('main:users_organizations_list', args=(obj.pk,)),
|
||||
admin_of_organizations = reverse('main:users_admin_organizations_list', args=(obj.pk,)),
|
||||
projects = reverse('main:users_projects_list', args=(obj.pk,)),
|
||||
credentials = reverse('main:users_credentials_list', args=(obj.pk,)),
|
||||
permissions = reverse('main:users_permissions_list', args=(obj.pk,)),
|
||||
)
|
||||
|
||||
def get_absolute_url_override(self, obj):
|
||||
return reverse('main:users_detail', args=(obj.pk,))
|
||||
|
||||
))
|
||||
return res
|
||||
|
||||
class TagSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = ('url', 'id', 'name')
|
||||
fields = ('id', 'url', 'name')
|
||||
|
||||
def get_related(self, obj):
|
||||
res = dict()
|
||||
res = super(TagSerializer, self).get_related(obj)
|
||||
res.pop('created_by', None)
|
||||
return res
|
||||
|
||||
class VariableDataSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = VariableData
|
||||
fields = ('url', 'id', 'data', 'related', 'name', 'description', 'creation_date')
|
||||
fields = BASE_FIELDS + ('data',)
|
||||
|
||||
def get_related(self, obj):
|
||||
# FIXME: add host or group if defined
|
||||
res = dict(
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
res = super(VariableDataSerializer, self).get_related(obj)
|
||||
try:
|
||||
res['host'] = reverse('main:hosts_detail', args=(obj.host.pk,))
|
||||
except Host.DoesNotExist:
|
||||
pass
|
||||
try:
|
||||
res['group'] = reverse('main:groups_detail', args=(obj.group.pk,))
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
return res
|
||||
|
||||
class JobTemplateSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
|
||||
class Meta:
|
||||
model = JobTemplate
|
||||
fields = ('url', 'id', 'related', 'name', 'description', 'job_type', 'credential', 'project', 'inventory', 'created_by', 'creation_date')
|
||||
fields = BASE_FIELDS + ('job_type', 'inventory', 'project', 'playbook',
|
||||
'credential', 'use_sudo', 'forks', 'limit',
|
||||
'verbosity', 'extra_vars')
|
||||
|
||||
def get_related(self, obj):
|
||||
# FIXME: fill in once further defined. related resources, credential, project, inventory, etc
|
||||
res = dict(
|
||||
credential = reverse('main:credentials_detail', args=(obj.credential.pk,)),
|
||||
project = reverse('main:projects_detail', args=(obj.project.pk,)),
|
||||
res = super(JobTemplateSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)),
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
project = reverse('main:projects_detail', args=(obj.project.pk,)),
|
||||
jobs = reverse('main:job_template_job_list', args=(obj.pk,)),
|
||||
))
|
||||
if obj.credential:
|
||||
res['credential'] = reverse('main:credentials_detail', args=(obj.credential.pk,))
|
||||
return res
|
||||
|
||||
class JobSerializer(BaseSerializer):
|
||||
|
||||
# add the URL and related resources
|
||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||
related = serializers.SerializerMethodField('get_related')
|
||||
passwords_needed_to_start = serializers.Field(source='get_passwords_needed_to_start')
|
||||
|
||||
class Meta:
|
||||
model = Job
|
||||
fields = ('url', 'id', 'related', 'name', 'description', 'job_type', 'credential', 'project', 'inventory', 'created_by', 'creation_date')
|
||||
fields = BASE_FIELDS + ('job_template', 'job_type', 'inventory',
|
||||
'project', 'playbook', 'credential',
|
||||
'use_sudo', 'forks', 'limit', 'verbosity',
|
||||
'extra_vars', 'status', 'result_stdout',
|
||||
'result_traceback', 'passwords_needed_to_start')
|
||||
|
||||
def get_related(self, obj):
|
||||
# FIXME: fill in once further defined. related resources, credential, project, inventory, etc
|
||||
res = dict(
|
||||
)
|
||||
if obj.created_by:
|
||||
res['created_by'] = reverse('main:users_detail', args=(obj.created_by.pk,))
|
||||
res = super(JobSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
inventory = reverse('main:inventory_detail', args=(obj.inventory.pk,)),
|
||||
project = reverse('main:projects_detail', args=(obj.project.pk,)),
|
||||
credential = reverse('main:credentials_detail', args=(obj.credential.pk,)),
|
||||
job_events = reverse('main:job_job_event_list', args=(obj.pk,)),
|
||||
#hosts = reverse('main:job_???', args=(obj.pk,)),
|
||||
))
|
||||
if obj.job_template:
|
||||
res['job_template'] = reverse('main:job_template_detail', args=(obj.job_template.pk,))
|
||||
return res
|
||||
|
||||
|
||||
class JobHostSummarySerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = JobHostSummary
|
||||
fields = ('id', 'url', 'job', 'host', 'changed', 'dark', 'failures',
|
||||
'ok', 'processed', 'skipped', 'related')
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(JobHostSummarySerializer, self).get_related(obj)
|
||||
res['job'] = reverse('main:job_detail', args=(obj.job.pk,))
|
||||
if obj.host:
|
||||
res['host'] = reverse('main:hosts_detail', args=(obj.host.pk,))
|
||||
return res
|
||||
|
||||
class JobEventSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = JobEvent
|
||||
fields = ('id', 'url', 'job', 'event', 'event_data', 'host', 'related')
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(JobEventSerializer, self).get_related(obj)
|
||||
res.update(dict(
|
||||
job = reverse('main:job_detail', args=(obj.job.pk,)),
|
||||
))
|
||||
if obj.host:
|
||||
res['host'] = reverse('main:hosts_detail', args=(obj.host.pk,))
|
||||
return res
|
||||
|
||||
@ -46,17 +46,6 @@ class BaseTestMixin(object):
|
||||
if os.path.exists(project_dir):
|
||||
shutil.rmtree(project_dir, True)
|
||||
|
||||
def make_user(self, username, password=None, super_user=False):
|
||||
user = None
|
||||
password = password or username
|
||||
if super_user:
|
||||
user = User.objects.create_superuser(username, "%s@example.com", password)
|
||||
else:
|
||||
user = User.objects.create_user(username, "%s@example.com", password)
|
||||
self.assertTrue(user.auth_token)
|
||||
self._user_passwords[user.username] = password
|
||||
return user
|
||||
|
||||
@contextlib.contextmanager
|
||||
def current_user(self, user_or_username, password=None):
|
||||
try:
|
||||
@ -71,6 +60,17 @@ class BaseTestMixin(object):
|
||||
finally:
|
||||
self._current_auth = previous_auth
|
||||
|
||||
def make_user(self, username, password=None, super_user=False):
|
||||
user = None
|
||||
password = password or username
|
||||
if super_user:
|
||||
user = User.objects.create_superuser(username, "%s@example.com", password)
|
||||
else:
|
||||
user = User.objects.create_user(username, "%s@example.com", password)
|
||||
self.assertTrue(user.auth_token)
|
||||
self._user_passwords[user.username] = password
|
||||
return user
|
||||
|
||||
def make_organizations(self, created_by, count=1):
|
||||
results = []
|
||||
for x in range(0, count):
|
||||
@ -80,35 +80,51 @@ class BaseTestMixin(object):
|
||||
))
|
||||
return results
|
||||
|
||||
def make_projects(self, created_by, count=1, playbook_content=''):
|
||||
results = []
|
||||
|
||||
def make_project(self, name, description='', created_by=None,
|
||||
playbook_content=''):
|
||||
if not os.path.exists(settings.PROJECTS_ROOT):
|
||||
os.makedirs(settings.PROJECTS_ROOT)
|
||||
# Create temp project directory.
|
||||
project_dir = tempfile.mkdtemp(dir=settings.PROJECTS_ROOT)
|
||||
self._temp_project_dirs.append(project_dir)
|
||||
# Create temp playbook in project (if playbook content is given).
|
||||
if playbook_content:
|
||||
handle, playbook_path = tempfile.mkstemp(suffix='.yml',
|
||||
dir=project_dir)
|
||||
test_playbook_file = os.fdopen(handle, 'w')
|
||||
test_playbook_file.write(playbook_content)
|
||||
test_playbook_file.close()
|
||||
return Project.objects.create(
|
||||
name=name, description=description, local_path=project_dir,
|
||||
created_by=created_by,
|
||||
#scm_type='git', default_playbook='foo.yml',
|
||||
)
|
||||
|
||||
def make_projects(self, created_by, count=1, playbook_content=''):
|
||||
results = []
|
||||
for x in range(0, count):
|
||||
self.object_ctr = self.object_ctr + 1
|
||||
# Create temp project directory.
|
||||
|
||||
project_dir = tempfile.mkdtemp(dir=settings.PROJECTS_ROOT)
|
||||
self._temp_project_dirs.append(project_dir)
|
||||
# Create temp playbook in project (if playbook content is given).
|
||||
if playbook_content:
|
||||
handle, playbook_path = tempfile.mkstemp(suffix='.yml',
|
||||
dir=project_dir)
|
||||
test_playbook_file = os.fdopen(handle, 'w')
|
||||
test_playbook_file.write(playbook_content)
|
||||
test_playbook_file.close()
|
||||
results.append(Project.objects.create(
|
||||
name="proj%s-%s" % (x, self.object_ctr), description="proj%s" % x,
|
||||
#scm_type='git', default_playbook='foo.yml',
|
||||
local_path=project_dir, created_by=created_by
|
||||
results.append(self.make_project(
|
||||
name="proj%s-%s" % (x, self.object_ctr),
|
||||
description="proj%s" % x,
|
||||
created_by=created_by,
|
||||
playbook_content=playbook_content,
|
||||
))
|
||||
return results
|
||||
|
||||
def check_pagination_and_size(self, data, desired_count, previous=None, next=None):
|
||||
self.assertEquals(data['previous'], previous)
|
||||
self.assertEquals(data['next'], next)
|
||||
self.assertTrue('results' in data)
|
||||
self.assertEqual(data['count'], desired_count)
|
||||
self.assertEqual(data['previous'], previous)
|
||||
self.assertEqual(data['next'], next)
|
||||
|
||||
def check_list_ids(self, data, queryset, check_order=False):
|
||||
data_ids = [x['id'] for x in data['results']]
|
||||
qs_ids = queryset.values_list('pk', flat=True)
|
||||
if check_order:
|
||||
self.assertEqual(data_ids, qs_ids)
|
||||
else:
|
||||
self.assertEqual(set(data_ids), set(qs_ids))
|
||||
|
||||
def setup_users(self, just_super_user=False):
|
||||
# Create a user.
|
||||
@ -191,7 +207,7 @@ class BaseTestMixin(object):
|
||||
# TODO: this test helper function doesn't support pagination
|
||||
data = self.get(collection_url, expect=200, auth=auth)
|
||||
return [item['url'] for item in data['results']]
|
||||
|
||||
|
||||
class BaseTest(BaseTestMixin, django.test.TestCase):
|
||||
'''
|
||||
Base class for unit tests.
|
||||
|
||||
@ -24,7 +24,7 @@ from lib.main.tests.base import BaseTest
|
||||
|
||||
__all__ = ['JobTemplateTest', 'JobTest']
|
||||
|
||||
TEST_PLAYBOOK = '''- hosts: mygroup
|
||||
TEST_PLAYBOOK = '''- hosts: all
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: woohoo
|
||||
@ -34,106 +34,360 @@ TEST_PLAYBOOK = '''- hosts: mygroup
|
||||
class BaseJobTest(BaseTest):
|
||||
''''''
|
||||
|
||||
def get_other2_credentials(self):
|
||||
return ('other2', 'other2')
|
||||
def _create_inventory(self, name, organization, created_by,
|
||||
groups_hosts_dict):
|
||||
'''Helper method for creating inventory with groups and hosts.'''
|
||||
inventory = organization.inventories.create(
|
||||
name=name,
|
||||
created_by=created_by,
|
||||
)
|
||||
for group_name, host_names in groups_hosts_dict.items():
|
||||
group = inventory.groups.create(
|
||||
name=group_name,
|
||||
created_by=created_by,
|
||||
)
|
||||
for host_name in host_names:
|
||||
host = inventory.hosts.create(
|
||||
name=host_name,
|
||||
created_by=created_by,
|
||||
)
|
||||
group.hosts.add(host)
|
||||
return inventory
|
||||
|
||||
def get_nobody_credentials(self):
|
||||
return ('nobody', 'nobody')
|
||||
def populate(self):
|
||||
# Here's a little story about the Ansible Bread Company, or ABC. They
|
||||
# make machines that make bread - bakers, slicers, and packagers - and
|
||||
# these machines are each controlled by a Linux boxes, which is in turn
|
||||
# managed by Ansible Commander.
|
||||
|
||||
# Sue is the super user. You don't mess with Sue or you're toast. Ha.
|
||||
self.user_sue = self.make_user('sue', super_user=True)
|
||||
|
||||
# There are three organizations in ABC using Ansible, since it's the
|
||||
# best thing for dev ops automation since, well, sliced bread.
|
||||
|
||||
# Engineering - They design and build the machines.
|
||||
self.org_eng = Organization.objects.create(
|
||||
name='engineering',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
# Support - They fix it when it's not working.
|
||||
self.org_sup = Organization.objects.create(
|
||||
name='support',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
# Operations - They implement the production lines using the machines.
|
||||
self.org_ops = Organization.objects.create(
|
||||
name='operations',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
|
||||
# Alex is Sue's IT assistant who can also administer all of the
|
||||
# organizations.
|
||||
self.user_alex = self.make_user('alex')
|
||||
self.org_eng.admins.add(self.user_alex)
|
||||
self.org_sup.admins.add(self.user_alex)
|
||||
self.org_ops.admins.add(self.user_alex)
|
||||
|
||||
# Bob is the head of engineering. He's an admin for engineering, but
|
||||
# also a user within the operations organization (so he can see the
|
||||
# results if things go wrong in production).
|
||||
self.user_bob = self.make_user('bob')
|
||||
self.org_eng.admins.add(self.user_bob)
|
||||
self.org_ops.users.add(self.user_bob)
|
||||
|
||||
# Chuck is the lead engineer. He has full reign over engineering, but
|
||||
# no other organizations.
|
||||
self.user_chuck = self.make_user('chuck')
|
||||
self.org_eng.admins.add(self.user_chuck)
|
||||
|
||||
# Doug is the other engineer working under Chuck. He can write
|
||||
# playbooks and check them, but Chuck doesn't quite think he's ready to
|
||||
# run them yet. Poor Doug.
|
||||
self.user_doug = self.make_user('doug')
|
||||
self.org_eng.users.add(self.user_doug)
|
||||
|
||||
# Eve is the head of support. She can also see what goes on in
|
||||
# operations to help them troubleshoot problems.
|
||||
self.user_eve = self.make_user('eve')
|
||||
self.org_sup.admins.add(self.user_eve)
|
||||
self.org_ops.users.add(self.user_eve)
|
||||
|
||||
# Frank is the other support guy.
|
||||
self.user_frank = self.make_user('frank')
|
||||
self.org_sup.users.add(self.user_frank)
|
||||
|
||||
# Greg is the head of operations.
|
||||
self.user_greg = self.make_user('greg')
|
||||
self.org_ops.admins.add(self.user_greg)
|
||||
|
||||
# Holly is an operations engineer.
|
||||
self.user_holly = self.make_user('holly')
|
||||
self.org_ops.users.add(self.user_holly)
|
||||
|
||||
# Iris is another operations engineer.
|
||||
self.user_iris = self.make_user('iris')
|
||||
self.org_ops.users.add(self.user_iris)
|
||||
|
||||
# Jim is the intern. He can login, but can't do anything quite yet
|
||||
# except make everyone else fresh coffee.
|
||||
self.user_jim = self.make_user('jim')
|
||||
|
||||
# There are three main projects, one each for the development, test and
|
||||
# production branches of the playbook repository. All three orgs can
|
||||
# use the production branch, support can use the production and testing
|
||||
# branches, and operations can only use the production branch.
|
||||
self.proj_dev = self.make_project('dev', 'development branch',
|
||||
self.user_sue, TEST_PLAYBOOK)
|
||||
self.org_eng.projects.add(self.proj_dev)
|
||||
self.proj_test = self.make_project('test', 'testing branch',
|
||||
self.user_sue, TEST_PLAYBOOK)
|
||||
self.org_eng.projects.add(self.proj_test)
|
||||
self.org_sup.projects.add(self.proj_test)
|
||||
self.proj_prod = self.make_project('prod', 'production branch',
|
||||
self.user_sue, TEST_PLAYBOOK)
|
||||
self.org_eng.projects.add(self.proj_prod)
|
||||
self.org_sup.projects.add(self.proj_prod)
|
||||
self.org_ops.projects.add(self.proj_prod)
|
||||
|
||||
# Operations also has 2 additional projects specific to the east/west
|
||||
# production environments.
|
||||
self.proj_prod_east = self.make_project('prod-east',
|
||||
'east production branch',
|
||||
self.user_sue, TEST_PLAYBOOK)
|
||||
self.org_ops.projects.add(self.proj_prod_east)
|
||||
self.proj_prod_west = self.make_project('prod-west',
|
||||
'west production branch',
|
||||
self.user_sue, TEST_PLAYBOOK)
|
||||
self.org_ops.projects.add(self.proj_prod_west)
|
||||
|
||||
# The engineering organization has a set of servers to use for
|
||||
# development and testing (2 bakers, 1 slicer, 1 packager).
|
||||
self.inv_eng = self._create_inventory(
|
||||
name='engineering environment',
|
||||
organization=self.org_eng,
|
||||
created_by=self.user_sue,
|
||||
groups_hosts_dict={
|
||||
'bakers': ['eng-baker1', 'eng-baker2'],
|
||||
'slicers': ['eng-slicer1'],
|
||||
'packagers': ['eng-packager1'],
|
||||
},
|
||||
)
|
||||
|
||||
# The support organization has a set of servers to use for
|
||||
# testing and reproducing problems from operations (1 baker, 1 slicer,
|
||||
# 1 packager).
|
||||
self.inv_sup = self._create_inventory(
|
||||
name='support environment',
|
||||
organization=self.org_sup,
|
||||
created_by=self.user_sue,
|
||||
groups_hosts_dict={
|
||||
'bakers': ['sup-baker1'],
|
||||
'slicers': ['sup-slicer1'],
|
||||
'packagers': ['sup-packager1'],
|
||||
},
|
||||
)
|
||||
|
||||
# The operations organization manages multiple sets of servers for the
|
||||
# east and west production facilities.
|
||||
self.inv_ops_east = self._create_inventory(
|
||||
name='east production environment',
|
||||
organization=self.org_ops,
|
||||
created_by=self.user_sue,
|
||||
groups_hosts_dict={
|
||||
'bakers': ['east-baker%d' % n for n in range(1, 4)],
|
||||
'slicers': ['east-slicer%d' % n for n in range(1, 3)],
|
||||
'packagers': ['east-packager%d' % n for n in range(1, 3)],
|
||||
},
|
||||
)
|
||||
self.inv_ops_west = self._create_inventory(
|
||||
name='west production environment',
|
||||
organization=self.org_ops,
|
||||
created_by=self.user_sue,
|
||||
groups_hosts_dict={
|
||||
'bakers': ['west-baker%d' % n for n in range(1, 6)],
|
||||
'slicers': ['west-slicer%d' % n for n in range(1, 4)],
|
||||
'packagers': ['west-packager%d' % n for n in range(1, 3)],
|
||||
},
|
||||
)
|
||||
|
||||
# Operations is divided into teams to work on the east/west servers.
|
||||
# Greg and Holly work on east, Greg and iris work on west.
|
||||
self.team_ops_east = self.org_ops.teams.create(
|
||||
name='easterners',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.team_ops_east.projects.add(self.proj_prod)
|
||||
self.team_ops_east.projects.add(self.proj_prod_east)
|
||||
self.team_ops_east.users.add(self.user_greg)
|
||||
self.team_ops_east.users.add(self.user_holly)
|
||||
self.team_ops_west = self.org_ops.teams.create(
|
||||
name='westerners',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.team_ops_west.projects.add(self.proj_prod)
|
||||
self.team_ops_west.projects.add(self.proj_prod_west)
|
||||
self.team_ops_west.users.add(self.user_greg)
|
||||
self.team_ops_west.users.add(self.user_iris)
|
||||
|
||||
# Each user has his/her own set of credentials.
|
||||
from lib.main.tests.tasks import (TEST_SSH_KEY_DATA,
|
||||
TEST_SSH_KEY_DATA_LOCKED,
|
||||
TEST_SSH_KEY_DATA_UNLOCK)
|
||||
self.cred_bob = self.user_bob.credentials.create(
|
||||
ssh_username='bob',
|
||||
ssh_password='ASK',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.cred_chuck = self.user_chuck.credentials.create(
|
||||
ssh_username='chuck',
|
||||
ssh_key_data=TEST_SSH_KEY_DATA,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.cred_doug = self.user_doug.credentials.create(
|
||||
ssh_username='doug',
|
||||
ssh_password='doug doesn\'t mind his password being saved. this '
|
||||
'is why we dont\'t let doug actually run jobs.',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.cred_eve = self.user_eve.credentials.create(
|
||||
ssh_username='eve',
|
||||
ssh_password='ASK',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.cred_frank = self.user_frank.credentials.create(
|
||||
ssh_username='frank',
|
||||
ssh_password='fr@nk the t@nk',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.cred_greg = self.user_greg.credentials.create(
|
||||
ssh_username='greg',
|
||||
ssh_key_data=TEST_SSH_KEY_DATA_LOCKED,
|
||||
ssh_key_unlock='ASK',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.cred_holly = self.user_holly.credentials.create(
|
||||
ssh_username='holly',
|
||||
ssh_password='holly rocks',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.cred_iris = self.user_iris.credentials.create(
|
||||
ssh_username='iris',
|
||||
ssh_password='',
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
|
||||
# Each operations team also has shared credentials they can use.
|
||||
self.cred_ops_east = self.team_ops_east.credentials.create(
|
||||
ssh_username='east',
|
||||
ssh_key_data=TEST_SSH_KEY_DATA_LOCKED,
|
||||
ssh_key_unlock=TEST_SSH_KEY_DATA_UNLOCK,
|
||||
created_by = self.user_sue,
|
||||
)
|
||||
self.cred_ops_west = self.team_ops_west.credentials.create(
|
||||
ssh_username='west',
|
||||
ssh_password='Heading270',
|
||||
created_by = self.user_sue,
|
||||
)
|
||||
|
||||
# FIXME: Define explicit permissions for tests.
|
||||
# other django user is on the project team and can deploy
|
||||
#self.permission1 = Permission.objects.create(
|
||||
# inventory = self.inventory,
|
||||
# project = self.project,
|
||||
# team = self.team,
|
||||
# permission_type = PERM_INVENTORY_DEPLOY,
|
||||
# created_by = self.normal_django_user
|
||||
#)
|
||||
# individual permission granted to other2 user, can run check mode
|
||||
#self.permission2 = Permission.objects.create(
|
||||
# inventory = self.inventory,
|
||||
# project = self.project,
|
||||
# user = self.other2_django_user,
|
||||
# permission_type = PERM_INVENTORY_CHECK,
|
||||
# created_by = self.normal_django_user
|
||||
#)
|
||||
|
||||
# Engineering has job templates to check/run the dev project onto
|
||||
# their own inventory.
|
||||
self.jt_eng_check = JobTemplate.objects.create(
|
||||
name='eng-dev-check',
|
||||
job_type='check',
|
||||
inventory= self.inv_eng,
|
||||
project=self.proj_dev,
|
||||
playbook=self.proj_dev.available_playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_eng_run = JobTemplate.objects.create(
|
||||
name='eng-dev-run',
|
||||
job_type='run',
|
||||
inventory= self.inv_eng,
|
||||
project=self.proj_dev,
|
||||
playbook=self.proj_dev.available_playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
|
||||
# Support has job templates to check/run the test project onto
|
||||
# their own inventory.
|
||||
self.jt_sup_check = JobTemplate.objects.create(
|
||||
name='sup-test-check',
|
||||
job_type='check',
|
||||
inventory= self.inv_sup,
|
||||
project=self.proj_test,
|
||||
playbook=self.proj_test.available_playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_sup_run = JobTemplate.objects.create(
|
||||
name='sup-test-run',
|
||||
job_type='run',
|
||||
inventory= self.inv_sup,
|
||||
project=self.proj_test,
|
||||
playbook=self.proj_test.available_playbooks[0],
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
|
||||
# Operations has job templates to check/run the prod project onto
|
||||
# both east and west inventories, by default using the team credential.
|
||||
self.jt_ops_east_check = JobTemplate.objects.create(
|
||||
name='ops-east-prod-check',
|
||||
job_type='check',
|
||||
inventory= self.inv_ops_east,
|
||||
project=self.proj_prod,
|
||||
playbook=self.proj_prod.available_playbooks[0],
|
||||
credential=self.cred_ops_east,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_ops_east_run = JobTemplate.objects.create(
|
||||
name='ops-east-prod-run',
|
||||
job_type='run',
|
||||
inventory= self.inv_ops_east,
|
||||
project=self.proj_prod,
|
||||
playbook=self.proj_prod.available_playbooks[0],
|
||||
credential=self.cred_ops_east,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_ops_west_check = JobTemplate.objects.create(
|
||||
name='ops-west-prod-check',
|
||||
job_type='check',
|
||||
inventory= self.inv_ops_west,
|
||||
project=self.proj_prod,
|
||||
playbook=self.proj_prod.available_playbooks[0],
|
||||
credential=self.cred_ops_west,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.jt_ops_west_run = JobTemplate.objects.create(
|
||||
name='ops-west-prod-run',
|
||||
job_type='run',
|
||||
inventory= self.inv_ops_west,
|
||||
project=self.proj_prod,
|
||||
playbook=self.proj_prod.available_playbooks[0],
|
||||
credential=self.cred_ops_west,
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(BaseJobTest, self).setUp()
|
||||
|
||||
# Users
|
||||
self.setup_users()
|
||||
self.other2_django_user = self.make_user('other2', 'other2')
|
||||
self.nobody_django_user = self.make_user('nobody', 'nobody')
|
||||
|
||||
# Organization
|
||||
self.organization = Organization.objects.create(
|
||||
name='engineering',
|
||||
created_by=self.normal_django_user,
|
||||
)
|
||||
self.organization.admins.add(self.normal_django_user)
|
||||
self.organization.users.add(self.normal_django_user)
|
||||
self.organization.users.add(self.other_django_user)
|
||||
self.organization.users.add(self.other2_django_user)
|
||||
|
||||
# Team
|
||||
self.team = self.organization.teams.create(
|
||||
name='Tigger',
|
||||
created_by=self.normal_django_user,
|
||||
)
|
||||
self.team.users.add(self.other_django_user)
|
||||
self.team.users.add(self.other2_django_user)
|
||||
|
||||
# Project
|
||||
self.project = self.make_projects(self.normal_django_user, 1,
|
||||
playbook_content=TEST_PLAYBOOK)[0]
|
||||
self.organization.projects.add(self.project)
|
||||
|
||||
# Inventory
|
||||
self.inventory = self.organization.inventories.create(
|
||||
name = 'prod',
|
||||
created_by = self.normal_django_user,
|
||||
)
|
||||
self.group_a = self.inventory.groups.create(
|
||||
name = 'group1',
|
||||
created_by = self.normal_django_user
|
||||
)
|
||||
self.host_a = self.inventory.hosts.create(
|
||||
name = '127.0.0.1',
|
||||
created_by = self.normal_django_user
|
||||
)
|
||||
self.host_b = self.inventory.hosts.create(
|
||||
name = '127.0.0.2',
|
||||
created_by = self.normal_django_user
|
||||
)
|
||||
self.group_a.hosts.add(self.host_a)
|
||||
self.group_a.hosts.add(self.host_b)
|
||||
|
||||
# Credentials
|
||||
self.user_credential = self.other_django_user.credentials.create(
|
||||
ssh_key_data = 'xxx',
|
||||
created_by = self.normal_django_user,
|
||||
)
|
||||
self.team_credential = self.team.credentials.create(
|
||||
ssh_key_data = 'xxx',
|
||||
created_by = self.normal_django_user,
|
||||
)
|
||||
|
||||
# other django user is on the project team and can deploy
|
||||
self.permission1 = Permission.objects.create(
|
||||
inventory = self.inventory,
|
||||
project = self.project,
|
||||
team = self.team,
|
||||
permission_type = PERM_INVENTORY_DEPLOY,
|
||||
created_by = self.normal_django_user
|
||||
)
|
||||
# individual permission granted to other2 user, can run check mode
|
||||
self.permission2 = Permission.objects.create(
|
||||
inventory = self.inventory,
|
||||
project = self.project,
|
||||
user = self.other2_django_user,
|
||||
permission_type = PERM_INVENTORY_CHECK,
|
||||
created_by = self.normal_django_user
|
||||
)
|
||||
|
||||
self.job_template1 = JobTemplate.objects.create(
|
||||
name = 'job-run',
|
||||
job_type = 'run',
|
||||
inventory = self.inventory,
|
||||
credential = self.user_credential,
|
||||
project = self.project,
|
||||
created_by = self.normal_django_user,
|
||||
)
|
||||
self.job_template2 = JobTemplate.objects.create(
|
||||
name = 'job-check',
|
||||
job_type = 'check',
|
||||
inventory = self.inventory,
|
||||
credential = self.team_credential,
|
||||
project = self.project,
|
||||
created_by = self.normal_django_user,
|
||||
)
|
||||
self.populate()
|
||||
|
||||
|
||||
class JobTemplateTest(BaseJobTest):
|
||||
@ -141,16 +395,58 @@ class JobTemplateTest(BaseJobTest):
|
||||
def setUp(self):
|
||||
super(JobTemplateTest, self).setUp()
|
||||
|
||||
def test_job_template_list(self):
|
||||
url = reverse('main:job_templates_list')
|
||||
|
||||
response = self.get(url, expect=401)
|
||||
with self.current_user(self.normal_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
self.assertTrue(response['count'], JobTemplate.objects.count())
|
||||
def test_get_job_template_list(self):
|
||||
url = reverse('main:job_template_list')
|
||||
|
||||
# FIXME: Test that user can only see job templates from own organization.
|
||||
# no credentials == 401
|
||||
self.options(url, expect=401)
|
||||
self.head(url, expect=401)
|
||||
self.get(url, expect=401)
|
||||
|
||||
# wrong credentials == 401
|
||||
with self.current_user('invalid', 'password'):
|
||||
self.options(url, expect=401)
|
||||
self.head(url, expect=401)
|
||||
self.get(url, expect=401)
|
||||
|
||||
# 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)
|
||||
|
||||
# FIXME: Check individual job template result.
|
||||
|
||||
# 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
|
||||
# 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],
|
||||
)
|
||||
#self.check_pagination_and_size(response, qs.count())
|
||||
#self.check_list_ids(response, qs)
|
||||
|
||||
|
||||
def test_post_job_template_list(self):
|
||||
url = reverse('main:job_template_list')
|
||||
|
||||
return # FIXME
|
||||
|
||||
# org admin can add job template
|
||||
data = dict(
|
||||
name = 'job-foo',
|
||||
@ -158,10 +454,11 @@ class JobTemplateTest(BaseJobTest):
|
||||
inventory = self.inventory.pk,
|
||||
project = self.project.pk,
|
||||
job_type = PERM_INVENTORY_DEPLOY,
|
||||
playbook = self.project.available_playbooks[0],
|
||||
)
|
||||
with self.current_user(self.normal_django_user):
|
||||
response = self.post(url, data, expect=201)
|
||||
detail_url = reverse('main:job_templates_detail',
|
||||
detail_url = reverse('main:job_template_detail',
|
||||
args=(response['id'],))
|
||||
self.assertEquals(response['url'], detail_url)
|
||||
|
||||
@ -192,31 +489,62 @@ class JobTemplateTest(BaseJobTest):
|
||||
data['job_type'] = PERM_INVENTORY_DEPLOY
|
||||
response = self.post(url, data, expect=403)
|
||||
|
||||
def test_job_template_detail(self):
|
||||
def test_get_job_template_detail(self):
|
||||
|
||||
return # FIXME
|
||||
|
||||
url = reverse('main:job_template_detail', args=(self.job_template1.pk,))
|
||||
|
||||
# verify we can also get the job template record
|
||||
got = self.get(url, expect=200, auth=self.get_other2_credentials())
|
||||
self.failUnlessEqual(got['url'], '/api/v1/job_templates/6/')
|
||||
with self.current_user(self.other2_django_user):
|
||||
self.options(url)
|
||||
self.head(url)
|
||||
response = self.get(url)
|
||||
self.assertEqual(response['url'], url)
|
||||
|
||||
# TODO: add more tests that show
|
||||
# the method used to START a JobTemplate follow the exact same permissions as those to create it ...
|
||||
# and that jobs come back nicely serialized with related resources and so on ...
|
||||
# that we can drill all the way down and can get at host failure lists, etc ...
|
||||
|
||||
def test_put_job_template_detail(self):
|
||||
pass
|
||||
|
||||
def test_get_job_template_job_list(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_post_job_template_job_list(self):
|
||||
pass
|
||||
|
||||
class JobTest(BaseJobTest):
|
||||
|
||||
def setUp(self):
|
||||
super(JobTest, self).setUp()
|
||||
|
||||
def test_mainline(self):
|
||||
def test_get_job_list(self):
|
||||
pass
|
||||
|
||||
def test_post_job_list(self):
|
||||
pass
|
||||
|
||||
def test_get_job_detail(self):
|
||||
pass
|
||||
|
||||
def test_put_job_detail(self):
|
||||
pass
|
||||
|
||||
def test_post_job_detail(self):
|
||||
pass
|
||||
|
||||
def test_get_job_host_list(self):
|
||||
pass
|
||||
|
||||
def test_get_job_job_event_list(self):
|
||||
pass
|
||||
|
||||
def _test_mainline(self):
|
||||
url = reverse('main:job_list')
|
||||
|
||||
return # FIXME
|
||||
|
||||
# job templates
|
||||
data = self.get('/api/v1/job_templates/', expect=401)
|
||||
data = self.get('/api/v1/job_templates/', expect=200, auth=self.get_normal_credentials())
|
||||
|
||||
@ -82,22 +82,30 @@ class OrganizationsTest(BaseTest):
|
||||
with self.current_user(self.super_django_user):
|
||||
self.options(url, expect=200)
|
||||
self.head(url, expect=200)
|
||||
data = self.get(url, expect=200)
|
||||
self.check_pagination_and_size(data, 10, previous=None, next=None)
|
||||
[self.assertTrue(key in data['results'][0]) for key in ['name', 'description', 'url', 'creation_date', 'id' ]]
|
||||
response = self.get(url, expect=200)
|
||||
self.check_pagination_and_size(response, 10, previous=None, next=None)
|
||||
self.assertEqual(len(response['results']),
|
||||
Organization.objects.count())
|
||||
for field in ['id', 'url', 'name', 'description', 'creation_date']:
|
||||
self.assertTrue(field in response['results'][0],
|
||||
'field %s not in result' % field)
|
||||
|
||||
# check that the related URL functionality works
|
||||
related = data['results'][0]['related']
|
||||
related = response['results'][0]['related']
|
||||
for x in [ 'audit_trail', 'projects', 'users', 'admins', 'tags' ]:
|
||||
self.assertTrue(x in related and related[x].endswith("/%s/" % x), "looking for %s in related" % x)
|
||||
|
||||
# normal credentials == 200, get only organizations that I am actually added to (there are 2)
|
||||
data = self.get(self.collection(), expect=200, auth=self.get_normal_credentials())
|
||||
self.check_pagination_and_size(data, 2, previous=None, next=None)
|
||||
# normal credentials == 200, get only organizations of which user is a member
|
||||
with self.current_user(self.normal_django_user):
|
||||
self.options(url, expect=200)
|
||||
self.head(url, expect=200)
|
||||
response = self.get(url, expect=200)
|
||||
self.check_pagination_and_size(response, 2, previous=None, next=None)
|
||||
|
||||
# no admin rights? get empty list
|
||||
data = self.get(self.collection(), expect=200, auth=self.get_other_credentials())
|
||||
self.check_pagination_and_size(data, 0, previous=None, next=None)
|
||||
with self.current_user(self.other_django_user):
|
||||
response = self.get(url, expect=200)
|
||||
self.check_pagination_and_size(response, 0, previous=None, next=None)
|
||||
|
||||
def test_get_item(self):
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ hosts_urls = patterns('lib.main.views',
|
||||
url(r'^$', 'hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'hosts_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/variable_data/$', 'hosts_variable_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/', 'hosts_job_events_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/', 'host_job_event_list'),
|
||||
)
|
||||
|
||||
groups_urls = patterns('lib.main.views',
|
||||
@ -106,24 +106,25 @@ permissions_urls = patterns('lib.main.views',
|
||||
)
|
||||
|
||||
job_templates_urls = patterns('lib.main.views',
|
||||
url(r'^$', 'job_templates_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_templates_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/start$', 'job_templates_start'),
|
||||
url(r'^$', 'job_template_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_template_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/jobs/$', 'job_template_job_list'),
|
||||
)
|
||||
|
||||
jobs_urls = patterns('lib.main.views',
|
||||
url(r'^$', 'jobs_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'jobs_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts$', 'jobs_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/successful_hosts$', 'jobs_successful_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/changed_hosts$', 'jobs_changed_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/failed_hosts$', 'jobs_failed_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/unreachable_hosts$', 'jobs_unreachable_hosts_list'),
|
||||
url(r'^$', 'job_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/hosts/$', 'job_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/successful_hosts/$', 'jobs_successful_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/changed_hosts/$', 'jobs_changed_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/failed_hosts/$', 'jobs_failed_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/unreachable_hosts/$', 'jobs_unreachable_hosts_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/job_events/$', 'job_job_event_list'),
|
||||
)
|
||||
|
||||
job_events_urls = patterns('lib.main.views',
|
||||
url(r'^$', 'job_events_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_events_detail'),
|
||||
url(r'^$', 'job_event_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'job_event_detail'),
|
||||
)
|
||||
|
||||
tags_urls = patterns('lib.main.views',
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.shortcuts import get_object_or_404
|
||||
from lib.main.models import *
|
||||
from django.contrib.auth.models import User
|
||||
from lib.main.serializers import *
|
||||
@ -36,6 +37,7 @@ import re
|
||||
import sys
|
||||
import json as python_json
|
||||
from base_views import *
|
||||
from lib.main.access import *
|
||||
|
||||
class ApiRootView(APIView):
|
||||
'''
|
||||
@ -77,8 +79,8 @@ class ApiV1RootView(APIView):
|
||||
inventory = reverse('main:inventory_list'),
|
||||
groups = reverse('main:groups_list'),
|
||||
hosts = reverse('main:hosts_list'),
|
||||
job_templates = reverse('main:job_templates_list'),
|
||||
jobs = reverse('main:jobs_list'),
|
||||
job_templates = reverse('main:job_template_list'),
|
||||
jobs = reverse('main:job_list'),
|
||||
authtoken = reverse('main:auth_token_view'),
|
||||
me = reverse('main:users_me_list'),
|
||||
)
|
||||
@ -314,7 +316,8 @@ class TeamsPermissionsList(BaseSubList):
|
||||
def _get_queryset(self):
|
||||
team = Team.objects.get(pk=self.kwargs['pk'])
|
||||
base = Permission.objects.filter(team = team)
|
||||
if Team.can_user_administrate(self.request.user, team, None):
|
||||
#if Team.can_user_administrate(self.request.user, team, None):
|
||||
if check_user_access(self.request.user, Team, 'change', team, None):
|
||||
return base
|
||||
elif team.users.filter(pk=self.request.user.pk).count() > 0:
|
||||
return base
|
||||
@ -358,7 +361,8 @@ class TeamsCredentialsList(BaseSubList):
|
||||
|
||||
def _get_queryset(self):
|
||||
team = Team.objects.get(pk=self.kwargs['pk'])
|
||||
if not Team.can_user_administrate(self.request.user, team, None):
|
||||
#if not Team.can_user_administrate(self.request.user, team, None):
|
||||
if not check_user_access(self.request.user, Team, 'change', team, None):
|
||||
if not (self.request.user.is_superuser or self.request.user in team.users.all()):
|
||||
raise PermissionDenied()
|
||||
project_credentials = Credential.objects.filter(
|
||||
@ -476,7 +480,8 @@ class UsersTeamsList(BaseSubList):
|
||||
|
||||
def _get_queryset(self):
|
||||
user = User.objects.get(pk=self.kwargs['pk'])
|
||||
if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
#if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
if not check_user_access(self.request.user, User, 'change', user, None):
|
||||
raise PermissionDenied()
|
||||
return Team.objects.filter(users__in = [ user ])
|
||||
|
||||
@ -493,7 +498,8 @@ class UsersPermissionsList(BaseSubList):
|
||||
|
||||
def _get_queryset(self):
|
||||
user = User.objects.get(pk=self.kwargs['pk'])
|
||||
if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
#if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
if not check_user_access(self.request.user, User, 'change', user, None):
|
||||
raise PermissionDenied()
|
||||
return Permission.objects.filter(user=user)
|
||||
|
||||
@ -509,7 +515,8 @@ class UsersProjectsList(BaseSubList):
|
||||
|
||||
def _get_queryset(self):
|
||||
user = User.objects.get(pk=self.kwargs['pk'])
|
||||
if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
#if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
if not check_user_access(self.request.user, User, 'change', user, None):
|
||||
raise PermissionDenied()
|
||||
teams = user.teams.all()
|
||||
return Project.objects.filter(teams__in = teams)
|
||||
@ -527,7 +534,8 @@ class UsersCredentialsList(BaseSubList):
|
||||
|
||||
def _get_queryset(self):
|
||||
user = User.objects.get(pk=self.kwargs['pk'])
|
||||
if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
#if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
if not check_user_access(self.request.user, User, 'change', user, None):
|
||||
raise PermissionDenied()
|
||||
project_credentials = Credential.objects.filter(
|
||||
team__users__in = [ user ]
|
||||
@ -546,7 +554,8 @@ class UsersOrganizationsList(BaseSubList):
|
||||
|
||||
def _get_queryset(self):
|
||||
user = User.objects.get(pk=self.kwargs['pk'])
|
||||
if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
#if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
if not check_user_access(self.request.user, User, 'change', user, None):
|
||||
raise PermissionDenied()
|
||||
return Organization.objects.filter(users__in = [ user ])
|
||||
|
||||
@ -562,7 +571,8 @@ class UsersAdminOrganizationsList(BaseSubList):
|
||||
|
||||
def _get_queryset(self):
|
||||
user = User.objects.get(pk=self.kwargs['pk'])
|
||||
if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
#if not UserHelper.can_user_administrate(self.request.user, user, None):
|
||||
if not check_user_access(self.request.user, User, 'change', user, None):
|
||||
raise PermissionDenied()
|
||||
return Organization.objects.filter(admins__in = [ user ])
|
||||
|
||||
@ -876,7 +886,7 @@ class VariableDetail(BaseDetail):
|
||||
def put(self, request, *args, **kwargs):
|
||||
raise PermissionDenied()
|
||||
|
||||
class JobTemplatesList(BaseList):
|
||||
class JobTemplateList(BaseList):
|
||||
|
||||
model = JobTemplate
|
||||
serializer_class = JobTemplateSerializer
|
||||
@ -884,33 +894,36 @@ class JobTemplatesList(BaseList):
|
||||
filter_fields = ('name',)
|
||||
|
||||
def _get_queryset(self):
|
||||
'''
|
||||
I can see job templates when I am a superuser, or I am an admin of the 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 JobTemplate.
|
||||
'''
|
||||
base = JobTemplate.objects
|
||||
if self.request.user.is_superuser:
|
||||
return base.all()
|
||||
return base.filter(
|
||||
project__organizations__admins__in = [ self.request.user ]
|
||||
).distinct() | base.filter(
|
||||
project__teams__users__in = [ self.request.user ]
|
||||
).distinct()
|
||||
return get_user_queryset(self.request.user, self.model)
|
||||
|
||||
|
||||
class JobTemplatesDetail(BaseDetail):
|
||||
class JobTemplateDetail(BaseDetail):
|
||||
|
||||
model = JobTemplate
|
||||
serializer_class = JobTemplateSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
|
||||
class JobTemplateJobList(BaseSubList):
|
||||
|
||||
model = Job
|
||||
serializer_class = JobSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
# to allow the sub-aspect listing
|
||||
parent_model = JobTemplate
|
||||
relationship = 'jobs'
|
||||
# to allow posting to this resource to create resources
|
||||
postable = True
|
||||
# FIXME: go back and add these to other SubLists
|
||||
inject_primary_key_on_post_as = 'job_template'
|
||||
severable = False
|
||||
#filter_fields = ('name',)
|
||||
|
||||
def _get_queryset(self):
|
||||
return self.model.objects.all() # FIXME
|
||||
# FIxME: Verify read permission on the job template.
|
||||
job_template = get_object_or_404(JobTemplate, pk=self.kwargs['pk'])
|
||||
return job_template.jobs
|
||||
|
||||
class JobTemplatesStart(BaseDetail):
|
||||
pass
|
||||
|
||||
class JobsList(BaseList):
|
||||
class JobList(BaseList):
|
||||
|
||||
model = Job
|
||||
serializer_class = JobSerializer
|
||||
@ -919,10 +932,16 @@ class JobsList(BaseList):
|
||||
def _get_queryset(self):
|
||||
return self.model.objects.all() # FIXME
|
||||
|
||||
class JobsDetail(BaseDetail):
|
||||
pass
|
||||
class JobDetail(BaseDetail):
|
||||
|
||||
class JobsHostsList(BaseSubList):
|
||||
model = Job
|
||||
serializer_class = JobSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
pass # FIXME
|
||||
|
||||
class JobHostsList(BaseSubList):
|
||||
pass
|
||||
|
||||
class JobsSuccessfulHostsList(BaseSubList):
|
||||
@ -937,14 +956,50 @@ class JobsFailedHostsList(BaseSubList):
|
||||
class JobsUnreachableHostsList(BaseSubList):
|
||||
pass
|
||||
|
||||
class JobEventsList(BaseList):
|
||||
pass
|
||||
class JobEventList(BaseList):
|
||||
|
||||
class JobEventsDetail(BaseDetail):
|
||||
pass
|
||||
model = JobEvent
|
||||
serializer_class = JobEventSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
|
||||
class HostsJobEventsList(BaseSubList):
|
||||
pass
|
||||
def _get_queryset(self):
|
||||
return self.model.objects.all() # FIXME
|
||||
|
||||
class JobEventDetail(BaseDetail):
|
||||
|
||||
model = JobEvent
|
||||
serializer_class = JobEventSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
|
||||
class JobJobEventList(BaseSubList):
|
||||
|
||||
model = JobEvent
|
||||
serializer_class = JobEventSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
parent_model = Job
|
||||
relationship = 'job_events'
|
||||
postable = False
|
||||
severable = False
|
||||
|
||||
def _get_queryset(self):
|
||||
job = get_object_or_404(Job, pk=self.kwargs['pk'])
|
||||
# FIXME: Verify read permission on the job.
|
||||
return job.job_events
|
||||
|
||||
class HostJobEventList(BaseSubList):
|
||||
|
||||
model = JobEvent
|
||||
serializer_class = JobEventSerializer
|
||||
permission_classes = (CustomRbac,)
|
||||
parent_model = Host
|
||||
relationship = 'job_events'
|
||||
postable = False
|
||||
severable = False
|
||||
|
||||
def _get_queryset(self):
|
||||
host = get_object_or_404(Host, pk=self.kwargs['pk'])
|
||||
# FIXME: Verify read permission on the host.
|
||||
return host.job_events
|
||||
|
||||
|
||||
# Create view functions for all of the class-based views to simplify inclusion
|
||||
|
||||
@ -39,6 +39,7 @@ MANAGERS = ADMINS
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'FILTER_BACKEND': 'lib.main.custom_filters.CustomFilterBackend',
|
||||
'DEFAULT_PAGINATION_SERIALIZER_CLASS': 'lib.main.serializers.PaginationSerializer',
|
||||
'PAGINATE_BY': 25,
|
||||
'PAGINATE_BY_PARAM': 'page_size',
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
@ -236,5 +237,10 @@ LOGGING = {
|
||||
# Comment the line below to show lots of permissions logging.
|
||||
'propagate': False,
|
||||
},
|
||||
'lib.main.access': {
|
||||
'handlers': ['null'],
|
||||
# Comment the line below to show lots of permissions logging.
|
||||
'propagate': False,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ $(function() {
|
||||
// Make linkes from relative URLs to resources.
|
||||
$('span.str').each(function() {
|
||||
var s = $(this).html();
|
||||
if (s.match(/^\"\/.+\/\"$/)) {
|
||||
if (s.match(/^\"\/.+\/\"$/) || s.match(/^\"\/.+\/\?.*\"$/)) {
|
||||
$(this).html('"<a href=' + s + '>' + s.replace(/\"/g, '') + '</a>"');
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user