mirror of
https://github.com/ansible/awx.git
synced 2026-03-28 22:35:08 -02:30
Renamed some API files/classes to mimic REST framework names, moved queryset filtering for permissions alongside other permissions/access checks, cleaned up base views to handle get_queryset based on class attributes, cleaned up post to sublist to create/attach/unattach.
This commit is contained in:
@@ -1,15 +1,18 @@
|
|||||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# Python
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
# Django
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from awx import MODE
|
# AWX
|
||||||
from awx.main.models import *
|
from awx.main.models import *
|
||||||
from awx.main.licenses import LicenseReader
|
from awx.main.licenses import LicenseReader
|
||||||
|
|
||||||
@@ -63,6 +66,12 @@ def check_user_access(user, model_class, action, *args, **kwargs):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
class BaseAccess(object):
|
class BaseAccess(object):
|
||||||
|
'''
|
||||||
|
Base class for checking user access to a given model. Subclasses should
|
||||||
|
define the model attribute, override the get_queryset method to return only
|
||||||
|
the instances the user should be able to view, and override/define can_*
|
||||||
|
methods to verify a user's permission to perform a particular action.
|
||||||
|
'''
|
||||||
|
|
||||||
model = None
|
model = None
|
||||||
|
|
||||||
@@ -102,8 +111,7 @@ class BaseAccess(object):
|
|||||||
return self.can_change(obj, None)
|
return self.can_change(obj, None)
|
||||||
else:
|
else:
|
||||||
return bool(self.can_change(obj, None) and
|
return bool(self.can_change(obj, None) and
|
||||||
check_user_access(self.user, type(sub_obj), 'read',
|
self.user.can_access(type(sub_obj), 'read', sub_obj))
|
||||||
sub_obj))
|
|
||||||
|
|
||||||
def can_unattach(self, obj, sub_obj, relationship):
|
def can_unattach(self, obj, sub_obj, relationship):
|
||||||
return self.can_change(obj, None)
|
return self.can_change(obj, None)
|
||||||
@@ -120,7 +128,7 @@ class UserAccess(BaseAccess):
|
|||||||
return self.model.objects.filter(is_active=True).filter(
|
return self.model.objects.filter(is_active=True).filter(
|
||||||
Q(pk=self.user.pk) |
|
Q(pk=self.user.pk) |
|
||||||
Q(organizations__in=self.user.admin_of_organizations.all()) |
|
Q(organizations__in=self.user.admin_of_organizations.all()) |
|
||||||
Q(teams__in=self.request.user.teams.all())
|
Q(teams__in=self.user.teams.all())
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
@@ -158,6 +166,14 @@ class OrganizationAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Organization
|
model = Organization
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
# I can see organizations when I am a superuser, or I am an admin or
|
||||||
|
# user in that organization.
|
||||||
|
qs = self.model.objects.distinct()
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return qs
|
||||||
|
return qs.filter(Q(admins__in=[self.user]) | Q(users__in=[self.user]))
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
return bool(self.can_change(obj, None) or
|
return bool(self.can_change(obj, None) or
|
||||||
self.user in obj.users.all())
|
self.user in obj.users.all())
|
||||||
@@ -174,6 +190,23 @@ class InventoryAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Inventory
|
model = Inventory
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
# I can see inventory when I'm a superuser, an org admin of the
|
||||||
|
# inventory, or I have permissions on it.
|
||||||
|
base = Inventory.objects.distinct()
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return base.all()
|
||||||
|
admin_of = base.filter(organization__admins__in = [ self.user ]).distinct()
|
||||||
|
has_user_perms = base.filter(
|
||||||
|
permissions__user__in = [ self.user ],
|
||||||
|
permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||||
|
).distinct()
|
||||||
|
has_team_perms = base.filter(
|
||||||
|
permissions__team__in = self.user.teams.all(),
|
||||||
|
permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||||
|
).distinct()
|
||||||
|
return admin_of | has_user_perms | has_team_perms
|
||||||
|
|
||||||
def _has_permission_types(self, obj, allowed):
|
def _has_permission_types(self, obj, allowed):
|
||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
@@ -241,7 +274,7 @@ class InventoryAccess(BaseAccess):
|
|||||||
''' whether you can add sub_obj to obj using the relationship type in a subobject view '''
|
''' 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 not sub_obj.can_user_read(user, sub_obj):
|
||||||
if sub_obj and not skip_sub_obj_read_check:
|
if sub_obj and not skip_sub_obj_read_check:
|
||||||
if not check_user_access(self.user, type(sub_obj), 'read', sub_obj):
|
if not self.user.can_access(type(sub_obj), 'read', sub_obj):
|
||||||
return False
|
return False
|
||||||
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
return self._has_permission_types(obj, PERMISSION_TYPES_ALLOWING_INVENTORY_WRITE)
|
||||||
|
|
||||||
@@ -252,8 +285,29 @@ class HostAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Host
|
model = Host
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
'''
|
||||||
|
I can see hosts when:
|
||||||
|
I'm a superuser,
|
||||||
|
or an organization admin of an inventory they are in
|
||||||
|
or when I have allowing read permissions via a user or team on an inventory they are in
|
||||||
|
'''
|
||||||
|
base = self.model.objects
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return base.all()
|
||||||
|
admin_of = base.filter(inventory__organization__admins__in = [ self.user ]).distinct()
|
||||||
|
has_user_perms = base.filter(
|
||||||
|
inventory__permissions__user__in = [ self.user ],
|
||||||
|
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||||
|
).distinct()
|
||||||
|
has_team_perms = base.filter(
|
||||||
|
inventory__permissions__team__in = self.user.teams.all(),
|
||||||
|
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||||
|
).distinct()
|
||||||
|
return admin_of | has_user_perms | has_team_perms
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
return check_user_access(self.user, Inventory, 'read', obj.inventory)
|
return self.user.can_access(Inventory, 'read', obj.inventory)
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
|
|
||||||
@@ -264,7 +318,7 @@ class HostAccess(BaseAccess):
|
|||||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||||
|
|
||||||
# Checks for admin or change permission on inventory.
|
# Checks for admin or change permission on inventory.
|
||||||
permissions_ok = check_user_access(self.user, Inventory, 'change', inventory, None)
|
permissions_ok = self.user.can_access(Inventory, 'change', inventory, None)
|
||||||
if not permissions_ok:
|
if not permissions_ok:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -286,31 +340,82 @@ class HostAccess(BaseAccess):
|
|||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
# Checks for admin or change permission on inventory, controls whether
|
# Checks for admin or change permission on inventory, controls whether
|
||||||
# the user can edit variable data.
|
# the user can edit variable data.
|
||||||
return check_user_access(self.user, Inventory, 'change', obj.inventory, None)
|
return self.user.can_access(Inventory, 'change', obj.inventory, None)
|
||||||
|
|
||||||
class GroupAccess(BaseAccess):
|
class GroupAccess(BaseAccess):
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
'''
|
||||||
|
I can see groups when:
|
||||||
|
I'm a superuser,
|
||||||
|
or an organization admin of an inventory they are in
|
||||||
|
or when I have allowing read permissions via a user or team on an inventory they are in
|
||||||
|
'''
|
||||||
|
base = Group.objects
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return base.distinct()
|
||||||
|
admin_of = base.filter(inventory__organization__admins__in = [ self.user ]).distinct()
|
||||||
|
has_user_perms = base.filter(
|
||||||
|
inventory__permissions__user__in = [ self.user ],
|
||||||
|
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||||
|
).distinct()
|
||||||
|
has_team_perms = base.filter(
|
||||||
|
inventory__permissions__team__in = self.user.teams.all(),
|
||||||
|
inventory__permissions__permission_type__in = PERMISSION_TYPES_ALLOWING_INVENTORY_READ,
|
||||||
|
).distinct()
|
||||||
|
return admin_of | has_user_perms | has_team_perms
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
return check_user_access(self.user, Inventory, 'read', obj.inventory)
|
return self.user.can_access(Inventory, 'read', obj.inventory)
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
if not 'inventory' in data:
|
if not 'inventory' in data:
|
||||||
return False
|
return False
|
||||||
inventory = Inventory.objects.get(pk=data['inventory'])
|
inventory = Inventory.objects.get(pk=data['inventory'])
|
||||||
# Checks for admin or change permission on inventory.
|
# Checks for admin or change permission on inventory.
|
||||||
return check_user_access(self.user, Inventory, 'change', inventory, None)
|
return self.user.can_access(Inventory, 'change', inventory, None)
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
# Checks for admin or change permission on inventory, controls whether
|
# Checks for admin or change permission on inventory, controls whether
|
||||||
# the user can attach subgroups or edit variable data.
|
# the user can attach subgroups or edit variable data.
|
||||||
return check_user_access(self.user, Inventory, 'change', obj.inventory, None)
|
return self.user.can_access(Inventory, 'change', obj.inventory, None)
|
||||||
|
|
||||||
|
def can_attach(self, obj, sub_obj, relationship, data,
|
||||||
|
skip_sub_obj_read_check=False):
|
||||||
|
if not self.can_change(obj, None):
|
||||||
|
return False
|
||||||
|
if sub_obj and not skip_sub_obj_read_check:
|
||||||
|
if not self.user.can_access(type(sub_obj), 'read', sub_obj):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Prevent group from being assigned as its own (grand)child.
|
||||||
|
if type(obj) == type(sub_obj):
|
||||||
|
parent_pks = set(obj.all_parents.values_list('pk', flat=True))
|
||||||
|
parent_pks.add(obj.pk)
|
||||||
|
child_pks = set(sub_obj.all_children.values_list('pk', flat=True))
|
||||||
|
child_pks.add(sub_obj.pk)
|
||||||
|
if parent_pks & child_pks:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
class CredentialAccess(BaseAccess):
|
class CredentialAccess(BaseAccess):
|
||||||
|
|
||||||
model = Credential
|
model = Credential
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
# I can see credentials when:
|
||||||
|
# - It's a user credential and it's my credential.
|
||||||
|
# - It's a user credential and I'm an admin of an organization
|
||||||
|
# -
|
||||||
|
# FIXME
|
||||||
|
qs = self.model.objects.distinct()
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return qs
|
||||||
|
return qs.filter(Q(user=self.user))
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
return self.can_change(obj, None)
|
return self.can_change(obj, None)
|
||||||
|
|
||||||
@@ -319,10 +424,10 @@ class CredentialAccess(BaseAccess):
|
|||||||
return True
|
return True
|
||||||
if 'user' in data:
|
if 'user' in data:
|
||||||
user_obj = User.objects.get(pk=data['user'])
|
user_obj = User.objects.get(pk=data['user'])
|
||||||
return check_user_access(self.user, User, 'change', user_obj, None)
|
return self.user.can_access(User, 'change', user_obj, None)
|
||||||
if 'team' in data:
|
if 'team' in data:
|
||||||
team_obj = Team.objects.get(pk=data['team'])
|
team_obj = Team.objects.get(pk=data['team'])
|
||||||
return check_user_access(self.user, Team, 'change', team_obj, None)
|
return self.user.can_access(Team, 'change', team_obj, None)
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
@@ -347,6 +452,9 @@ class TeamAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Team
|
model = Team
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.distinct() # FIXME
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
@@ -378,6 +486,22 @@ class ProjectAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Project
|
model = Project
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
# I can see projects when:
|
||||||
|
# - I am a superuser
|
||||||
|
# - I am an admin or user in that organization...
|
||||||
|
# FIXME
|
||||||
|
base = Project.objects.distinct()
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return base.all()
|
||||||
|
my_teams = Team.objects.filter(users__in = [ self.user])
|
||||||
|
my_orgs = Organization.objects.filter(admins__in = [ self.user ])
|
||||||
|
return base.filter(
|
||||||
|
teams__in = my_teams
|
||||||
|
).distinct() | base.filter(
|
||||||
|
organizations__in = my_orgs
|
||||||
|
).distinct()
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if self.can_change(obj, None):
|
if self.can_change(obj, None):
|
||||||
return True
|
return True
|
||||||
@@ -403,6 +527,9 @@ class PermissionAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Permission
|
model = Permission
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.distinct() # FIXME
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
# a permission can be seen by the assigned user or team
|
# a permission can be seen by the assigned user or team
|
||||||
# or anyone who can administrate that permission
|
# or anyone who can administrate that permission
|
||||||
@@ -524,6 +651,9 @@ class JobAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Job
|
model = Job
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.distinct() # FIXME
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
return self.user.is_superuser and obj.status == 'new'
|
return self.user.is_superuser and obj.status == 'new'
|
||||||
|
|
||||||
@@ -537,10 +667,16 @@ class JobHostSummaryAccess(BaseAccess):
|
|||||||
|
|
||||||
model = JobHostSummary
|
model = JobHostSummary
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.distinct() # FIXME
|
||||||
|
|
||||||
class JobEventAccess(BaseAccess):
|
class JobEventAccess(BaseAccess):
|
||||||
|
|
||||||
model = JobEvent
|
model = JobEvent
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.model.objects.distinct() # FIXME
|
||||||
|
|
||||||
register_access(User, UserAccess)
|
register_access(User, UserAccess)
|
||||||
register_access(Organization, OrganizationAccess)
|
register_access(Organization, OrganizationAccess)
|
||||||
register_access(Inventory, InventoryAccess)
|
register_access(Inventory, InventoryAccess)
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework import authentication
|
from rest_framework import authentication
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
|
|||||||
@@ -6,66 +6,90 @@ import json
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.http import HttpResponse, Http404
|
from django.http import HttpResponse, Http404
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework import mixins
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework import permissions
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import *
|
from awx.main.models import *
|
||||||
from awx.main.serializers import *
|
|
||||||
from awx.main.rbac import *
|
|
||||||
from awx.main.access import *
|
|
||||||
|
|
||||||
# FIXME: machinery for auto-adding audit trail logs to all CREATE/EDITS
|
# FIXME: machinery for auto-adding audit trail logs to all CREATE/EDITS
|
||||||
|
|
||||||
class BaseList(generics.ListCreateAPIView):
|
class ListAPIView(generics.ListAPIView):
|
||||||
|
# Base class for a read-only list view.
|
||||||
permission_classes = (CustomRbac,)
|
|
||||||
|
|
||||||
# Subclasses should define:
|
# Subclasses should define:
|
||||||
# model = ModelClass
|
# model = ModelClass
|
||||||
# serializer_class = SerializerClass
|
# serializer_class = SerializerClass
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def get_queryset(self):
|
||||||
# FIXME: Should inherit from generics.ListAPIView if not postable.
|
return self.request.user.get_queryset(self.model)
|
||||||
postable = getattr(self.__class__, 'postable', True)
|
|
||||||
if not postable:
|
|
||||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
|
||||||
return super(BaseList, self).post(request, *args, **kwargs)
|
|
||||||
|
|
||||||
# NOTE: Moved filtering from get_queryset into custom filter backend.
|
class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
|
||||||
|
# Base class for a list view that allows creating new objects.
|
||||||
|
pass
|
||||||
|
|
||||||
class BaseSubList(BaseList):
|
class SubListAPIView(ListAPIView):
|
||||||
|
# Base class for a read-only sublist view.
|
||||||
''' used for subcollections with an overriden post '''
|
|
||||||
|
|
||||||
# Subclasses should define at least:
|
# Subclasses should define at least:
|
||||||
# model = ModelClass
|
# model = ModelClass
|
||||||
# serializer_class = SerializerClass
|
# serializer_class = SerializerClass
|
||||||
# parent_model = ModelClass
|
# parent_model = ModelClass
|
||||||
# relationship = 'rel_name_from_parent_to_model'
|
# relationship = 'rel_name_from_parent_to_model'
|
||||||
# And optionally:
|
# And optionally (user must have given access permission on parent object
|
||||||
# postable = True/False
|
# to view sublist):
|
||||||
|
# parent_access = 'admin'
|
||||||
|
|
||||||
|
def get_parent_object(self):
|
||||||
|
parent_filter = {
|
||||||
|
self.lookup_field: self.kwargs.get(self.lookup_field, None),
|
||||||
|
}
|
||||||
|
return get_object_or_404(self.parent_model, **parent_filter)
|
||||||
|
|
||||||
|
def check_parent_access(self, parent=None):
|
||||||
|
parent = parent or self.get_parent_object()
|
||||||
|
parent_access = getattr(self, 'parent_access', 'admin')
|
||||||
|
if parent_access in ('read', 'delete'):
|
||||||
|
args = (self.parent_model, parent_access, parent)
|
||||||
|
else:
|
||||||
|
args = (self.parent_model, parent_access, parent, None)
|
||||||
|
if not self.request.user.can_access(*args):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
parent = self.get_parent_object()
|
||||||
|
self.check_parent_access(parent)
|
||||||
|
qs = self.request.user.get_queryset(self.model).distinct()
|
||||||
|
sublist_qs = getattr(parent, self.relationship).distinct()
|
||||||
|
return qs & sublist_qs
|
||||||
|
|
||||||
|
class SubListCreateAPIView(SubListAPIView, generics.ListCreateAPIView):
|
||||||
|
# Base class for a sublist view that allows for creating subobjects and
|
||||||
|
# attaching/detaching them from the parent.
|
||||||
|
|
||||||
|
# In addition to SubListAPIView properties, subclasses may define:
|
||||||
# inject_primary_key_on_post_as = 'field_on_model_referring_to_parent'
|
# inject_primary_key_on_post_as = 'field_on_model_referring_to_parent'
|
||||||
# severable = True/False
|
# severable = True/False
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
|
# If the object ID was not specified, it probably doesn't exist in the
|
||||||
|
# DB yet. We want to see if we can create it. The URL may choose to
|
||||||
|
# inject it's primary key into the object because we are posting to a
|
||||||
|
# subcollection. Use all the normal access control mechanisms.
|
||||||
|
|
||||||
# decide whether to return 201 with data (new object) or 204 (just associated)
|
inject_primary_key = getattr(self, 'inject_primary_key_on_post_as', None)
|
||||||
created = False
|
|
||||||
ser = None
|
|
||||||
|
|
||||||
postable = getattr(self.__class__, 'postable', False)
|
if inject_primary_key is None:
|
||||||
if not postable:
|
# view didn't specify a way to get the pk from the URL, so not even trying
|
||||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
return Response(status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
data=dict(msg='object cannot be created'))
|
||||||
|
|
||||||
# Make a copy of the data provided (since it's readonly) in order to
|
# Make a copy of the data provided (since it's readonly) in order to
|
||||||
# inject additional data.
|
# inject additional data.
|
||||||
@@ -73,157 +97,100 @@ class BaseSubList(BaseList):
|
|||||||
data = request.DATA.dict()
|
data = request.DATA.dict()
|
||||||
else:
|
else:
|
||||||
data = request.DATA
|
data = request.DATA
|
||||||
parent_id = kwargs['pk']
|
|
||||||
sub_id = data.get('id', None)
|
|
||||||
main = self.__class__.parent_model.objects.get(pk=parent_id)
|
|
||||||
severable = getattr(self.__class__, 'severable', True)
|
|
||||||
|
|
||||||
subs = None
|
# add the key to the post data using the pk from the URL
|
||||||
|
data[inject_primary_key] = kwargs['pk']
|
||||||
|
|
||||||
if sub_id:
|
# attempt to deserialize the object
|
||||||
subs = self.__class__.model.objects.filter(pk=sub_id)
|
serializer = self.serializer_class(data=data)
|
||||||
else:
|
if not serializer.is_valid():
|
||||||
if 'disassociate' in data:
|
return Response(serializer.errors,
|
||||||
raise PermissionDenied() # ID is required to disassociate
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
else:
|
|
||||||
|
|
||||||
# this is a little tricky and a little manual
|
# Verify we have permission to add the object as given.
|
||||||
# the object ID was not specified, so it probably doesn't exist in the DB yet.
|
if not request.user.can_access(self.model, 'add', serializer.init_data):
|
||||||
# we want to see if we can create it. The URL may choose to inject it's primary key into the object
|
raise PermissionDenied()
|
||||||
# because we are posting to a subcollection. Use all the normal access control mechanisms.
|
|
||||||
|
|
||||||
inject_primary_key = getattr(self.__class__, 'inject_primary_key_on_post_as', None)
|
# save the object through the serializer, reload and returned the saved object deserialized
|
||||||
|
obj = serializer.save()
|
||||||
|
serializer = self.serializer_class(obj)
|
||||||
|
|
||||||
if inject_primary_key is not None:
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
# add the key to the post data using the pk from the URL
|
def attach(self, request, *args, **kwargs):
|
||||||
data[inject_primary_key] = kwargs['pk']
|
created = False
|
||||||
|
parent = self.get_parent_object()
|
||||||
|
relationship = getattr(parent, self.relationship)
|
||||||
|
sub_id = request.DATA.get('id', None)
|
||||||
|
data = request.DATA
|
||||||
|
|
||||||
# attempt to deserialize the object
|
# Create the sub object if an ID is not provided.
|
||||||
ser = self.__class__.serializer_class(data=data)
|
if not sub_id:
|
||||||
if not ser.is_valid():
|
response = self.create(request, *args, **kwargs)
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST, data=ser.errors)
|
if response.status_code != status.HTTP_201_CREATED:
|
||||||
|
return response
|
||||||
|
sub_id = response.data['id']
|
||||||
|
data = response.data
|
||||||
|
created = True
|
||||||
|
|
||||||
if not check_user_access(request.user, self.model, 'add', ser.init_data):
|
# Retrive the sub object (whether created or by ID).
|
||||||
raise PermissionDenied()
|
try:
|
||||||
|
sub = self.model.objects.get(pk=sub_id)
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
data = dict(msg='Object with id %s cannot be found' % sub_id)
|
||||||
|
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# Verify we have permission to attach.
|
||||||
|
if not request.user.can_access(self.parent_model, 'attach', parent, sub, self.relationship, data, skip_sub_obj_read_check=created):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
# save the object through the serializer, reload and returned the saved object deserialized
|
# Attach the object to the collection.
|
||||||
obj = ser.save()
|
if sub not in relationship.all():
|
||||||
ser = self.__class__.serializer_class(obj)
|
relationship.add(sub)
|
||||||
|
|
||||||
# now make sure we could have already attached the two together. If we could not have, raise an exception
|
|
||||||
# such that the transaction does not commit.
|
|
||||||
|
|
||||||
if main == obj:
|
|
||||||
# no attaching to yourself
|
|
||||||
raise PermissionDenied()
|
|
||||||
|
|
||||||
|
|
||||||
if self.__class__.parent_model != User:
|
|
||||||
|
|
||||||
# FIXME: refactor into smaller functions
|
|
||||||
|
|
||||||
if obj.__class__ in [ User]:
|
|
||||||
if self.__class__.parent_model == Organization:
|
|
||||||
# user can't inject an organization because it's not part of the user
|
|
||||||
# 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=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 NotImplementedError()
|
|
||||||
else:
|
|
||||||
if not check_user_access(request.user, type(obj), 'read', obj):
|
|
||||||
raise PermissionDenied()
|
|
||||||
# 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=data[inject_primary_key])
|
|
||||||
import awx.main.views
|
|
||||||
if self.__class__ == awx.main.views.OrganizationUsersList:
|
|
||||||
organization.users.add(obj)
|
|
||||||
elif self.__class__ == awx.main.views.OrganizationAdminsList:
|
|
||||||
organization.admins.add(obj)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if not check_user_access(request.user, type(obj), 'read', obj):
|
|
||||||
raise PermissionDenied()
|
|
||||||
# FIXME: should generalize this
|
|
||||||
if not check_user_access(request.user, self.parent_model, 'attach', main, obj, self.relationship, data):
|
|
||||||
raise PermissionDenied()
|
|
||||||
|
|
||||||
# why are we returning here?
|
|
||||||
# return Response(status=status.HTTP_201_CREATED, data=ser.data)
|
|
||||||
created = True
|
|
||||||
subs = [ obj ]
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
# view didn't specify a way to get the pk from the URL, so not even trying
|
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST, data=json.dumps(dict(msg='object cannot be created')))
|
|
||||||
|
|
||||||
# we didn't have to create the object, so this is just associating the two objects together now...
|
|
||||||
# (or disassociating them)
|
|
||||||
|
|
||||||
if len(subs) != 1:
|
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
sub = subs[0]
|
|
||||||
relationship = getattr(main, self.__class__.relationship)
|
|
||||||
|
|
||||||
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, 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, data):
|
|
||||||
if not check_user_access(request.user, self.parent_model, 'attach', main, sub, self.relationship, data):
|
|
||||||
raise PermissionDenied()
|
|
||||||
|
|
||||||
if sub not in relationship.all():
|
|
||||||
relationship.add(sub)
|
|
||||||
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 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 check_user_access(request.user, self.parent_model, 'unattach', main, sub, self.relationship):
|
|
||||||
raise PermissionDenied()
|
|
||||||
|
|
||||||
|
|
||||||
if severable:
|
|
||||||
relationship.remove(sub)
|
|
||||||
else:
|
|
||||||
# resource is just a ForeignKey, can't remove it from the set, just set it inactive
|
|
||||||
sub.mark_inactive()
|
|
||||||
|
|
||||||
|
|
||||||
# verify we didn't add anything to it's own children
|
|
||||||
if type(main) == Group:
|
|
||||||
all_children = main.get_all_children().all()
|
|
||||||
if main in all_children:
|
|
||||||
# no attaching to child objects (in the case of groups)
|
|
||||||
raise PermissionDenied()
|
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
return Response(status=status.HTTP_201_CREATED, data=ser.data)
|
return Response(data, status=status.HTTP_201_CREATED)
|
||||||
else:
|
else:
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def unattach(self, request, *args, **kwargs):
|
||||||
|
sub_id = request.DATA.get('id', None)
|
||||||
|
if not sub_id:
|
||||||
|
data = dict(msg='"id" is required to disassociate')
|
||||||
|
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
|
parent = self.get_parent_object()
|
||||||
|
severable = getattr(self, 'severable', True)
|
||||||
|
relationship = getattr(parent, self.relationship)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sub = self.model.objects.get(pk=sub_id)
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
data = dict(msg='Object with id %s cannot be found' % sub_id)
|
||||||
|
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if not request.user.can_access(self.parent_model, 'unattach', parent, sub, self.relationship):
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
if severable:
|
||||||
|
relationship.remove(sub)
|
||||||
|
else:
|
||||||
|
# resource is just a ForeignKey, can't remove it from the set, just set it inactive
|
||||||
|
sub.mark_inactive()
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if 'disassociate' in request.DATA:
|
||||||
|
return self.unattach(request, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
return self.attach(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveAPIView(generics.RetrieveAPIView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RetrieveUpdateDestroyAPIView(RetrieveAPIView, generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
|
||||||
def pre_save(self, obj):
|
def pre_save(self, obj):
|
||||||
if type(obj) not in [ User ]:
|
if type(obj) not in [ User ]:
|
||||||
@@ -237,23 +204,18 @@ class BaseDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
raise Http404()
|
raise Http404()
|
||||||
if getattr(obj, 'is_active', True) == False:
|
if getattr(obj, 'is_active', True) == False:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
#if not request.user.is_superuser and not self.delete_permissions_check(request, obj):
|
if not request.user.can_access(self.model, 'delete', obj):
|
||||||
if not check_user_access(request.user, self.model, 'delete', obj):
|
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
if isinstance(obj, PrimordialModel):
|
if hasattr(obj, 'mark_inactive'):
|
||||||
obj.mark_inactive()
|
obj.mark_inactive()
|
||||||
elif type(obj) == User:
|
|
||||||
obj.username = "_deleted_%s_%s" % (now().isoformat(), obj.username)
|
|
||||||
obj.is_active = False
|
|
||||||
obj.save()
|
|
||||||
else:
|
else:
|
||||||
raise Exception("InternalError: destroy() not implemented yet for %s" % obj)
|
raise Exception("InternalError: destroy() not implemented yet for %s" % obj)
|
||||||
return HttpResponse(status=204)
|
return HttpResponse(status=204)
|
||||||
|
|
||||||
def put(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
self.put_filter(request, *args, **kwargs)
|
self.update_filter(request, *args, **kwargs)
|
||||||
return super(BaseDetail, self).put(request, *args, **kwargs)
|
return super(RetrieveUpdateDestroyAPIView, self).update(request, *args, **kwargs)
|
||||||
|
|
||||||
def put_filter(self, request, *args, **kwargs):
|
def update_filter(self, request, *args, **kwargs):
|
||||||
''' scrub any fields the user cannot/should not put, based on user context. This runs after read-only serialization filtering '''
|
''' scrub any fields the user cannot/should not put/patch, based on user context. This runs after read-only serialization filtering '''
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Compability library for support of both Django 1.4.x and Django 1.5.x.
|
Compability library for support of both Django 1.4.x and Django 1.5.x.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# Django REST Framework
|
||||||
from rest_framework.filters import BaseFilterBackend
|
from rest_framework.filters import BaseFilterBackend
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
|
|
||||||
class CustomFilterBackend(object):
|
class DefaultFilterBackend(BaseFilterBackend):
|
||||||
|
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
|
|
||||||
@@ -33,6 +33,10 @@ from djcelery.models import TaskMeta
|
|||||||
# Django-REST-Framework
|
# Django-REST-Framework
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
__all__ = ['Organization', 'Team', 'Project', 'Credential', 'Inventory',
|
||||||
|
'Host', 'Group', 'Permission', 'JobTemplate', 'Job',
|
||||||
|
'JobHostSummary', 'JobEvent']
|
||||||
|
|
||||||
# TODO: reporting model TBD
|
# TODO: reporting model TBD
|
||||||
|
|
||||||
PERM_INVENTORY_ADMIN = 'admin'
|
PERM_INVENTORY_ADMIN = 'admin'
|
||||||
@@ -1213,6 +1217,21 @@ class JobEvent(models.Model):
|
|||||||
|
|
||||||
# TODO: reporting (MPD)
|
# TODO: reporting (MPD)
|
||||||
|
|
||||||
|
# Add mark_inactive method to User model.
|
||||||
|
def user_mark_inactive(user, save=True):
|
||||||
|
'''Use instead of delete to rename and mark users inactive.'''
|
||||||
|
if user.is_active:
|
||||||
|
user.username = "_deleted_%s_%s" % (now().isoformat(), user.username)
|
||||||
|
user.is_active = False
|
||||||
|
if save:
|
||||||
|
user.save()
|
||||||
|
User.add_to_class('mark_inactive', user_mark_inactive)
|
||||||
|
|
||||||
|
# Add custom methods to User model for permissions checks.
|
||||||
|
from awx.main.access import *
|
||||||
|
User.add_to_class('get_queryset', get_user_queryset)
|
||||||
|
User.add_to_class('can_access', check_user_access)
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def create_auth_token_for_user(sender, **kwargs):
|
def create_auth_token_for_user(sender, **kwargs):
|
||||||
instance = kwargs.get('instance', None)
|
instance = kwargs.get('instance', None)
|
||||||
|
|||||||
@@ -1,26 +1,38 @@
|
|||||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# Python
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
# Django
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
|
||||||
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
# AWX
|
||||||
from awx.main.access import *
|
from awx.main.access import *
|
||||||
from awx.main.models import *
|
from awx.main.models import *
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.rbac')
|
logger = logging.getLogger('awx.main.permissions')
|
||||||
|
|
||||||
# FIXME: this will probably need to be subclassed by object type
|
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
|
||||||
|
'JobTaskPermission']
|
||||||
|
|
||||||
class CustomRbac(permissions.BasePermission):
|
class ModelAccessPermission(permissions.BasePermission):
|
||||||
|
'''
|
||||||
|
Default permissions class to check user access based on the model and
|
||||||
|
request method, optionally verifying the request data.
|
||||||
|
'''
|
||||||
|
|
||||||
def _check_options_permissions(self, request, view, obj=None):
|
def check_options_permissions(self, request, view, obj=None):
|
||||||
return self._check_get_permissions(request, view, obj)
|
return self.check_get_permissions(request, view, obj)
|
||||||
|
|
||||||
def _check_head_permissions(self, request, view, obj=None):
|
def check_head_permissions(self, request, view, obj=None):
|
||||||
return self._check_get_permissions(request, view, obj)
|
return self.check_get_permissions(request, view, obj)
|
||||||
|
|
||||||
def _check_get_permissions(self, request, view, obj=None):
|
def check_get_permissions(self, request, view, obj=None):
|
||||||
if hasattr(view, 'parent_model'):
|
if hasattr(view, 'parent_model'):
|
||||||
parent_obj = view.parent_model.objects.get(pk=view.kwargs['pk'])
|
parent_obj = view.parent_model.objects.get(pk=view.kwargs['pk'])
|
||||||
if not check_user_access(request.user, view.parent_model, 'read',
|
if not check_user_access(request.user, view.parent_model, 'read',
|
||||||
@@ -30,7 +42,7 @@ class CustomRbac(permissions.BasePermission):
|
|||||||
return True
|
return True
|
||||||
return check_user_access(request.user, view.model, 'read', obj)
|
return check_user_access(request.user, view.model, 'read', obj)
|
||||||
|
|
||||||
def _check_post_permissions(self, request, view, obj=None):
|
def check_post_permissions(self, request, view, obj=None):
|
||||||
if hasattr(view, 'parent_model'):
|
if hasattr(view, 'parent_model'):
|
||||||
parent_obj = view.parent_model.objects.get(pk=view.kwargs['pk'])
|
parent_obj = view.parent_model.objects.get(pk=view.kwargs['pk'])
|
||||||
return True
|
return True
|
||||||
@@ -39,7 +51,7 @@ class CustomRbac(permissions.BasePermission):
|
|||||||
return True
|
return True
|
||||||
return check_user_access(request.user, view.model, 'add', request.DATA)
|
return check_user_access(request.user, view.model, 'add', request.DATA)
|
||||||
|
|
||||||
def _check_put_permissions(self, request, view, obj=None):
|
def check_put_permissions(self, request, view, obj=None):
|
||||||
if not obj:
|
if not obj:
|
||||||
return True # FIXME: For some reason this needs to return True
|
return True # FIXME: For some reason this needs to return True
|
||||||
# because it is first called with obj=None?
|
# because it is first called with obj=None?
|
||||||
@@ -50,13 +62,20 @@ class CustomRbac(permissions.BasePermission):
|
|||||||
return check_user_access(request.user, view.model, 'change', obj,
|
return check_user_access(request.user, view.model, 'change', obj,
|
||||||
request.DATA)
|
request.DATA)
|
||||||
|
|
||||||
def _check_delete_permissions(self, request, view, obj=None):
|
def check_patch_permissions(self, request, view, obj=None):
|
||||||
|
return self.check_put_permissions(request, view, obj)
|
||||||
|
|
||||||
|
def check_delete_permissions(self, request, view, obj=None):
|
||||||
if not obj:
|
if not obj:
|
||||||
return True # FIXME: For some reason this needs to return True
|
return True # FIXME: For some reason this needs to return True
|
||||||
# because it is first called with obj=None?
|
# because it is first called with obj=None?
|
||||||
return check_user_access(request.user, view.model, 'delete', obj)
|
return check_user_access(request.user, view.model, 'delete', obj)
|
||||||
|
|
||||||
def _check_permissions(self, request, view, obj=None):
|
def check_permissions(self, request, view, obj=None):
|
||||||
|
'''
|
||||||
|
Perform basic permissions checking before delegating to the appropriate
|
||||||
|
method based on the request method.
|
||||||
|
'''
|
||||||
|
|
||||||
# Check that obj (if given) is active, otherwise raise a 404.
|
# Check that obj (if given) is active, otherwise raise a 404.
|
||||||
active = getattr(obj, 'active', getattr(obj, 'is_active', True))
|
active = getattr(obj, 'active', getattr(obj, 'is_active', True))
|
||||||
@@ -79,39 +98,20 @@ class CustomRbac(permissions.BasePermission):
|
|||||||
|
|
||||||
# Check permissions for the given view and object, based on the request
|
# Check permissions for the given view and object, based on the request
|
||||||
# method used.
|
# method used.
|
||||||
check_method = getattr(self, '_check_%s_permissions' % \
|
check_method = getattr(self, 'check_%s_permissions' % \
|
||||||
request.method.lower(), None)
|
request.method.lower(), None)
|
||||||
result = check_method and check_method(request, view, obj)
|
result = check_method and check_method(request, view, obj)
|
||||||
if not result:
|
if not result:
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# If no obj is given, check list permissions.
|
|
||||||
|
|
||||||
if obj is None:
|
|
||||||
if getattr(view, 'list_permissions_check', None):
|
|
||||||
if not view.list_permissions_check(request):
|
|
||||||
raise PermissionDenied()
|
|
||||||
elif not getattr(view, 'item_permissions_check', None):
|
|
||||||
raise Exception('internal error, list_permissions_check or '
|
|
||||||
'item_permissions_check must be defined')
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Otherwise, check the item permissions for the given obj.
|
|
||||||
|
|
||||||
else:
|
|
||||||
if not view.item_permissions_check(request, obj):
|
|
||||||
raise PermissionDenied()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def has_permission(self, request, view, obj=None):
|
def has_permission(self, request, view, obj=None):
|
||||||
|
|
||||||
logger.debug('has_permission(user=%s method=%s data=%r, %s, %r)',
|
logger.debug('has_permission(user=%s method=%s data=%r, %s, %r)',
|
||||||
request.user, request.method, request.DATA,
|
request.user, request.method, request.DATA,
|
||||||
view.__class__.__name__, obj)
|
view.__class__.__name__, obj)
|
||||||
try:
|
try:
|
||||||
response = self._check_permissions(request, view, obj)
|
response = self.check_permissions(request, view, obj)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.debug('has_permission raised %r', e, exc_info=True)
|
logger.debug('has_permission raised %r', e, exc_info=True)
|
||||||
raise
|
raise
|
||||||
@@ -122,7 +122,7 @@ class CustomRbac(permissions.BasePermission):
|
|||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
return self.has_permission(request, view, obj)
|
return self.has_permission(request, view, obj)
|
||||||
|
|
||||||
class JobTemplateCallbackPermission(CustomRbac):
|
class JobTemplateCallbackPermission(ModelAccessPermission):
|
||||||
'''
|
'''
|
||||||
Permission check used by job template callback view for requests from
|
Permission check used by job template callback view for requests from
|
||||||
empheral hosts.
|
empheral hosts.
|
||||||
@@ -149,7 +149,7 @@ class JobTemplateCallbackPermission(CustomRbac):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class JobTaskPermission(CustomRbac):
|
class JobTaskPermission(ModelAccessPermission):
|
||||||
'''
|
'''
|
||||||
Permission checks used for API callbacks from running a task.
|
Permission checks used for API callbacks from running a task.
|
||||||
'''
|
'''
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
# Copyright (c) 2013 AnsibleWorks, Inc.
|
# Copyright (c) 2013 AnsibleWorks, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
# Python
|
||||||
import cStringIO
|
import cStringIO
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import select
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
|
||||||
import traceback
|
import traceback
|
||||||
from celery import Task
|
|
||||||
from django.conf import settings
|
# Pexpect
|
||||||
import pexpect
|
import pexpect
|
||||||
from awx.main.models import *
|
|
||||||
|
# Celery
|
||||||
|
from celery import Task
|
||||||
|
|
||||||
|
# Django
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# AWX
|
||||||
|
from awx.main.models import Job
|
||||||
|
|
||||||
__all__ = ['RunJob']
|
__all__ = ['RunJob']
|
||||||
|
|
||||||
@@ -39,7 +46,6 @@ class RunJob(Task):
|
|||||||
if field == 'status':
|
if field == 'status':
|
||||||
update_fields.append('failed')
|
update_fields.append('failed')
|
||||||
job.save(update_fields=update_fields)
|
job.save(update_fields=update_fields)
|
||||||
# FIXME: Commit transaction?
|
|
||||||
return job
|
return job
|
||||||
|
|
||||||
def get_path_to(self, *args):
|
def get_path_to(self, *args):
|
||||||
@@ -165,6 +171,7 @@ class RunJob(Task):
|
|||||||
r'Bad passphrase, try again for .*:',
|
r'Bad passphrase, try again for .*:',
|
||||||
r'sudo password.*:',
|
r'sudo password.*:',
|
||||||
r'SSH password:',
|
r'SSH password:',
|
||||||
|
r'Password:',
|
||||||
pexpect.TIMEOUT,
|
pexpect.TIMEOUT,
|
||||||
pexpect.EOF,
|
pexpect.EOF,
|
||||||
]
|
]
|
||||||
@@ -175,7 +182,7 @@ class RunJob(Task):
|
|||||||
child.sendline('')
|
child.sendline('')
|
||||||
elif result_id == 2:
|
elif result_id == 2:
|
||||||
child.sendline(passwords.get('sudo_password', ''))
|
child.sendline(passwords.get('sudo_password', ''))
|
||||||
elif result_id == 3:
|
elif result_id in (3, 4):
|
||||||
child.sendline(passwords.get('ssh_password', ''))
|
child.sendline(passwords.get('ssh_password', ''))
|
||||||
job_updates = {}
|
job_updates = {}
|
||||||
if logfile_pos != logfile.tell():
|
if logfile_pos != logfile.tell():
|
||||||
|
|||||||
@@ -187,9 +187,7 @@ class CleanupDeletedTest(BaseCommandTest):
|
|||||||
self.assertEqual(counts_before, counts_after)
|
self.assertEqual(counts_before, counts_after)
|
||||||
# "Delete some users".
|
# "Delete some users".
|
||||||
for user in User.objects.all():
|
for user in User.objects.all():
|
||||||
user.username = "_deleted_%s_%s" % (now().isoformat(), user.username)
|
user.mark_inactive()
|
||||||
user.is_active = False
|
|
||||||
user.save()
|
|
||||||
# With days=1, no users will be deleted.
|
# With days=1, no users will be deleted.
|
||||||
counts_before = self.get_user_counts()
|
counts_before = self.get_user_counts()
|
||||||
self.assertTrue(counts_before[1])
|
self.assertTrue(counts_before[1])
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -147,8 +147,11 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
),
|
),
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'awx.main.permissions.ModelAccessPermission',
|
||||||
|
),
|
||||||
'DEFAULT_FILTER_BACKENDS': (
|
'DEFAULT_FILTER_BACKENDS': (
|
||||||
'awx.main.custom_filters.CustomFilterBackend',
|
'awx.main.filters.DefaultFilterBackend',
|
||||||
),
|
),
|
||||||
'DEFAULT_PARSER_CLASSES': (
|
'DEFAULT_PARSER_CLASSES': (
|
||||||
'rest_framework.parsers.JSONParser',
|
'rest_framework.parsers.JSONParser',
|
||||||
@@ -323,7 +326,7 @@ LOGGING = {
|
|||||||
'handlers': ['console', 'file', 'syslog'],
|
'handlers': ['console', 'file', 'syslog'],
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
},
|
},
|
||||||
'awx.main.rbac': {
|
'awx.main.permissions': {
|
||||||
'handlers': ['null'],
|
'handlers': ['null'],
|
||||||
# Comment the line below to show lots of permissions logging.
|
# Comment the line below to show lots of permissions logging.
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
|
|||||||
Reference in New Issue
Block a user