Merge pull request #1560 from anoek/11th-hour

RBAC RolePermission removal updates
This commit is contained in:
Akita Noek
2016-04-18 21:08:54 -04:00
76 changed files with 1582 additions and 1346 deletions
+4 -1
View File
@@ -13,6 +13,7 @@ from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.contrib.contenttypes.models import ContentType
# Django REST Framework # Django REST Framework
from rest_framework.authentication import get_authorization_header from rest_framework.authentication import get_authorization_header
@@ -475,7 +476,9 @@ class ResourceAccessList(ListAPIView):
resource_model = getattr(self, 'resource_model') resource_model = getattr(self, 'resource_model')
obj = resource_model.objects.get(pk=self.object_id) obj = resource_model.objects.get(pk=self.object_id)
roles = set([p.role for p in obj.role_permissions.all()]) content_type = ContentType.objects.get_for_model(obj)
roles = set(Role.objects.filter(content_type=content_type, object_id=obj.id))
ancestors = set() ancestors = set()
for r in roles: for r in roles:
ancestors.update(set(r.ancestors.all())) ancestors.update(set(r.ancestors.all()))
+7 -5
View File
@@ -1485,7 +1485,7 @@ class ResourceAccessListElementSerializer(UserSerializer):
if 'summary_fields' not in ret: if 'summary_fields' not in ret:
ret['summary_fields'] = {} ret['summary_fields'] = {}
ret['summary_fields']['permissions'] = get_user_permissions_on_resource(obj, user) ret['summary_fields']['permissions'] = get_roles_on_resource(obj, user)
def format_role_perm(role): def format_role_perm(role):
role_dict = { 'id': role.id, 'name': role.name, 'description': role.description} role_dict = { 'id': role.id, 'name': role.name, 'description': role.description}
@@ -1495,7 +1495,7 @@ class ResourceAccessListElementSerializer(UserSerializer):
role_dict['related'] = reverse_gfk(role.content_object) role_dict['related'] = reverse_gfk(role.content_object)
except: except:
pass pass
return { 'role': role_dict, 'permissions': get_role_permissions_on_resource(obj, role)} return { 'role': role_dict, 'permissions': get_roles_on_resource(obj, role)}
def format_team_role_perm(team_role, permissive_role_ids): def format_team_role_perm(team_role, permissive_role_ids):
role = team_role.children.filter(id__in=permissive_role_ids)[0] role = team_role.children.filter(id__in=permissive_role_ids)[0]
@@ -1513,13 +1513,15 @@ class ResourceAccessListElementSerializer(UserSerializer):
role_dict['related'] = reverse_gfk(role.content_object) role_dict['related'] = reverse_gfk(role.content_object)
except: except:
pass pass
return { 'role': role_dict, 'permissions': get_role_permissions_on_resource(obj, team_role)} return { 'role': role_dict, 'permissions': get_roles_on_resource(obj, team_role)}
team_content_type = ContentType.objects.get_for_model(Team) team_content_type = ContentType.objects.get_for_model(Team)
content_type = ContentType.objects.get_for_model(obj) content_type = ContentType.objects.get_for_model(obj)
direct_permissive_role_ids = RolePermission.objects.filter(content_type=content_type, object_id=obj.id).values_list('role__id')
all_permissive_role_ids = RolePermission.objects.filter(content_type=content_type, object_id=obj.id).values_list('role__ancestors__id') content_type = ContentType.objects.get_for_model(obj)
direct_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('id', flat=True)
all_permissive_role_ids = Role.objects.filter(content_type=content_type, object_id=obj.id).values_list('ancestors__id', flat=True)
direct_access_roles = user.roles \ direct_access_roles = user.roles \
.filter(id__in=direct_permissive_role_ids).all() .filter(id__in=direct_permissive_role_ids).all()
+30 -30
View File
@@ -220,7 +220,7 @@ class ApiV1ConfigView(APIView):
user_ldap_fields.extend(getattr(settings, 'AUTH_LDAP_USER_FLAGS_BY_GROUP', {}).keys()) user_ldap_fields.extend(getattr(settings, 'AUTH_LDAP_USER_FLAGS_BY_GROUP', {}).keys())
data['user_ldap_fields'] = user_ldap_fields data['user_ldap_fields'] = user_ldap_fields
if request.user.is_superuser or Organization.accessible_objects(request.user, {'write': True}).exists(): if request.user.is_superuser or Organization.accessible_objects(request.user, 'admin_role').exists():
data.update(dict( data.update(dict(
project_base_dir = settings.PROJECTS_ROOT, project_base_dir = settings.PROJECTS_ROOT,
project_local_paths = Project.get_local_path_choices(), project_local_paths = Project.get_local_path_choices(),
@@ -566,7 +566,7 @@ class OrganizationList(ListCreateAPIView):
serializer_class = OrganizationSerializer serializer_class = OrganizationSerializer
def get_queryset(self): def get_queryset(self):
qs = Organization.accessible_objects(self.request.user, {'read': True}) qs = Organization.accessible_objects(self.request.user, 'read_role')
qs = qs.select_related('admin_role', 'auditor_role', 'member_role') qs = qs.select_related('admin_role', 'auditor_role', 'member_role')
return qs return qs
@@ -595,27 +595,27 @@ class OrganizationList(ListCreateAPIView):
return full_context return full_context
db_results = {} db_results = {}
org_qs = self.model.accessible_objects(self.request.user, {"read": True}) org_qs = self.model.accessible_objects(self.request.user, 'read_role')
org_id_list = org_qs.values('id') org_id_list = org_qs.values('id')
if len(org_id_list) == 0: if len(org_id_list) == 0:
if self.request.method == 'POST': if self.request.method == 'POST':
full_context['related_field_counts'] = {} full_context['related_field_counts'] = {}
return full_context return full_context
inv_qs = Inventory.accessible_objects(self.request.user, {"read": True}) inv_qs = Inventory.accessible_objects(self.request.user, 'read_role')
project_qs = Project.accessible_objects(self.request.user, {"read": True}) project_qs = Project.accessible_objects(self.request.user, 'read_role')
# Produce counts of Foreign Key relationships # Produce counts of Foreign Key relationships
db_results['inventories'] = inv_qs\ db_results['inventories'] = inv_qs\
.values('organization').annotate(Count('organization')).order_by('organization') .values('organization').annotate(Count('organization')).order_by('organization')
db_results['teams'] = Team.accessible_objects( db_results['teams'] = Team.accessible_objects(
self.request.user, {"read": True}).values('organization').annotate( self.request.user, 'read_role').values('organization').annotate(
Count('organization')).order_by('organization') Count('organization')).order_by('organization')
JT_reference = 'project__organization' JT_reference = 'project__organization'
db_results['job_templates'] = JobTemplate.accessible_objects( db_results['job_templates'] = JobTemplate.accessible_objects(
self.request.user, {"read": True}).values(JT_reference).annotate( self.request.user, 'read_role').values(JT_reference).annotate(
Count(JT_reference)).order_by(JT_reference) Count(JT_reference)).order_by(JT_reference)
db_results['projects'] = project_qs\ db_results['projects'] = project_qs\
@@ -667,7 +667,7 @@ class OrganizationDetail(RetrieveUpdateDestroyAPIView):
org_id = int(self.kwargs['pk']) org_id = int(self.kwargs['pk'])
org_counts = {} org_counts = {}
access_kwargs = {'accessor': self.request.user, 'permissions': {"read": True}} access_kwargs = {'accessor': self.request.user, 'role_field': 'read_role'}
direct_counts = Organization.objects.filter(id=org_id).annotate( direct_counts = Organization.objects.filter(id=org_id).annotate(
users=Count('member_role__members', distinct=True), users=Count('member_role__members', distinct=True),
admins=Count('admin_role__members', distinct=True) admins=Count('admin_role__members', distinct=True)
@@ -784,8 +784,8 @@ class TeamList(ListCreateAPIView):
serializer_class = TeamSerializer serializer_class = TeamSerializer
def get_queryset(self): def get_queryset(self):
qs = Team.accessible_objects(self.request.user, {'read': True}) qs = Team.accessible_objects(self.request.user, 'read_role').order_by()
qs = qs.select_related('admin_role', 'auditor_role', 'member_role') qs = qs.select_related('admin_role', 'auditor_role', 'member_role', 'organization')
return qs return qs
class TeamDetail(RetrieveUpdateDestroyAPIView): class TeamDetail(RetrieveUpdateDestroyAPIView):
@@ -831,8 +831,8 @@ class TeamProjectsList(SubListAPIView):
def get_queryset(self): def get_queryset(self):
team = self.get_parent_object() team = self.get_parent_object()
self.check_parent_access(team) self.check_parent_access(team)
team_qs = Project.objects.filter(Q(member_role__parents=team.member_role) | Q(admin_role__parents=team.member_role)) team_qs = Project.objects.filter(Q(member_role__parents=team.member_role) | Q(admin_role__parents=team.member_role)).distinct()
user_qs = Project.accessible_objects(self.request.user, {'read': True}) user_qs = Project.accessible_objects(self.request.user, 'read_role').distinct()
return team_qs & user_qs return team_qs & user_qs
@@ -860,8 +860,8 @@ class TeamActivityStreamList(SubListAPIView):
qs = self.request.user.get_queryset(self.model) qs = self.request.user.get_queryset(self.model)
return qs.filter(Q(team=parent) | return qs.filter(Q(team=parent) |
Q(project__in=Project.accessible_objects(parent, {'read':True})) | Q(project__in=Project.accessible_objects(parent, 'read_role')) |
Q(credential__in=Credential.accessible_objects(parent, {'read':True}))) Q(credential__in=Credential.accessible_objects(parent, 'read_role')))
class TeamAccessList(ResourceAccessList): class TeamAccessList(ResourceAccessList):
@@ -875,7 +875,7 @@ class ProjectList(ListCreateAPIView):
serializer_class = ProjectSerializer serializer_class = ProjectSerializer
def get_queryset(self): def get_queryset(self):
projects_qs = Project.accessible_objects(self.request.user, {'read': True}) projects_qs = Project.accessible_objects(self.request.user, 'read_role')
projects_qs = projects_qs.select_related( projects_qs = projects_qs.select_related(
'organization', 'organization',
'admin_role', 'admin_role',
@@ -1065,7 +1065,7 @@ class UserTeamsList(ListAPIView):
u = get_object_or_404(User, pk=self.kwargs['pk']) u = get_object_or_404(User, pk=self.kwargs['pk'])
if not self.request.user.can_access(User, 'read', u): if not self.request.user.can_access(User, 'read', u):
raise PermissionDenied() raise PermissionDenied()
return Team.accessible_objects(self.request.user, {'read': True}).filter(member_role__members=u) return Team.accessible_objects(self.request.user, 'read_role').filter(member_role__members=u)
class UserRolesList(SubListCreateAttachDetachAPIView): class UserRolesList(SubListCreateAttachDetachAPIView):
@@ -1103,8 +1103,8 @@ class UserProjectsList(SubListAPIView):
def get_queryset(self): def get_queryset(self):
parent = self.get_parent_object() parent = self.get_parent_object()
self.check_parent_access(parent) self.check_parent_access(parent)
my_qs = Project.accessible_objects(self.request.user, {'read': True}) my_qs = Project.accessible_objects(self.request.user, 'read_role')
user_qs = Project.accessible_objects(parent, {'read': True}) user_qs = Project.accessible_objects(parent, 'read_role')
return my_qs & user_qs return my_qs & user_qs
class UserOrganizationsList(SubListAPIView): class UserOrganizationsList(SubListAPIView):
@@ -1117,7 +1117,7 @@ class UserOrganizationsList(SubListAPIView):
def get_queryset(self): def get_queryset(self):
parent = self.get_parent_object() parent = self.get_parent_object()
self.check_parent_access(parent) self.check_parent_access(parent)
my_qs = Organization.accessible_objects(self.request.user, {'read': True}) my_qs = Organization.accessible_objects(self.request.user, 'read_role')
user_qs = Organization.objects.filter(member_role__members=parent) user_qs = Organization.objects.filter(member_role__members=parent)
return my_qs & user_qs return my_qs & user_qs
@@ -1131,7 +1131,7 @@ class UserAdminOfOrganizationsList(SubListAPIView):
def get_queryset(self): def get_queryset(self):
parent = self.get_parent_object() parent = self.get_parent_object()
self.check_parent_access(parent) self.check_parent_access(parent)
my_qs = Organization.accessible_objects(self.request.user, {'read': True}) my_qs = Organization.accessible_objects(self.request.user, 'read_role')
user_qs = Organization.objects.filter(admin_role__members=parent) user_qs = Organization.objects.filter(admin_role__members=parent)
return my_qs & user_qs return my_qs & user_qs
@@ -1217,7 +1217,7 @@ class CredentialList(ListCreateAPIView):
organization = Organization.objects.get(pk=request.data['organization']) organization = Organization.objects.get(pk=request.data['organization'])
obj = organization obj = organization
if not obj.accessible_by(self.request.user, {'write': True}): if self.request.user not in obj.admin_role:
raise PermissionDenied() raise PermissionDenied()
ret = super(CredentialList, self).post(request, *args, **kwargs) ret = super(CredentialList, self).post(request, *args, **kwargs)
@@ -1242,8 +1242,8 @@ class UserCredentialsList(CredentialList):
if not self.request.user.can_access(User, 'read', user): if not self.request.user.can_access(User, 'read', user):
raise PermissionDenied() raise PermissionDenied()
visible_creds = Credential.accessible_objects(self.request.user, {'read': True}) visible_creds = Credential.accessible_objects(self.request.user, 'read_role')
user_creds = Credential.accessible_objects(user, {'read': True}) user_creds = Credential.accessible_objects(user, 'read_role')
return user_creds & visible_creds return user_creds & visible_creds
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@@ -1262,7 +1262,7 @@ class TeamCredentialsList(CredentialList):
if not self.request.user.can_access(Team, 'read', team): if not self.request.user.can_access(Team, 'read', team):
raise PermissionDenied() raise PermissionDenied()
visible_creds = Credential.accessible_objects(self.request.user, {'read': True}) visible_creds = Credential.accessible_objects(self.request.user, 'read_role')
team_creds = Credential.objects.filter(owner_role__parents=team.member_role) team_creds = Credential.objects.filter(owner_role__parents=team.member_role)
return team_creds & visible_creds return team_creds & visible_creds
@@ -1282,8 +1282,8 @@ class OrganizationCredentialList(CredentialList):
if not self.request.user.can_access(Organization, 'read', organization): if not self.request.user.can_access(Organization, 'read', organization):
raise PermissionDenied() raise PermissionDenied()
user_visible = Credential.accessible_objects(self.request.user, {'read': True}).all() user_visible = Credential.accessible_objects(self.request.user, 'read_role').all()
org_set = Credential.accessible_objects(organization.admin_role, {'read': True}).all() org_set = Credential.accessible_objects(organization.admin_role, 'read_role').all()
if self.request.user.is_superuser: if self.request.user.is_superuser:
return org_set return org_set
@@ -1353,8 +1353,8 @@ class InventoryList(ListCreateAPIView):
serializer_class = InventorySerializer serializer_class = InventorySerializer
def get_queryset(self): def get_queryset(self):
qs = Inventory.accessible_objects(self.request.user, {'read': True}) qs = Inventory.accessible_objects(self.request.user, 'read_role')
qs = qs.select_related('admin_role', 'auditor_role', 'updater_role', 'executor_role') qs = qs.select_related('admin_role', 'auditor_role', 'update_role', 'execute_role')
return qs return qs
class InventoryDetail(RetrieveUpdateDestroyAPIView): class InventoryDetail(RetrieveUpdateDestroyAPIView):
@@ -2120,12 +2120,12 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
if 'credential' in prompted_fields and prompted_fields['credential'] != getattrd(obj, 'credential.pk', None): if 'credential' in prompted_fields and prompted_fields['credential'] != getattrd(obj, 'credential.pk', None):
new_credential = Credential.objects.get(pk=prompted_fields['credential']) new_credential = Credential.objects.get(pk=prompted_fields['credential'])
if not request.user.can_access(Credential, 'use', new_credential): if request.user not in new_credential.use_role:
raise PermissionDenied() raise PermissionDenied()
if 'inventory' in prompted_fields and prompted_fields['inventory'] != getattrd(obj, 'inventory.pk', None): if 'inventory' in prompted_fields and prompted_fields['inventory'] != getattrd(obj, 'inventory.pk', None):
new_inventory = Inventory.objects.get(pk=prompted_fields['inventory']) new_inventory = Inventory.objects.get(pk=prompted_fields['inventory'])
if not request.user.can_access(Inventory, 'use', new_inventory): if request.user not in new_inventory.use_role:
raise PermissionDenied() raise PermissionDenied()
kv = prompted_fields kv = prompted_fields
+98 -94
View File
@@ -18,13 +18,12 @@ from rest_framework.exceptions import ParseError, PermissionDenied
from awx.main.utils import * # noqa from awx.main.utils import * # noqa
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.models.mixins import ResourceMixin from awx.main.models.mixins import ResourceMixin
from awx.main.models.rbac import ALL_PERMISSIONS
from awx.api.license import LicenseForbids from awx.api.license import LicenseForbids
from awx.main.task_engine import TaskSerializer from awx.main.task_engine import TaskSerializer
from awx.main.conf import tower_settings from awx.main.conf import tower_settings
__all__ = ['get_user_queryset', 'check_user_access', __all__ = ['get_user_queryset', 'check_user_access',
'user_accessible_objects', 'user_accessible_by', 'user_accessible_objects',
'user_admin_role',] 'user_admin_role',]
PERMISSION_TYPES = [ PERMISSION_TYPES = [
@@ -64,19 +63,14 @@ def register_access(model_class, access_class):
@property @property
def user_admin_role(self): def user_admin_role(self):
return Role.objects.get(content_type=ContentType.objects.get_for_model(User), object_id=self.id) return Role.objects.get(
content_type=ContentType.objects.get_for_model(User),
object_id=self.id,
role_field='admin_role'
)
def user_accessible_objects(user, permissions): def user_accessible_objects(user, role_name):
return ResourceMixin._accessible_objects(User, user, permissions) return ResourceMixin._accessible_objects(User, user, role_name)
def user_accessible_by(instance, user, permissions):
perms = get_user_permissions_on_resource(instance, user)
if perms is None:
return False
for k in permissions:
if k not in perms or perms[k] < permissions[k]:
return False
return True
def get_user_queryset(user, model_class): def get_user_queryset(user, model_class):
''' '''
@@ -218,11 +212,18 @@ class UserAccess(BaseAccess):
if tower_settings.ORG_ADMINS_CAN_SEE_ALL_USERS and self.user.admin_of_organizations.exists(): if tower_settings.ORG_ADMINS_CAN_SEE_ALL_USERS and self.user.admin_of_organizations.exists():
return User.objects.all() return User.objects.all()
viewable_users_set = set() return (
viewable_users_set.update(self.user.roles.values_list('ancestors__members__id', flat=True)) User.objects.filter(
viewable_users_set.update(self.user.roles.values_list('descendents__members__id', flat=True)) pk__in=Organization.accessible_objects(self.user, 'read_role').values('member_role__members')
) |
User.objects.filter(
pk=self.user.id
) |
User.objects.filter(
pk__in=Role.objects.filter(singleton_name__in = [ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR]).values('members')
)
).distinct()
return User.objects.filter(id__in=viewable_users_set)
def can_add(self, data): def can_add(self, data):
if data is not None and 'is_superuser' in data: if data is not None and 'is_superuser' in data:
@@ -230,7 +231,7 @@ class UserAccess(BaseAccess):
return False return False
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return Organization.accessible_objects(self.user, ALL_PERMISSIONS).exists() return Organization.accessible_objects(self.user, 'admin_role').exists()
def can_change(self, obj, data): def can_change(self, obj, data):
if data is not None and 'is_superuser' in data: if data is not None and 'is_superuser' in data:
@@ -257,7 +258,7 @@ class UserAccess(BaseAccess):
return False return False
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, {'delete': True}) return False
class OrganizationAccess(BaseAccess): class OrganizationAccess(BaseAccess):
@@ -273,13 +274,13 @@ class OrganizationAccess(BaseAccess):
model = Organization model = Organization
def get_queryset(self): def get_queryset(self):
qs = self.model.accessible_objects(self.user, {'read':True}) qs = self.model.accessible_objects(self.user, 'read_role')
return qs.select_related('created_by', 'modified_by').all() return qs.select_related('created_by', 'modified_by').all()
def can_change(self, obj, data): def can_change(self, obj, data):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.admin_role
def can_delete(self, obj): def can_delete(self, obj):
self.check_license(feature='multiple_organizations', check_expiration=False) self.check_license(feature='multiple_organizations', check_expiration=False)
@@ -308,29 +309,29 @@ class InventoryAccess(BaseAccess):
model = Inventory model = Inventory
def get_queryset(self, allowed=None, ad_hoc=None): def get_queryset(self, allowed=None, ad_hoc=None):
qs = self.model.accessible_objects(self.user, {'read': True}) qs = self.model.accessible_objects(self.user, 'read_role')
return qs.select_related('created_by', 'modified_by', 'organization').all() return qs.select_related('created_by', 'modified_by', 'organization').all()
def can_read(self, obj): def can_read(self, obj):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, {'read': True}) return self.user in obj.read_role
def can_use(self, obj): def can_use(self, obj):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, {'use': True}) return self.user in obj.use_role
def can_add(self, data): def can_add(self, data):
# If no data is specified, just checking for generic add permission? # If no data is specified, just checking for generic add permission?
if not data: if not data:
return Organization.accessible_objects(self.user, ALL_PERMISSIONS).exists() return Organization.accessible_objects(self.user, 'admin_role').exists()
if self.user.is_superuser: if self.user.is_superuser:
return True return True
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
org = get_object_or_400(Organization, pk=org_pk) org = get_object_or_400(Organization, pk=org_pk)
return org.accessible_by(self.user, {'read': True, 'create':True, 'update': True, 'delete': True}) return self.user in org.admin_role
def can_change(self, obj, data): def can_change(self, obj, data):
# Verify that the user has access to the new organization if moving an # Verify that the user has access to the new organization if moving an
@@ -338,10 +339,10 @@ class InventoryAccess(BaseAccess):
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
if obj and org_pk and obj.organization.pk != org_pk: if obj and org_pk and obj.organization.pk != org_pk:
org = get_object_or_400(Organization, pk=org_pk) org = get_object_or_400(Organization, pk=org_pk)
if not org.accessible_by(self.user, {'read': True, 'create':True, 'update': True, 'delete': True}): if self.user not in org.admin_role:
return False return False
# Otherwise, just check for write permission. # Otherwise, just check for write permission.
return obj.accessible_by(self.user, {'read': True, 'create':True, 'update': True, 'delete': True}) return self.user in obj.admin_role
def can_admin(self, obj, data): def can_admin(self, obj, data):
# Verify that the user has access to the new organization if moving an # Verify that the user has access to the new organization if moving an
@@ -349,16 +350,16 @@ class InventoryAccess(BaseAccess):
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
if obj and org_pk and obj.organization.pk != org_pk: if obj and org_pk and obj.organization.pk != org_pk:
org = get_object_or_400(Organization, pk=org_pk) org = get_object_or_400(Organization, pk=org_pk)
if not org.accessible_by(self.user, ALL_PERMISSIONS): if self.user not in org.admin_role:
return False return False
# Otherwise, just check for admin permission. # Otherwise, just check for admin permission.
return obj.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.admin_role
def can_delete(self, obj): def can_delete(self, obj):
return self.can_admin(obj, None) return self.can_admin(obj, None)
def can_run_ad_hoc_commands(self, obj): def can_run_ad_hoc_commands(self, obj):
return obj.accessible_by(self.user, {'execute': True}) return self.user in obj.adhoc_role
class HostAccess(BaseAccess): class HostAccess(BaseAccess):
''' '''
@@ -369,14 +370,17 @@ class HostAccess(BaseAccess):
model = Host model = Host
def get_queryset(self): def get_queryset(self):
qs = self.model.accessible_objects(self.user, {'read':True}) inv_qs = Inventory.accessible_objects(self.user, 'read_role')
qs = qs.select_related('created_by', 'modified_by', 'inventory', group_qs = Group.accessible_objects(self.user, 'read_role')
'last_job__job_template', qs = (self.model.objects.filter(inventory=inv_qs) | self.model.objects.filter(groups=group_qs)).distinct()
'last_job_host_summary__job') #qs = qs.select_related('created_by', 'modified_by', 'inventory',
return qs.prefetch_related('groups').all() # 'last_job__job_template',
# 'last_job_host_summary__job')
#return qs.prefetch_related('groups').all()
return qs
def can_read(self, obj): def can_read(self, obj):
return obj and obj.inventory.accessible_by(self.user, {'read':True}) return obj and any(self.user in grp.read_role for grp in obj.groups.all()) or self.user in obj.inventory.read_role
def can_add(self, data): def can_add(self, data):
if not data or 'inventory' not in data: if not data or 'inventory' not in data:
@@ -385,7 +389,7 @@ class HostAccess(BaseAccess):
# Checks for admin or change permission on inventory. # Checks for admin or change permission on inventory.
inventory_pk = get_pk_from_dict(data, 'inventory') inventory_pk = get_pk_from_dict(data, 'inventory')
inventory = get_object_or_400(Inventory, pk=inventory_pk) inventory = get_object_or_400(Inventory, pk=inventory_pk)
if not inventory.accessible_by(self.user, {'read':True, 'create':True}): if self.user not in inventory.admin_role:
return False return False
# Check to see if we have enough licenses # Check to see if we have enough licenses
@@ -399,7 +403,7 @@ class HostAccess(BaseAccess):
raise PermissionDenied('Unable to change inventory on a host') raise PermissionDenied('Unable to change inventory on a host')
# 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 obj and obj.inventory.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) return obj and self.user in obj.inventory.admin_role
def can_attach(self, obj, sub_obj, relationship, data, def can_attach(self, obj, sub_obj, relationship, data,
skip_sub_obj_read_check=False): skip_sub_obj_read_check=False):
@@ -412,7 +416,7 @@ class HostAccess(BaseAccess):
return True return True
def can_delete(self, obj): def can_delete(self, obj):
return obj and obj.inventory.accessible_by(self.user, {'delete':True}) return obj and self.user in obj.inventory.admin_role
class GroupAccess(BaseAccess): class GroupAccess(BaseAccess):
''' '''
@@ -423,12 +427,12 @@ class GroupAccess(BaseAccess):
model = Group model = Group
def get_queryset(self): def get_queryset(self):
qs = self.model.accessible_objects(self.user, {'read':True}) qs = self.model.accessible_objects(self.user, 'read_role')
qs = qs.select_related('created_by', 'modified_by', 'inventory') qs = qs.select_related('created_by', 'modified_by', 'inventory')
return qs.prefetch_related('parents', 'children', 'inventory_source').all() return qs.prefetch_related('parents', 'children', 'inventory_source').all()
def can_read(self, obj): def can_read(self, obj):
return obj and obj.inventory.accessible_by(self.user, {'read':True}) return obj and self.user in obj.inventory.read_role
def can_add(self, data): def can_add(self, data):
if not data or 'inventory' not in data: if not data or 'inventory' not in data:
@@ -436,7 +440,7 @@ class GroupAccess(BaseAccess):
# Checks for admin or change permission on inventory. # Checks for admin or change permission on inventory.
inventory_pk = get_pk_from_dict(data, 'inventory') inventory_pk = get_pk_from_dict(data, 'inventory')
inventory = get_object_or_400(Inventory, pk=inventory_pk) inventory = get_object_or_400(Inventory, pk=inventory_pk)
return inventory.accessible_by(self.user, {'read':True, 'create':True}) return self.user in inventory.admin_role
def can_change(self, obj, data): def can_change(self, obj, data):
# Prevent moving a group to a different inventory. # Prevent moving a group to a different inventory.
@@ -445,7 +449,7 @@ class GroupAccess(BaseAccess):
raise PermissionDenied('Unable to change inventory on a group') raise PermissionDenied('Unable to change inventory on a group')
# 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 obj and obj.inventory.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) return obj and self.user in obj.inventory.admin_role
def can_attach(self, obj, sub_obj, relationship, data, def can_attach(self, obj, sub_obj, relationship, data,
skip_sub_obj_read_check=False): skip_sub_obj_read_check=False):
@@ -466,7 +470,7 @@ class GroupAccess(BaseAccess):
return True return True
def can_delete(self, obj): def can_delete(self, obj):
return obj and obj.inventory.accessible_by(self.user, {'delete':True}) return obj and self.user in obj.inventory.admin_role
class InventorySourceAccess(BaseAccess): class InventorySourceAccess(BaseAccess):
''' '''
@@ -485,9 +489,9 @@ class InventorySourceAccess(BaseAccess):
def can_read(self, obj): def can_read(self, obj):
if obj and obj.group: if obj and obj.group:
return obj.group.accessible_by(self.user, {'read':True}) return self.user in obj.group.read_role
elif obj and obj.inventory: elif obj and obj.inventory:
return obj.inventory.accessible_by(self.user, {'read':True}) return self.user in obj.inventory.read_role
else: else:
return False return False
@@ -498,7 +502,7 @@ class InventorySourceAccess(BaseAccess):
def can_change(self, obj, data): def can_change(self, obj, data):
# Checks for admin or change permission on group. # Checks for admin or change permission on group.
if obj and obj.group: if obj and obj.group:
return obj.group.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) return self.user in obj.group.admin_role
# Can't change inventory sources attached to only the inventory, since # Can't change inventory sources attached to only the inventory, since
# these are created automatically from the management command. # these are created automatically from the management command.
else: else:
@@ -548,11 +552,11 @@ class CredentialAccess(BaseAccess):
"""Return the queryset for credentials, based on what the user is """Return the queryset for credentials, based on what the user is
permitted to see. permitted to see.
""" """
qs = self.model.accessible_objects(self.user, {'read':True}) qs = self.model.accessible_objects(self.user, 'read_role')
return qs.select_related('created_by', 'modified_by').all() return qs.select_related('created_by', 'modified_by').all()
def can_read(self, obj): def can_read(self, obj):
return obj.accessible_by(self.user, {'read': True}) return self.user in obj.read_role
def can_add(self, data): def can_add(self, data):
# Access enforced in our view where we have context enough to make a decision # Access enforced in our view where we have context enough to make a decision
@@ -561,12 +565,12 @@ class CredentialAccess(BaseAccess):
def can_use(self, obj): def can_use(self, obj):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, {'use': True}) return self.user in obj.use_role
def can_change(self, obj, data): def can_change(self, obj, data):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, {'read':True, 'update': True, 'delete':True}) return self.user in obj.owner_role
def can_delete(self, obj): def can_delete(self, obj):
# Unassociated credentials may be marked deleted by anyone, though we # Unassociated credentials may be marked deleted by anyone, though we
@@ -579,17 +583,17 @@ class TeamAccess(BaseAccess):
''' '''
I can see a team when: I can see a team when:
- I'm a superuser. - I'm a superuser.
- I'm an admin of the team's organization. - I'm an admin of the team
- I'm a member of that team. - I'm a member of that team.
I can create/change a team when: I can create/change a team when:
- I'm a superuser. - I'm a superuser.
- I'm an org admin for the team's org. - I'm an admin for the team
''' '''
model = Team model = Team
def get_queryset(self): def get_queryset(self):
qs = self.model.accessible_objects(self.user, {'read':True}) qs = self.model.accessible_objects(self.user, 'read_role')
return qs.select_related('created_by', 'modified_by', 'organization').all() return qs.select_related('created_by', 'modified_by', 'organization').all()
def can_add(self, data): def can_add(self, data):
@@ -598,7 +602,7 @@ class TeamAccess(BaseAccess):
else: else:
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
org = get_object_or_400(Organization, pk=org_pk) org = get_object_or_400(Organization, pk=org_pk)
if org.accessible_by(self.user, {'read':True, 'update':True, 'write':True}): if self.user in org.admin_role:
return True return True
return False return False
@@ -607,7 +611,7 @@ class TeamAccess(BaseAccess):
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
if obj and org_pk and obj.organization.pk != org_pk: if obj and org_pk and obj.organization.pk != org_pk:
raise PermissionDenied('Unable to change organization on a team') raise PermissionDenied('Unable to change organization on a team')
return obj.organization.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.admin_role
def can_delete(self, obj): def can_delete(self, obj):
return self.can_change(obj, None) return self.can_change(obj, None)
@@ -633,19 +637,19 @@ class ProjectAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
if self.user.is_superuser: if self.user.is_superuser:
return self.model.objects.all() return self.model.objects.all()
qs = self.model.accessible_objects(self.user, {'read':True}) qs = self.model.accessible_objects(self.user, 'read_role')
return qs.select_related('modified_by', 'credential', 'current_job', 'last_job').all() return qs.select_related('modified_by', 'credential', 'current_job', 'last_job').all()
def can_add(self, data): def can_add(self, data):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
qs = Organization.accessible_objects(self.user, ALL_PERMISSIONS) qs = Organization.accessible_objects(self.user, 'admin_role')
return qs.exists() return qs.exists()
def can_change(self, obj, data): def can_change(self, obj, data):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.admin_role
def can_delete(self, obj): def can_delete(self, obj):
return self.can_change(obj, None) return self.can_change(obj, None)
@@ -674,7 +678,7 @@ class ProjectUpdateAccess(BaseAccess):
return self.can_change(obj, {}) and obj.can_cancel return self.can_change(obj, {}) and obj.can_cancel
def can_delete(self, obj): def can_delete(self, obj):
return obj and obj.project.accessible_by(self.user, {'delete':True}) return obj and self.user in obj.project.admin_role
class JobTemplateAccess(BaseAccess): class JobTemplateAccess(BaseAccess):
''' '''
@@ -695,7 +699,7 @@ class JobTemplateAccess(BaseAccess):
if self.user.is_superuser: if self.user.is_superuser:
qs = self.model.objects.all() qs = self.model.objects.all()
else: else:
qs = self.model.accessible_objects(self.user, {'read':True}) qs = self.model.accessible_objects(self.user, 'read_role')
return qs.select_related('created_by', 'modified_by', 'inventory', 'project', return qs.select_related('created_by', 'modified_by', 'inventory', 'project',
'credential', 'cloud_credential', 'next_schedule').all() 'credential', 'cloud_credential', 'next_schedule').all()
@@ -727,7 +731,7 @@ class JobTemplateAccess(BaseAccess):
credential_pk = get_pk_from_dict(data, 'credential') credential_pk = get_pk_from_dict(data, 'credential')
if credential_pk: if credential_pk:
credential = get_object_or_400(Credential, pk=credential_pk) credential = get_object_or_400(Credential, pk=credential_pk)
if not credential.accessible_by(self.user, {'read':True}): if self.user not in credential.read_role:
return False return False
# If a cloud credential is provided, the user should have read access. # If a cloud credential is provided, the user should have read access.
@@ -735,7 +739,7 @@ class JobTemplateAccess(BaseAccess):
if cloud_credential_pk: if cloud_credential_pk:
cloud_credential = get_object_or_400(Credential, cloud_credential = get_object_or_400(Credential,
pk=cloud_credential_pk) pk=cloud_credential_pk)
if not cloud_credential.accessible_by(self.user, {'read':True}): if self.user not in cloud_credential.read_role:
return False return False
# Check that the given inventory ID is valid. # Check that the given inventory ID is valid.
@@ -747,7 +751,7 @@ class JobTemplateAccess(BaseAccess):
project_pk = get_pk_from_dict(data, 'project') project_pk = get_pk_from_dict(data, 'project')
if 'job_type' in data and data['job_type'] == PERM_INVENTORY_SCAN: if 'job_type' in data and data['job_type'] == PERM_INVENTORY_SCAN:
org = inventory[0].organization org = inventory[0].organization
accessible = org.accessible_by(self.user, {'read':True, 'update':True, 'write':True}) accessible = self.user in org.admin_role
if not project_pk and accessible: if not project_pk and accessible:
return True return True
elif not accessible: elif not accessible:
@@ -755,10 +759,10 @@ class JobTemplateAccess(BaseAccess):
# If the user has admin access to the project (as an org admin), should # If the user has admin access to the project (as an org admin), should
# be able to proceed without additional checks. # be able to proceed without additional checks.
project = get_object_or_400(Project, pk=project_pk) project = get_object_or_400(Project, pk=project_pk)
if project.accessible_by(self.user, ALL_PERMISSIONS): if self.user in project.admin_role:
return True return True
return project.accessible_by(self.user, ALL_PERMISSIONS) and inventory.accessible_by(self.user, {'read':True}) return self.user in project.admin_role and self.user in inventory.read_role
def can_start(self, obj, validate_license=True): def can_start(self, obj, validate_license=True):
# Check license. # Check license.
@@ -776,10 +780,10 @@ class JobTemplateAccess(BaseAccess):
if obj.job_type == PERM_INVENTORY_SCAN: if obj.job_type == PERM_INVENTORY_SCAN:
# Scan job with default project, must have JT execute or be org admin # Scan job with default project, must have JT execute or be org admin
if obj.project is None and obj.inventory: if obj.project is None and obj.inventory:
return (obj.accessible_by(self.user, {'execute': True}) or return (self.user in obj.execute_role or
obj.inventory.organization.accessible_by(self.user, ALL_PERMISSIONS)) self.user in obj.inventory.organization.admin_role)
return obj.accessible_by(self.user, {'execute':True}) return self.user in obj.execute_role
def can_change(self, obj, data): def can_change(self, obj, data):
data_for_change = data data_for_change = data
@@ -814,7 +818,7 @@ class JobAccess(BaseAccess):
credential_ids = self.user.get_queryset(Credential) credential_ids = self.user.get_queryset(Credential)
return qs.filter( return qs.filter(
credential_id__in=credential_ids, credential_id__in=credential_ids,
job_template__in=JobTemplate.accessible_objects(self.user, {'read': True}) job_template__in=JobTemplate.accessible_objects(self.user, 'read_role')
) )
def can_add(self, data): def can_add(self, data):
@@ -847,7 +851,7 @@ class JobAccess(BaseAccess):
# Allow org admins and superusers to delete jobs # Allow org admins and superusers to delete jobs
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.inventory.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.inventory.admin_role
def can_start(self, obj): def can_start(self, obj):
self.check_license() self.check_license()
@@ -859,12 +863,12 @@ class JobAccess(BaseAccess):
# If a user can launch the job template then they can relaunch a job from that # If a user can launch the job template then they can relaunch a job from that
# job template # job template
if obj.job_template is not None: if obj.job_template is not None:
return obj.job_template.accessible_by(self.user, {'execute': True}) return self.user in obj.job_template.execute_role
inventory_access = obj.inventory.accessible_by(self.user, {'use':True}) inventory_access = self.user in obj.inventory.use_role
org_access = obj.inventory.organization.accessible_by(self.user, ALL_PERMISSIONS) org_access = self.user in obj.inventory.organization.admin_role
project_access = obj.project is None or obj.project.accessible_by(self.user, ALL_PERMISSIONS) project_access = obj.project is None or self.user in obj.project.admin_role
return inventory_access and (org_access or project_access) return inventory_access and (org_access or project_access)
@@ -906,7 +910,7 @@ class AdHocCommandAccess(BaseAccess):
return qs.all() return qs.all()
credential_ids = set(self.user.get_queryset(Credential).values_list('id', flat=True)) credential_ids = set(self.user.get_queryset(Credential).values_list('id', flat=True))
inventory_qs = Inventory.accessible_objects(self.user, {'read': True, 'execute': True}) inventory_qs = Inventory.accessible_objects(self.user, 'execute_role')
return qs.filter(credential_id__in=credential_ids, return qs.filter(credential_id__in=credential_ids,
inventory__in=inventory_qs) inventory__in=inventory_qs)
@@ -921,7 +925,7 @@ class AdHocCommandAccess(BaseAccess):
credential_pk = get_pk_from_dict(data, 'credential') credential_pk = get_pk_from_dict(data, 'credential')
if credential_pk: if credential_pk:
credential = get_object_or_400(Credential, pk=credential_pk) credential = get_object_or_400(Credential, pk=credential_pk)
if not credential.accessible_by(self.user, {'read':True}): if self.user not in credential.read_role:
return False return False
# Check that the user has the run ad hoc command permission on the # Check that the user has the run ad hoc command permission on the
@@ -929,7 +933,7 @@ class AdHocCommandAccess(BaseAccess):
inventory_pk = get_pk_from_dict(data, 'inventory') inventory_pk = get_pk_from_dict(data, 'inventory')
if inventory_pk: if inventory_pk:
inventory = get_object_or_400(Inventory, pk=inventory_pk) inventory = get_object_or_400(Inventory, pk=inventory_pk)
if not inventory.accessible_by(self.user, {'execute': True}): if self.user not in inventory.execute_role:
return False return False
return True return True
@@ -1189,23 +1193,23 @@ class NotifierAccess(BaseAccess):
qs = self.model.objects.all() qs = self.model.objects.all()
if self.user.is_superuser: if self.user.is_superuser:
return qs return qs
return self.model.objects.filter(organization__in=Organization.accessible_objects(self.user, ALL_PERMISSIONS).all()) return self.model.objects.filter(organization__in=Organization.accessible_objects(self.user, 'admin_role').all())
def can_read(self, obj): def can_read(self, obj):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
if obj.organization is not None: if obj.organization is not None:
return obj.organization.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.organization.admin_role
return False return False
def can_add(self, data): def can_add(self, data):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
if not data: if not data:
return Organization.accessible_objects(self.user, ALL_PERMISSIONS).exists() return Organization.accessible_objects(self.user, 'admin_role').exists()
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
org = get_object_or_400(Organization, pk=org_pk) org = get_object_or_400(Organization, pk=org_pk)
return org.accessible_by(self.user, ALL_PERMISSIONS) return self.user in org.admin_role
def can_change(self, obj, data): def can_change(self, obj, data):
if self.user.is_superuser: if self.user.is_superuser:
@@ -1213,10 +1217,10 @@ class NotifierAccess(BaseAccess):
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
if obj and org_pk and obj.organization.pk != org_pk: if obj and org_pk and obj.organization.pk != org_pk:
org = get_object_or_400(Organization, pk=org_pk) org = get_object_or_400(Organization, pk=org_pk)
if not org.accessible_by(self.user, ALL_PERMISSIONS): if self.user not in org.admin_role:
return False return False
if obj.organization is not None: if obj.organization is not None:
return obj.organization.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.organization.admin_role
return False return False
def can_admin(self, obj, data): def can_admin(self, obj, data):
@@ -1235,7 +1239,7 @@ class NotificationAccess(BaseAccess):
qs = self.model.objects.all() qs = self.model.objects.all()
if self.user.is_superuser: if self.user.is_superuser:
return qs return qs
return self.model.objects.filter(notifier__organization__in=Organization.accessible_objects(self.user, ALL_PERMISSIONS)) return self.model.objects.filter(notifier__organization__in=Organization.accessible_objects(self.user, 'admin_role'))
def can_read(self, obj): def can_read(self, obj):
return self.user.can_access(Notifier, 'read', obj.notifier) return self.user.can_access(Notifier, 'read', obj.notifier)
@@ -1253,13 +1257,13 @@ class LabelAccess(BaseAccess):
if self.user.is_superuser: if self.user.is_superuser:
return self.model.objects.all() return self.model.objects.all()
return self.model.objects.filter( return self.model.objects.filter(
organization__in=Organization.accessible_objects(self.user, {'read': True}) organization__in=Organization.accessible_objects(self.user, 'read_role')
) )
def can_read(self, obj): def can_read(self, obj):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.organization.accessible_by(self.user, {'read': True}) return self.user in obj.organization.read_role
def can_add(self, data): def can_add(self, data):
if self.user.is_superuser: if self.user.is_superuser:
@@ -1270,7 +1274,7 @@ class LabelAccess(BaseAccess):
org_pk = get_pk_from_dict(data, 'organization') org_pk = get_pk_from_dict(data, 'organization')
org = get_object_or_400(Organization, pk=org_pk) org = get_object_or_400(Organization, pk=org_pk)
return org.accessible_by(self.user, {'read': True}) return self.user in org.read_role
def can_change(self, obj, data): def can_change(self, obj, data):
if self.user.is_superuser: if self.user.is_superuser:
@@ -1279,7 +1283,7 @@ class LabelAccess(BaseAccess):
if self.can_add(data) is False: if self.can_add(data) is False:
return False return False
return obj.organization.accessible_by(self.user, ALL_PERMISSIONS) return self.user in obj.organization.admin_role
def can_delete(self, obj): def can_delete(self, obj):
return self.can_change(obj, None) return self.can_change(obj, None)
@@ -1370,12 +1374,12 @@ class CustomInventoryScriptAccess(BaseAccess):
def get_queryset(self): def get_queryset(self):
if self.user.is_superuser: if self.user.is_superuser:
return self.model.objects.distinct().all() return self.model.objects.distinct().all()
return self.model.accessible_objects(self.user, {'read':True}).all() return self.model.accessible_objects(self.user, 'read_role').all()
def can_read(self, obj): def can_read(self, obj):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
return obj.accessible_by(self.user, {'read':True}) return self.user in obj.read_role
def can_add(self, data): def can_add(self, data):
if self.user.is_superuser: if self.user.is_superuser:
@@ -1464,7 +1468,7 @@ class RoleAccess(BaseAccess):
return True return True
if obj.object_id and \ if obj.object_id and \
isinstance(obj.content_object, ResourceMixin) and \ isinstance(obj.content_object, ResourceMixin) and \
obj.content_object.accessible_by(self.user, {'write': True}): self.user in obj.content_object.admin_role:
return True return True
return False return False
+5 -34
View File
@@ -93,10 +93,9 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
class ImplicitRoleField(models.ForeignKey): class ImplicitRoleField(models.ForeignKey):
"""Implicitly creates a role entry for a resource""" """Implicitly creates a role entry for a resource"""
def __init__(self, role_name=None, role_description=None, permissions=None, parent_role=None, *args, **kwargs): def __init__(self, role_name=None, role_description=None, parent_role=None, *args, **kwargs):
self.role_name = role_name self.role_name = role_name
self.role_description = role_description if role_description else "" self.role_description = role_description if role_description else ""
self.permissions = permissions
self.parent_role = parent_role self.parent_role = parent_role
kwargs.setdefault('to', 'Role') kwargs.setdefault('to', 'Role')
@@ -108,7 +107,6 @@ class ImplicitRoleField(models.ForeignKey):
name, path, args, kwargs = super(ImplicitRoleField, self).deconstruct() name, path, args, kwargs = super(ImplicitRoleField, self).deconstruct()
kwargs['role_name'] = self.role_name kwargs['role_name'] = self.role_name
kwargs['role_description'] = self.role_description kwargs['role_description'] = self.role_description
kwargs['permissions'] = self.permissions
kwargs['parent_role'] = self.parent_role kwargs['parent_role'] = self.parent_role
return name, path, args, kwargs return name, path, args, kwargs
@@ -185,45 +183,17 @@ class ImplicitRoleField(models.ForeignKey):
role = Role_.objects.create( role = Role_.objects.create(
created=now(), created=now(),
modified=now(), modified=now(),
role_field=self.name,
name=self.role_name, name=self.role_name,
description=self.role_description description=self.role_description
) )
setattr(instance, self.name, role) setattr(instance, self.name, role)
def _patch_role_content_object_and_grant_permissions(self, instance): def _patch_role_content_object(self, instance):
role = getattr(instance, self.name) role = getattr(instance, self.name)
role.content_object = instance role.content_object = instance
role.save() role.save()
if self.permissions is not None:
RolePermission_ = get_current_apps().get_model('main', 'RolePermission')
ContentType = get_current_apps().get_model('contenttypes', "ContentType")
instance_content_type = ContentType.objects.get_for_model(instance)
permissions = RolePermission_(
created=now(),
modified=now(),
role=role,
content_type=instance_content_type,
object_id=instance.id,
auto_generated=True
)
if 'all' in self.permissions and self.permissions['all']:
del self.permissions['all']
self.permissions['create'] = True
self.permissions['read'] = True
self.permissions['write'] = True
self.permissions['update'] = True
self.permissions['delete'] = True
self.permissions['scm_update'] = True
self.permissions['use'] = True
self.permissions['execute'] = True
for k,v in self.permissions.items():
setattr(permissions, k, v)
permissions.save()
def _pre_save(self, instance, *args, **kwargs): def _pre_save(self, instance, *args, **kwargs):
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'): for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
implicit_role_field._create_role_instance_if_not_exists(instance) implicit_role_field._create_role_instance_if_not_exists(instance)
@@ -231,7 +201,7 @@ class ImplicitRoleField(models.ForeignKey):
def _post_save(self, instance, created, *args, **kwargs): def _post_save(self, instance, created, *args, **kwargs):
if created: if created:
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'): for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
implicit_role_field._patch_role_content_object_and_grant_permissions(instance) implicit_role_field._patch_role_content_object(instance)
with batch_role_ancestor_rebuilding(): with batch_role_ancestor_rebuilding():
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'): for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
@@ -264,6 +234,7 @@ class ImplicitRoleField(models.ForeignKey):
else: else:
role = Role_.objects.create(created=now(), role = Role_.objects.create(created=now(),
modified=now(), modified=now(),
role_field=path,
singleton_name=singleton_name, singleton_name=singleton_name,
name=singleton_name, name=singleton_name,
description=singleton_name) description=singleton_name)
@@ -1,354 +0,0 @@
# Copyright (c) 2016 Ansible, Inc.
# All Rights Reserved
# Python
import sys
from collections import defaultdict
from optparse import make_option
# Django
from django.core.management.base import BaseCommand
from django.utils.timezone import now
from django.contrib.auth.models import User
from django.db import transaction
# awx
from awx.main.models import * # noqa
class Rollback(Exception):
pass
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--organizations', action='store', type='int', default=3,
help='Number of organizations to create'),
make_option('--users', action='store', type='int', default=10,
help='Number of users to create'),
make_option('--teams', action='store', type='int', default=5,
help='Number of teams to create'),
make_option('--projects', action='store', type='int', default=10,
help='Number of projects to create'),
make_option('--job-templates', action='store', type='int', default=20,
help='Number of job templates to create'),
make_option('--credentials', action='store', type='int', default=5,
help='Number of credentials to create'),
make_option('--inventories', action='store', type='int', default=5,
help='Number of credentials to create'),
make_option('--inventory-groups', action='store', type='int', default=10,
help='Number of credentials to create'),
make_option('--inventory-hosts', action='store', type='int', default=40,
help='number of credentials to create'),
make_option('--jobs', action='store', type='int', default=200,
help='number of job entries to create'),
make_option('--job-events', action='store', type='int', default=500,
help='number of job event entries to create'),
make_option('--pretend', action='store_true',
help="Don't commit the data to the database"),
make_option('--prefix', action='store', type='string', default='',
help="Prefix generated names with this string"),
#make_option('--spread-bias', action='store', type='string', default='exponential',
# help='"exponential" to bias associations exponentially front loaded for - for ex'),
)
def handle(self, *args, **options):
n_organizations = int(options['organizations'])
n_users = int(options['users'])
n_teams = int(options['teams'])
n_projects = int(options['projects'])
n_job_templates = int(options['job_templates'])
n_credentials = int(options['credentials'])
n_inventories = int(options['inventories'])
n_inventory_groups = int(options['inventory_groups'])
n_inventory_hosts = int(options['inventory_hosts'])
n_jobs = int(options['jobs'])
n_job_events = int(options['job_events'])
prefix = options['prefix']
organizations = []
users = []
teams = []
projects = []
job_templates = []
credentials = []
inventories = []
inventory_groups = []
inventory_hosts = []
jobs = []
#job_events = []
def spread(n, m):
ret = []
# At least one in each slot, split up the rest exponentially so the first
# buckets contain a lot of entries
for i in xrange(m):
if n > 0:
ret.append(1)
n -= 1
else:
ret.append(0)
for i in xrange(m):
n_in_this_slot = n // 2
n-= n_in_this_slot
ret[i] += n_in_this_slot
if n > 0 and len(ret):
ret[0] += n
return ret
ids = defaultdict(lambda: 0)
try:
with transaction.atomic():
with batch_role_ancestor_rebuilding():
print('# Creating %d organizations' % n_organizations)
for i in xrange(n_organizations):
sys.stdout.write('\r%d ' % (i + 1))
sys.stdout.flush()
organizations.append(Organization.objects.create(name='%s Organization %d' % (prefix, i)))
print('')
print('# Creating %d users' % n_users)
org_idx = 0
for n in spread(n_users, n_organizations):
for i in range(n):
ids['user'] += 1
user_id = ids['user']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, organizations[org_idx].name, i+ 1))
sys.stdout.flush()
user = User.objects.create(username='%suser-%d' % (prefix, user_id))
organizations[org_idx].member_role.members.add(user)
users.append(user)
org_idx += 1
print('')
print('# Creating %d teams' % n_teams)
org_idx = 0
for n in spread(n_teams, n_organizations):
org = organizations[org_idx]
for i in range(n):
ids['team'] += 1
team_id = ids['team']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, org.name, i+ 1))
sys.stdout.flush()
team = Team.objects.create(name='%s Team %d Org %d' % (prefix, team_id, org_idx), organization=org)
teams.append(team)
org_idx += 1
print('')
print('# Adding users to teams')
for org in organizations:
org_teams = [t for t in org.teams.all()]
org_users = [u for u in org.member_role.members.all()]
print(' Spreading %d users accross %d teams for %s' % (len(org_users), len(org_teams), org.name))
# Our normal spread for most users
cur_user_idx = 0
cur_team_idx = 0
for n in spread(len(org_users), len(org_teams)):
team = org_teams[cur_team_idx]
for i in range(n):
if cur_user_idx < len(org_users):
user = org_users[cur_user_idx]
team.member_role.members.add(user)
cur_user_idx += 1
cur_team_idx += 1
# First user gets added to all teams
for team in org_teams:
team.member_role.members.add(org_users[0])
print('# Creating %d credentials for users' % (n_credentials - n_credentials // 2))
user_idx = 0
for n in spread(n_credentials - n_credentials // 2, n_users):
user = users[user_idx]
for i in range(n):
ids['credential'] += 1
sys.stdout.write('\r %d ' % (ids['credential']))
sys.stdout.flush()
credential_id = ids['credential']
credential = Credential.objects.create(name='%s Credential %d User %d' % (prefix, credential_id, user_idx))
credential.owner_role.members.add(user)
credentials.append(credential)
user_idx += 1
print('')
print('# Creating %d credentials for teams' % (n_credentials // 2))
team_idx = 0
starting_credential_id = ids['credential']
for n in spread(n_credentials - n_credentials // 2, n_teams):
team = teams[team_idx]
for i in range(n):
ids['credential'] += 1
sys.stdout.write('\r %d ' % (ids['credential'] - starting_credential_id))
sys.stdout.flush()
credential_id = ids['credential']
credential = Credential.objects.create(name='%s Credential %d team %d' % (prefix, credential_id, team_idx))
credential.owner_role.parents.add(team.member_role)
credentials.append(credential)
team_idx += 1
print('')
print('# Creating %d projects' % n_projects)
org_idx = 0
for n in spread(n_projects, n_organizations):
org = organizations[org_idx]
for i in range(n):
ids['project'] += 1
project_id = ids['project']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, org.name, i+ 1))
sys.stdout.flush()
project = Project.objects.create(name='%s Project %d Org %d' % (prefix, project_id, org_idx), organization=org)
projects.append(project)
org_idx += 1
print('')
print('# Creating %d inventories' % n_inventories)
org_idx = 0
for n in spread(n_inventories, min(n_inventories // 4 + 1, n_organizations)):
org = organizations[org_idx]
for i in range(n):
ids['inventory'] += 1
inventory_id = ids['inventory']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, org.name, i+ 1))
sys.stdout.flush()
inventory = Inventory.objects.create(name='%s Inventory %d Org %d' % (prefix, inventory_id, org_idx), organization=org)
inventories.append(inventory)
org_idx += 1
print('')
print('# Creating %d inventory_groups' % n_inventory_groups)
inv_idx = 0
for n in spread(n_inventory_groups, n_inventories):
inventory = inventories[inv_idx]
parent_list = [None] * 3
for i in range(n):
ids['group'] += 1
group_id = ids['group']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, inventory.name, i+ 1))
sys.stdout.flush()
group = Group.objects.create(
name='%s Group %d Inventory %d' % (prefix, group_id, inv_idx),
inventory=inventory,
)
# Have each group have up to 3 parent groups
for parent_n in range(3):
if i // 4 + parent_n < len(parent_list) and parent_list[i // 4 + parent_n]:
group.parents.add(parent_list[i // 4 + parent_n])
if parent_list[i // 4] is None:
parent_list[i // 4] = group
else:
parent_list.append(group)
inventory_groups.append(group)
inv_idx += 1
print('')
print('# Creating %d inventory_hosts' % n_inventory_hosts)
group_idx = 0
for n in spread(n_inventory_hosts, n_inventory_groups):
group = inventory_groups[group_idx]
for i in range(n):
ids['host'] += 1
host_id = ids['host']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, group.name, i+ 1))
sys.stdout.flush()
host = Host.objects.create(name='%s Host %d Group %d' % (prefix, host_id, group_idx), inventory=group.inventory)
# Add the host to up to 3 groups
host.groups.add(group)
for m in range(2):
if group_idx + m < len(inventory_groups) and group.inventory.id == inventory_groups[group_idx + m].inventory.id:
host.groups.add(inventory_groups[group_idx + m])
inventory_hosts.append(host)
group_idx += 1
print('')
print('# Creating %d job_templates' % n_job_templates)
project_idx = 0
inv_idx = 0
for n in spread(n_job_templates, n_projects):
project = projects[project_idx]
for i in range(n):
ids['job_template'] += 1
job_template_id = ids['job_template']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, project.name, i+ 1))
sys.stdout.flush()
inventory = None
org_inv_count = project.organization.inventories.count()
if org_inv_count > 0:
inventory = project.organization.inventories.all()[inv_idx % org_inv_count]
job_template = JobTemplate.objects.create(
name='%s Job Template %d Project %d' % (prefix, job_template_id, project_idx),
inventory=inventory,
project=project,
)
job_templates.append(job_template)
inv_idx += 1
project_idx += 1
print('')
print('# Creating %d jobs' % n_jobs)
group_idx = 0
job_template_idx = 0
for n in spread(n_jobs, n_job_templates):
job_template = job_templates[job_template_idx]
for i in range(n):
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, job_template.name, i+ 1))
sys.stdout.flush()
job = Job.objects.create(job_template=job_template)
jobs.append(job)
if job_template.inventory:
inv_groups = [g for g in job_template.inventory.groups.all()]
if len(inv_groups):
JobHostSummary.objects.bulk_create([
JobHostSummary(
job=job, host=h, host_name=h.name, processed=1,
created=now(), modified=now()
)
for h in inv_groups[group_idx % len(inv_groups)].hosts.all()[:100]
])
group_idx += 1
job_template_idx += 1
if n:
print('')
print('# Creating %d job events' % n_job_events)
job_idx = 0
for n in spread(n_job_events, n_jobs):
job = jobs[job_idx]
sys.stdout.write('\r Creating %d job events for job %d' % (n, job.id))
sys.stdout.flush()
JobEvent.objects.bulk_create([
JobEvent(
created=now(),
modified=now(),
job=job,
event='runner_on_ok'
)
for i in range(n)
])
job_idx += 1
if n:
print('')
if options['pretend']:
raise Rollback()
except Rollback:
print('Rolled back changes')
pass
return
@@ -780,10 +780,10 @@ class Command(NoArgsCommand):
if settings.SQL_DEBUG: if settings.SQL_DEBUG:
queries_before = len(connection.queries) queries_before = len(connection.queries)
if self.inventory_source.group: if self.inventory_source.group:
groups_qs = self.inventory_source.group.all_children groups_qs = self.inventory_source.group.all_children.all()
# FIXME: Also include groups from inventory_source.managed_groups? # FIXME: Also include groups from inventory_source.managed_groups?
else: else:
groups_qs = self.inventory.groups groups_qs = self.inventory.groups.all()
# Build list of all group pks, remove those that should not be deleted. # Build list of all group pks, remove those that should not be deleted.
del_group_pks = set(groups_qs.values_list('pk', flat=True)) del_group_pks = set(groups_qs.values_list('pk', flat=True))
all_group_names = self.all_group.all_groups.keys() all_group_names = self.all_group.all_groups.keys()
@@ -1273,42 +1273,43 @@ class Command(NoArgsCommand):
self.is_custom) self.is_custom)
self.all_group.debug_tree() self.all_group.debug_tree()
# Ensure that this is managed as an atomic SQL transaction, with batch_role_ancestor_rebuilding():
# and thus properly rolled back if there is an issue. # Ensure that this is managed as an atomic SQL transaction,
with transaction.atomic(): # and thus properly rolled back if there is an issue.
# Merge/overwrite inventory into database. with transaction.atomic():
if settings.SQL_DEBUG: # Merge/overwrite inventory into database.
self.logger.warning('loading into database...') if settings.SQL_DEBUG:
with ignore_inventory_computed_fields(): self.logger.warning('loading into database...')
if getattr(settings, 'ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC', True): with ignore_inventory_computed_fields():
self.load_into_database() if getattr(settings, 'ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC', True):
else:
with disable_activity_stream():
self.load_into_database() self.load_into_database()
if settings.SQL_DEBUG: else:
queries_before2 = len(connection.queries) with disable_activity_stream():
self.inventory.update_computed_fields() self.load_into_database()
if settings.SQL_DEBUG: if settings.SQL_DEBUG:
self.logger.warning('update computed fields took %d queries', queries_before2 = len(connection.queries)
len(connection.queries) - queries_before2) self.inventory.update_computed_fields()
try: if settings.SQL_DEBUG:
self.check_license() self.logger.warning('update computed fields took %d queries',
except CommandError as e: len(connection.queries) - queries_before2)
self.mark_license_failure(save=True) try:
raise e self.check_license()
except CommandError as e:
self.mark_license_failure(save=True)
raise e
if self.inventory_source.group: if self.inventory_source.group:
inv_name = 'group "%s"' % (self.inventory_source.group.name) inv_name = 'group "%s"' % (self.inventory_source.group.name)
else: else:
inv_name = '"%s" (id=%s)' % (self.inventory.name, inv_name = '"%s" (id=%s)' % (self.inventory.name,
self.inventory.id) self.inventory.id)
if settings.SQL_DEBUG: if settings.SQL_DEBUG:
self.logger.warning('Inventory import completed for %s in %0.1fs', self.logger.warning('Inventory import completed for %s in %0.1fs',
inv_name, time.time() - begin) inv_name, time.time() - begin)
else: else:
self.logger.info('Inventory import completed for %s in %0.1fs', self.logger.info('Inventory import completed for %s in %0.1fs',
inv_name, time.time() - begin) inv_name, time.time() - begin)
status = 'successful' status = 'successful'
# If we're in debug mode, then log the queries and time # If we're in debug mode, then log the queries and time
# used to do the operation. # used to do the operation.
+161 -63
View File
@@ -48,6 +48,19 @@ class Migration(migrations.Migration):
field=models.ManyToManyField(related_name='deprecated_teams', to='main.Project', blank=True), field=models.ManyToManyField(related_name='deprecated_teams', to='main.Project', blank=True),
), ),
migrations.CreateModel(
name='RoleAncestorEntry',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('role_field', models.TextField()),
('content_type_id', models.PositiveIntegerField(null=False)),
('object_id', models.PositiveIntegerField(null=False)),
],
options={
'db_table': 'main_rbac_role_ancestors',
'verbose_name_plural': 'role_ancestors',
},
),
migrations.CreateModel( migrations.CreateModel(
name='Role', name='Role',
fields=[ fields=[
@@ -58,7 +71,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=512)), ('name', models.CharField(max_length=512)),
('singleton_name', models.TextField(default=None, unique=True, null=True, db_index=True)), ('singleton_name', models.TextField(default=None, unique=True, null=True, db_index=True)),
('object_id', models.PositiveIntegerField(default=None, null=True)), ('object_id', models.PositiveIntegerField(default=None, null=True)),
('ancestors', models.ManyToManyField(related_name='descendents', to='main.Role')), ('ancestors', models.ManyToManyField(related_name='descendents', through='main.RoleAncestorEntry', to='main.Role')),
('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)), ('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)),
('created_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), ('created_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)), ('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)),
@@ -72,177 +85,262 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'roles', 'verbose_name_plural': 'roles',
}, },
), ),
migrations.CreateModel( migrations.AddField(
name='RolePermission', model_name='roleancestorentry',
fields=[ name='ancestor',
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), field=models.ForeignKey(related_name='+', to='main.Role'),
('created', models.DateTimeField(default=None, editable=False)), ),
('modified', models.DateTimeField(default=None, editable=False)), migrations.AddField(
('auto_generated', models.BooleanField(default=False)), model_name='roleancestorentry',
('object_id', models.PositiveIntegerField(default=None)), name='descendent',
('create', models.IntegerField(default=0)), field=models.ForeignKey(related_name='+', to='main.Role'),
('read', models.IntegerField(default=0)), ),
('write', models.IntegerField(default=0)), migrations.AlterIndexTogether(
('update', models.IntegerField(default=0)), name='roleancestorentry',
('delete', models.IntegerField(default=0)), index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field')]),
('execute', models.IntegerField(default=0)),
('scm_update', models.IntegerField(default=0)),
('use', models.IntegerField(default=0)),
('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType')),
('role', models.ForeignKey(related_name='permissions', to='main.Role')),
],
options={
'db_table': 'main_rbac_permissions',
'verbose_name_plural': 'permissions',
},
), ),
migrations.AddField( migrations.AddField(
model_name='credential', model_name='credential',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Auditor of the credential', parent_role=[b'singleton:System Auditor'], to='main.Role', role_name=b'Credential Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Auditor of the credential', parent_role=[b'singleton:System Auditor'], to='main.Role', role_name=b'Credential Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='credential', model_name='credential',
name='owner_role', name='owner_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Owner of the credential', parent_role=[b'singleton:System Administrator'], to='main.Role', role_name=b'Credential Owner', null=b'True', permissions={b'all': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Owner of the credential', parent_role=[b'singleton:System Administrator'], to='main.Role', role_name=b'Credential Owner', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='credential', model_name='credential',
name='usage_role', name='use_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Credential User', null=b'True', permissions={b'use': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Credential User', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='custominventoryscript', model_name='custominventoryscript',
name='admin_role', name='admin_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'CustomInventory Administrator', null=b'True', permissions={b'all': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'CustomInventory Administrator', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='custominventoryscript', model_name='custominventoryscript',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'CustomInventory Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'CustomInventory Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='custominventoryscript', model_name='custominventoryscript',
name='member_role', name='member_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.member_role', to='main.Role', role_name=b'CustomInventory Member', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.member_role', to='main.Role', role_name=b'CustomInventory Member', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='group', model_name='group',
name='admin_role', name='admin_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', role_name=b'Inventory Group Administrator', null=b'True', permissions={b'all': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', role_name=b'Inventory Group Administrator', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='group', model_name='group',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.auditor_role', b'parents.auditor_role'], to='main.Role', role_name=b'Inventory Group Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.auditor_role', b'parents.auditor_role'], to='main.Role', role_name=b'Inventory Group Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='group', model_name='group',
name='executor_role', name='execute_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.executor_role', b'parents.executor_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True', permissions={b'read': True, b'execute': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.execute_role', b'parents.executor_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='group', model_name='group',
name='updater_role', name='update_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.updater_role', b'parents.updater_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True', permissions={b'read': True, b'write': True, b'create': True, b'use': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.update_role', b'parents.updater_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='inventory', model_name='inventory',
name='admin_role', name='admin_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Inventory Administrator', null=b'True', permissions={b'all': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Inventory Administrator', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='inventory', model_name='inventory',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Inventory Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Inventory Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='inventory', model_name='inventory',
name='executor_role', name='execute_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=None, to='main.Role', role_name=b'Inventory Executor', null=b'True', permissions={b'read': True, b'execute': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=None, to='main.Role', role_name=b'Inventory Executor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='inventory', model_name='inventory',
name='updater_role', name='update_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=None, to='main.Role', role_name=b'Inventory Updater', null=b'True', permissions={b'read': True, b'update': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=None, to='main.Role', role_name=b'Inventory Updater', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='inventory', model_name='inventory',
name='usage_role', name='use_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Inventory User', null=b'True', permissions={b'use': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Inventory User', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='jobtemplate', model_name='jobtemplate',
name='admin_role', name='admin_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Full access to all settings', parent_role=b'project.admin_role', to='main.Role', role_name=b'Job Template Administrator', null=b'True', permissions={b'all': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Full access to all settings', parent_role=b'project.admin_role', to='main.Role', role_name=b'Job Template Administrator', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='jobtemplate', model_name='jobtemplate',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read-only access to all settings', parent_role=b'project.auditor_role', to='main.Role', role_name=b'Job Template Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read-only access to all settings', parent_role=b'project.auditor_role', to='main.Role', role_name=b'Job Template Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='jobtemplate', model_name='jobtemplate',
name='executor_role', name='execute_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=None, to='main.Role', role_name=b'Job Template Runner', null=b'True', permissions={b'read': True, b'execute': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=None, to='main.Role', role_name=b'Job Template Runner', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='organization', model_name='organization',
name='admin_role', name='admin_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage all aspects of this organization', parent_role=b'singleton:System Administrator', to='main.Role', role_name=b'Organization Administrator', null=b'True', permissions={b'write': True, b'use': True, b'scm_update': True, b'execute': True, b'read': True, b'create': True, b'update': True, b'delete': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage all aspects of this organization', parent_role=b'singleton:System Administrator', to='main.Role', role_name=b'Organization Administrator', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='organization', model_name='organization',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this organization', parent_role=b'singleton:System Auditor', to='main.Role', role_name=b'Organization Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this organization', parent_role=b'singleton:System Auditor', to='main.Role', role_name=b'Organization Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='organization', model_name='organization',
name='member_role', name='member_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this organization', parent_role=b'admin_role', to='main.Role', role_name=b'Organization Member', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this organization', parent_role=b'admin_role', to='main.Role', role_name=b'Organization Member', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='project', model_name='project',
name='admin_role', name='admin_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this project', parent_role=[b'organization.admin_role', b'singleton:System Administrator'], to='main.Role', role_name=b'Project Administrator', null=b'True', permissions={b'all': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this project', parent_role=[b'organization.admin_role', b'singleton:System Administrator'], to='main.Role', role_name=b'Project Administrator', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='project', model_name='project',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this project', parent_role=[b'organization.auditor_role', b'singleton:System Auditor'], to='main.Role', role_name=b'Project Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this project', parent_role=[b'organization.auditor_role', b'singleton:System Auditor'], to='main.Role', role_name=b'Project Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='project', model_name='project',
name='member_role', name='member_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=None, to='main.Role', role_name=b'Project Member', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=None, to='main.Role', role_name=b'Project Member', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='project', model_name='project',
name='scm_update_role', name='scm_update_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update this project from the source control management system', parent_role=b'admin_role', to='main.Role', role_name=b'Project Updater', null=b'True', permissions={b'scm_update': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update this project from the source control management system', parent_role=b'admin_role', to='main.Role', role_name=b'Project Updater', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='team', model_name='team',
name='admin_role', name='admin_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this team', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Team Administrator', null=b'True', permissions={b'write': True, b'use': True, b'scm_update': True, b'execute': True, b'read': True, b'create': True, b'update': True, b'delete': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this team', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Team Administrator', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='team', model_name='team',
name='auditor_role', name='auditor_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this team', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Team Auditor', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this team', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Team Auditor', null=b'True'),
), ),
migrations.AddField( migrations.AddField(
model_name='team', model_name='team',
name='member_role', name='member_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this team', parent_role=b'admin_role', to='main.Role', role_name=b'Team Member', null=b'True', permissions={b'read': True}), field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this team', to='main.Role', role_name=b'Team Member', null=b'True'),
),
migrations.AddField(
model_name='credential',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read this credential', parent_role=[b'use_role', b'auditor_role', b'owner_role'], to='main.Role', role_name=b'Credential REad', null=b'True'),
),
migrations.AddField(
model_name='custominventoryscript',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=[b'auditor_role', b'member_role', b'admin_role'], to='main.Role', role_name=b'CustomInventory Read', null=b'True'),
),
migrations.AddField(
model_name='group',
name='adhoc_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute ad hoc commands against this inventory', parent_role=[b'inventory.adhoc_role', b'parents.adhoc_role', b'admin_role'], to='main.Role', role_name=b'Inventory Ad Hoc', null=b'True'),
),
migrations.AddField(
model_name='group',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'execute_role', b'update_role', b'auditor_role', b'admin_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'),
),
migrations.AddField(
model_name='inventory',
name='adhoc_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute ad hoc commands against this inventory', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory Ad Hoc', null=b'True'),
),
migrations.AddField(
model_name='inventory',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view this inventory', parent_role=[b'auditor_role', b'execute_role', b'update_role', b'use_role', b'admin_role'], to='main.Role', role_name=b'Read', null=b'True'),
),
migrations.AddField(
model_name='jobtemplate',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=[b'execute_role', b'auditor_role', b'admin_role'], to='main.Role', role_name=b'Job Template Runner', null=b'True'),
),
migrations.AddField(
model_name='organization',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read an organization', parent_role=[b'member_role', b'auditor_role'], to='main.Role', role_name=b'Organization Read Access', null=b'True'),
),
migrations.AddField(
model_name='project',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read access to this project', parent_role=[b'member_role', b'auditor_role', b'scm_update_role'], to='main.Role', role_name=b'Project Read Access', null=b'True'),
),
migrations.AddField(
model_name='role',
name='role_field',
field=models.TextField(default=b''),
),
migrations.AddField(
model_name='team',
name='read_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Can view this team', parent_role=[b'admin_role', b'auditor_role', b'member_role'], to='main.Role', role_name=b'Read', null=b'True'),
),
migrations.AlterField(
model_name='credential',
name='use_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=[b'owner_role'], to='main.Role', role_name=b'Credential User', null=b'True'),
),
migrations.AlterField(
model_name='group',
name='execute_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.execute_role', b'parents.execute_role', b'adhoc_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'),
),
migrations.AlterField(
model_name='group',
name='update_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.update_role', b'parents.update_role', b'admin_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True'),
),
migrations.AlterField(
model_name='inventory',
name='execute_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=b'adhoc_role', to='main.Role', role_name=b'Inventory Executor', null=b'True'),
),
migrations.AlterField(
model_name='inventory',
name='update_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory Updater', null=b'True'),
),
migrations.AlterField(
model_name='inventory',
name='use_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory User', null=b'True'),
),
migrations.AlterField(
model_name='jobtemplate',
name='execute_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=[b'admin_role'], to='main.Role', role_name=b'Job Template Runner', null=b'True'),
),
migrations.AlterField(
model_name='project',
name='member_role',
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=b'admin_role', to='main.Role', role_name=b'Project Member', null=b'True'),
), ),
migrations.AlterIndexTogether(
name='rolepermission',
index_together=set([('content_type', 'object_id')]),
),
migrations.RenameField( migrations.RenameField(
model_name='organization', model_name='organization',
old_name='projects', old_name='projects',
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0017_v300_prompting_migrations'),
]
operations = [
migrations.AlterModelOptions(
name='host',
options={'ordering': ('name',)},
),
]
+9 -19
View File
@@ -34,7 +34,6 @@ def init_rbac_migration(apps, schema_editor):
def migrate_users(apps, schema_editor): def migrate_users(apps, schema_editor):
User = apps.get_model('auth', "User") User = apps.get_model('auth', "User")
Role = apps.get_model('main', "Role") Role = apps.get_model('main', "Role")
RolePermission = apps.get_model('main', "RolePermission")
ContentType = apps.get_model('contenttypes', "ContentType") ContentType = apps.get_model('contenttypes', "ContentType")
user_content_type = ContentType.objects.get_for_model(User) user_content_type = ContentType.objects.get_for_model(User)
@@ -52,15 +51,6 @@ def migrate_users(apps, schema_editor):
object_id = user.id object_id = user.id
) )
role.members.add(user) role.members.add(user)
RolePermission.objects.create(
created=now(),
modified=now(),
role = role,
content_type = user_content_type,
object_id = user.id,
create=1, read=1, write=1, delete=1, update=1,
execute=1, scm_update=1, use=1,
)
logger.info(smart_text(u"migrating to new role for user: {}".format(user.username))) logger.info(smart_text(u"migrating to new role for user: {}".format(user.username)))
if user.is_superuser: if user.is_superuser:
@@ -113,7 +103,7 @@ def attrfunc(attr_path):
def _update_credential_parents(org, cred): def _update_credential_parents(org, cred):
org.admin_role.children.add(cred.owner_role) org.admin_role.children.add(cred.owner_role)
org.member_role.children.add(cred.usage_role) org.member_role.children.add(cred.use_role)
cred.deprecated_user, cred.deprecated_team = None, None cred.deprecated_user, cred.deprecated_team = None, None
cred.save() cred.save()
@@ -147,7 +137,7 @@ def _discover_credentials(instances, cred, orgfunc):
# Unlink the old information from the new credential # Unlink the old information from the new credential
cred.deprecated_user, cred.deprecated_team = None, None cred.deprecated_user, cred.deprecated_team = None, None
cred.owner_role, cred.usage_role = None, None cred.owner_role, cred.use_role = None, None
cred.save() cred.save()
for i in orgs[org]: for i in orgs[org]:
@@ -189,7 +179,7 @@ def migrate_credential(apps, schema_editor):
if cred.deprecated_team is not None: if cred.deprecated_team is not None:
cred.deprecated_team.admin_role.children.add(cred.owner_role) cred.deprecated_team.admin_role.children.add(cred.owner_role)
cred.deprecated_team.member_role.children.add(cred.usage_role) cred.deprecated_team.member_role.children.add(cred.use_role)
cred.deprecated_user, cred.deprecated_team = None, None cred.deprecated_user, cred.deprecated_team = None, None
cred.save() cred.save()
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host))) logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host)))
@@ -214,7 +204,7 @@ def migrate_inventory(apps, schema_editor):
elif perm.permission_type == 'read': elif perm.permission_type == 'read':
return inventory.auditor_role return inventory.auditor_role
elif perm.permission_type == 'write': elif perm.permission_type == 'write':
return inventory.updater_role return inventory.update_role
elif perm.permission_type == 'check' or perm.permission_type == 'run': elif perm.permission_type == 'check' or perm.permission_type == 'run':
# These permission types are handled differntly in RBAC now, nothing to migrate. # These permission types are handled differntly in RBAC now, nothing to migrate.
return False return False
@@ -232,7 +222,7 @@ def migrate_inventory(apps, schema_editor):
raise Exception(smart_text(u'Unhandled permission type for inventory: {}'.format( perm.permission_type))) raise Exception(smart_text(u'Unhandled permission type for inventory: {}'.format( perm.permission_type)))
if perm.run_ad_hoc_commands: if perm.run_ad_hoc_commands:
execrole = inventory.executor_role execrole = inventory.execute_role
if perm.team: if perm.team:
if role: if role:
@@ -392,20 +382,20 @@ def migrate_job_templates(apps, schema_editor):
for team in Team.objects.iterator(): for team in Team.objects.iterator():
if permission.filter(team=team).exists(): if permission.filter(team=team).exists():
team.member_role.children.add(jt.executor_role) team.member_role.children.add(jt.execute_role)
logger.info(smart_text(u'adding Team({}) access to JobTemplate({})'.format(team.name, jt.name))) logger.info(smart_text(u'adding Team({}) access to JobTemplate({})'.format(team.name, jt.name)))
for user in User.objects.iterator(): for user in User.objects.iterator():
if permission.filter(user=user).exists(): if permission.filter(user=user).exists():
jt.executor_role.members.add(user) jt.execute_role.members.add(user)
logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name))) logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name)))
if jt.accessible_by(user, {'execute': True}): if user in jt.execute_role:
# If the job template is already accessible by the user, because they # If the job template is already accessible by the user, because they
# are a sytem, organization, or project admin, then don't add an explicit # are a sytem, organization, or project admin, then don't add an explicit
# role entry for them # role entry for them
continue continue
if old_access.check_user_access(user, jt.__class__, 'start', jt, False): if old_access.check_user_access(user, jt.__class__, 'start', jt, False):
jt.executor_role.members.add(user) jt.execute_role.members.add(user)
logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name))) logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name)))
-3
View File
@@ -3,7 +3,6 @@
# Django # Django
from django.conf import settings # noqa from django.conf import settings # noqa
from django.contrib.contenttypes.fields import GenericRelation
# AWX # AWX
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
@@ -43,10 +42,8 @@ from awx.main.access import * # noqa
User.add_to_class('get_queryset', get_user_queryset) User.add_to_class('get_queryset', get_user_queryset)
User.add_to_class('can_access', check_user_access) User.add_to_class('can_access', check_user_access)
User.add_to_class('accessible_by', user_accessible_by)
User.add_to_class('accessible_objects', user_accessible_objects) User.add_to_class('accessible_objects', user_accessible_objects)
User.add_to_class('admin_role', user_admin_role) User.add_to_class('admin_role', user_admin_role)
User.add_to_class('role_permissions', GenericRelation('main.RolePermission'))
@property @property
def user_get_organizations(user): def user_get_organizations(user):
+9 -4
View File
@@ -174,7 +174,6 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
parent_role=[ parent_role=[
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, 'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
], ],
permissions = {'all': True}
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='Credential Auditor', role_name='Credential Auditor',
@@ -182,12 +181,18 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
parent_role=[ parent_role=[
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, 'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
], ],
permissions = {'read': True}
) )
usage_role = ImplicitRoleField( use_role = ImplicitRoleField(
role_name='Credential User', role_name='Credential User',
role_description='May use this credential, but not read sensitive portions or modify it', role_description='May use this credential, but not read sensitive portions or modify it',
permissions = {'use': True} parent_role=['owner_role']
)
read_role = ImplicitRoleField(
role_name='Credential REad',
role_description='May read this credential',
parent_role=[
'use_role', 'auditor_role', 'owner_role'
],
) )
@property @property
+35 -20
View File
@@ -100,28 +100,36 @@ class Inventory(CommonModel, ResourceMixin):
role_name='Inventory Administrator', role_name='Inventory Administrator',
role_description='May manage this inventory', role_description='May manage this inventory',
parent_role='organization.admin_role', parent_role='organization.admin_role',
permissions = {'all': True}
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='Inventory Auditor', role_name='Inventory Auditor',
role_description='May view but not modify this inventory', role_description='May view but not modify this inventory',
parent_role='organization.auditor_role', parent_role='organization.auditor_role',
permissions = {'read': True}
) )
updater_role = ImplicitRoleField( update_role = ImplicitRoleField(
role_name='Inventory Updater', role_name='Inventory Updater',
role_description='May update the inventory', role_description='May update the inventory',
permissions = {'read': True, 'update': True} parent_role=['admin_role'],
) )
usage_role = ImplicitRoleField( use_role = ImplicitRoleField(
role_name='Inventory User', role_name='Inventory User',
role_description='May use this inventory, but not read sensitive portions or modify it', role_description='May use this inventory, but not read sensitive portions or modify it',
permissions = {'use': True} parent_role=['admin_role'],
) )
executor_role = ImplicitRoleField( adhoc_role = ImplicitRoleField(
role_name='Inventory Ad Hoc',
role_description='May execute ad hoc commands against this inventory',
parent_role=['admin_role'],
)
execute_role = ImplicitRoleField(
role_name='Inventory Executor', role_name='Inventory Executor',
role_description='May execute jobs against this inventory', role_description='May execute jobs against this inventory',
permissions = {'read': True, 'execute': True} parent_role='adhoc_role',
)
read_role = ImplicitRoleField(
role_name='Read',
parent_role=['auditor_role', 'execute_role', 'update_role', 'use_role', 'admin_role'],
role_description='May view this inventory',
) )
def get_absolute_url(self): def get_absolute_url(self):
@@ -335,7 +343,7 @@ class Host(CommonModelNameNotUnique, ResourceMixin):
class Meta: class Meta:
app_label = 'main' app_label = 'main'
unique_together = (("name", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration. unique_together = (("name", "inventory"),) # FIXME: Add ('instance_id', 'inventory') after migration.
ordering = ('inventory', 'name') ordering = ('name',)
inventory = models.ForeignKey( inventory = models.ForeignKey(
'Inventory', 'Inventory',
@@ -525,22 +533,27 @@ class Group(CommonModelNameNotUnique, ResourceMixin):
admin_role = ImplicitRoleField( admin_role = ImplicitRoleField(
role_name='Inventory Group Administrator', role_name='Inventory Group Administrator',
parent_role=['inventory.admin_role', 'parents.admin_role'], parent_role=['inventory.admin_role', 'parents.admin_role'],
permissions = {'all': True}
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='Inventory Group Auditor', role_name='Inventory Group Auditor',
parent_role=['inventory.auditor_role', 'parents.auditor_role'], parent_role=['inventory.auditor_role', 'parents.auditor_role'],
permissions = {'read': True}
) )
updater_role = ImplicitRoleField( update_role = ImplicitRoleField(
role_name='Inventory Group Updater', role_name='Inventory Group Updater',
parent_role=['inventory.updater_role', 'parents.updater_role'], parent_role=['inventory.update_role', 'parents.update_role', 'admin_role'],
permissions = {'read': True, 'write': True, 'create': True, 'use': True},
) )
executor_role = ImplicitRoleField( adhoc_role = ImplicitRoleField(
role_name='Inventory Ad Hoc',
parent_role=['inventory.adhoc_role', 'parents.adhoc_role', 'admin_role'],
role_description='May execute ad hoc commands against this inventory',
)
execute_role = ImplicitRoleField(
role_name='Inventory Group Executor', role_name='Inventory Group Executor',
parent_role=['inventory.executor_role', 'parents.executor_role'], parent_role=['inventory.execute_role', 'parents.execute_role', 'adhoc_role'],
permissions = {'read':True, 'execute':True}, )
read_role = ImplicitRoleField(
role_name='Inventory Group Executor',
parent_role=['execute_role', 'update_role', 'auditor_role', 'admin_role'],
) )
def __unicode__(self): def __unicode__(self):
@@ -1296,21 +1309,23 @@ class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):
role_name='CustomInventory Administrator', role_name='CustomInventory Administrator',
role_description='May manage this inventory', role_description='May manage this inventory',
parent_role='organization.admin_role', parent_role='organization.admin_role',
permissions = {'all': True}
) )
member_role = ImplicitRoleField( member_role = ImplicitRoleField(
role_name='CustomInventory Member', role_name='CustomInventory Member',
role_description='May view but not modify this inventory', role_description='May view but not modify this inventory',
parent_role='organization.member_role', parent_role='organization.member_role',
permissions = {'read': True}
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='CustomInventory Auditor', role_name='CustomInventory Auditor',
role_description='May view but not modify this inventory', role_description='May view but not modify this inventory',
parent_role='organization.auditor_role', parent_role='organization.auditor_role',
permissions = {'read': True} )
read_role = ImplicitRoleField(
role_name='CustomInventory Read',
role_description='May view but not modify this inventory',
parent_role=['auditor_role', 'member_role', 'admin_role'],
) )
def get_absolute_url(self): def get_absolute_url(self):
+7 -4
View File
@@ -229,18 +229,21 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
role_name='Job Template Administrator', role_name='Job Template Administrator',
role_description='Full access to all settings', role_description='Full access to all settings',
parent_role='project.admin_role', parent_role='project.admin_role',
permissions = {'all': True}
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='Job Template Auditor', role_name='Job Template Auditor',
role_description='Read-only access to all settings', role_description='Read-only access to all settings',
parent_role='project.auditor_role', parent_role='project.auditor_role',
permissions = {'read': True}
) )
executor_role = ImplicitRoleField( execute_role = ImplicitRoleField(
role_name='Job Template Runner', role_name='Job Template Runner',
role_description='May run the job template', role_description='May run the job template',
permissions = {'read': True, 'execute': True} parent_role=['admin_role'],
)
read_role = ImplicitRoleField(
role_name='Job Template Runner',
role_description='May run the job template',
parent_role=['execute_role', 'auditor_role', 'admin_role'],
) )
@classmethod @classmethod
+21 -66
View File
@@ -1,15 +1,11 @@
# Django # Django
from django.db import models from django.db import models
from django.db.models.aggregates import Max
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User # noqa from django.contrib.auth.models import User # noqa
# AWX # AWX
from awx.main.models.rbac import ( from awx.main.models.rbac import (
get_user_permissions_on_resource, Role, RoleAncestorEntry, get_roles_on_resource
get_role_permissions_on_resource,
Role,
) )
@@ -20,88 +16,47 @@ class ResourceMixin(models.Model):
class Meta: class Meta:
abstract = True abstract = True
role_permissions = GenericRelation('main.RolePermission')
@classmethod @classmethod
def accessible_objects(cls, accessor, permissions): def accessible_objects(cls, accessor, role_field):
''' '''
Use instead of `MyModel.objects` when you want to only consider Use instead of `MyModel.objects` when you want to only consider
resources that a user has specific permissions for. For example: resources that a user has specific permissions for. For example:
MyModel.accessible_objects(user, {'read': True}).filter(name__istartswith='bar'); MyModel.accessible_objects(user, 'read_role').filter(name__istartswith='bar');
NOTE: This should only be used for list type things. If you have a NOTE: This should only be used for list type things. If you have a
specific resource you want to check permissions on, it is more specific resource you want to check permissions on, it is more
performant to resolve the resource in question then call performant to resolve the resource in question then call
`myresource.get_permissions(user)`. `myresource.get_permissions(user)`.
''' '''
return ResourceMixin._accessible_objects(cls, accessor, permissions) return ResourceMixin._accessible_objects(cls, accessor, role_field)
@staticmethod @staticmethod
def _accessible_objects(cls, accessor, permissions): def _accessible_objects(cls, accessor, role_field):
if type(accessor) == User: if type(accessor) == User:
qs = cls.objects.filter( ancestor_roles = accessor.roles.all()
role_permissions__role__ancestors__members=accessor
)
elif type(accessor) == Role: elif type(accessor) == Role:
qs = cls.objects.filter( ancestor_roles = [accessor]
role_permissions__role__ancestors=accessor
)
else: else:
accessor_type = ContentType.objects.get_for_model(accessor) accessor_type = ContentType.objects.get_for_model(accessor)
roles = Role.objects.filter(content_type__pk=accessor_type.id, ancestor_roles = Role.objects.filter(content_type__pk=accessor_type.id,
object_id=accessor.id) object_id=accessor.id)
qs = cls.objects.filter( qs = cls.objects.filter(pk__in =
role_permissions__role__ancestors__in=roles RoleAncestorEntry.objects.filter(
) ancestor__in=ancestor_roles,
content_type_id = ContentType.objects.get_for_model(cls).id,
for perm in permissions: role_field = role_field
qs = qs.annotate(**{'max_' + perm: Max('role_permissions__' + perm)}) ).values_list('object_id').distinct()
qs = qs.filter(**{'max_' + perm: int(permissions[perm])}) )
#return cls.objects.filter(resource__in=qs)
return qs return qs
def get_permissions(self, user): def get_permissions(self, accessor):
''' '''
Returns a dict (or None) of the permissions a user has for a given Returns a dict (or None) of the roles a accessor has for a given resource.
resource. An accessor can be either a User, Role, or an arbitrary resource that
contains one or more Roles associated with it.
Note: Each field in the dict is the `or` of all respective permissions
that have been granted to the roles that are applicable for the given
user.
In example, if a user has been granted read access through a permission
on one role and write access through a permission on a separate role,
the returned dict will denote that the user has both read and write
access.
''' '''
return get_user_permissions_on_resource(self, user) return get_roles_on_resource(self, accessor)
def get_role_permissions(self, role):
'''
Returns a dict (or None) of the permissions a role has for a given
resource.
Note: Each field in the dict is the `or` of all respective permissions
that have been granted to either the role or any descendents of that role.
'''
return get_role_permissions_on_resource(self, role)
def accessible_by(self, user, permissions):
'''
Returns true if the user has all of the specified permissions
'''
perms = self.get_permissions(user)
if perms is None:
return False
for k in permissions:
if k not in perms or perms[k] < permissions[k]:
return False
return True
+10 -8
View File
@@ -19,7 +19,6 @@ from django.utils.translation import ugettext_lazy as _
from awx.main.fields import AutoOneToOneField, ImplicitRoleField from awx.main.fields import AutoOneToOneField, ImplicitRoleField
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
from awx.main.models.rbac import ( from awx.main.models.rbac import (
ALL_PERMISSIONS,
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
ROLE_SINGLETON_SYSTEM_AUDITOR, ROLE_SINGLETON_SYSTEM_AUDITOR,
) )
@@ -57,19 +56,21 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
role_name='Organization Administrator', role_name='Organization Administrator',
role_description='May manage all aspects of this organization', role_description='May manage all aspects of this organization',
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
permissions = ALL_PERMISSIONS,
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='Organization Auditor', role_name='Organization Auditor',
role_description='May read all settings associated with this organization', role_description='May read all settings associated with this organization',
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
permissions = {'read': True}
) )
member_role = ImplicitRoleField( member_role = ImplicitRoleField(
role_name='Organization Member', role_name='Organization Member',
role_description='A member of this organization', role_description='A member of this organization',
parent_role='admin_role', parent_role='admin_role',
permissions = {'read': True} )
read_role = ImplicitRoleField(
role_name='Organization Read Access',
role_description='Read an organization',
parent_role=['member_role', 'auditor_role'],
) )
@@ -112,19 +113,20 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
role_name='Team Administrator', role_name='Team Administrator',
role_description='May manage this team', role_description='May manage this team',
parent_role='organization.admin_role', parent_role='organization.admin_role',
permissions = ALL_PERMISSIONS,
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='Team Auditor', role_name='Team Auditor',
role_description='May read all settings associated with this team', role_description='May read all settings associated with this team',
parent_role='organization.auditor_role', parent_role='organization.auditor_role',
permissions = {'read': True}
) )
member_role = ImplicitRoleField( member_role = ImplicitRoleField(
role_name='Team Member', role_name='Team Member',
role_description='A member of this team', role_description='A member of this team',
parent_role='admin_role', )
permissions = {'read':True}, read_role = ImplicitRoleField(
role_name='Read',
role_description='Can view this team',
parent_role=['admin_role', 'auditor_role', 'member_role'],
) )
def get_absolute_url(self): def get_absolute_url(self):
+6 -4
View File
@@ -227,7 +227,6 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
'organization.admin_role', 'organization.admin_role',
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, 'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
], ],
permissions = {'all': True}
) )
auditor_role = ImplicitRoleField( auditor_role = ImplicitRoleField(
role_name='Project Auditor', role_name='Project Auditor',
@@ -236,18 +235,21 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
'organization.auditor_role', 'organization.auditor_role',
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, 'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
], ],
permissions = {'read': True}
) )
member_role = ImplicitRoleField( member_role = ImplicitRoleField(
role_name='Project Member', role_name='Project Member',
role_description='Implies membership within this project', role_description='Implies membership within this project',
permissions = {'read': True} parent_role='admin_role',
) )
scm_update_role = ImplicitRoleField( scm_update_role = ImplicitRoleField(
role_name='Project Updater', role_name='Project Updater',
role_description='May update this project from the source control management system', role_description='May update this project from the source control management system',
parent_role='admin_role', parent_role='admin_role',
permissions = {'scm_update': True} )
read_role = ImplicitRoleField(
role_name='Project Read Access',
role_description='Read access to this project',
parent_role=['member_role', 'auditor_role', 'scm_update_role'],
) )
@classmethod @classmethod
+240 -117
View File
@@ -7,24 +7,22 @@ import threading
import contextlib import contextlib
# Django # Django
from django.db import models, transaction from django.db import models, transaction, connection
from django.db.models import Q from django.db.models import Q
from django.db.models.aggregates import Max
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.fields import GenericForeignKey
# AWX # AWX
from django.contrib.auth.models import User # noqa from django.contrib.auth.models import User # noqa
from awx.main.models.base import * # noqa from awx.main.models.base import * # noqa
__all__ = [ __all__ = [
'Role', 'Role',
'RolePermission',
'batch_role_ancestor_rebuilding', 'batch_role_ancestor_rebuilding',
'get_user_permissions_on_resource', 'get_roles_on_resource',
'get_role_permissions_on_resource',
'ROLE_SINGLETON_SYSTEM_ADMINISTRATOR', 'ROLE_SINGLETON_SYSTEM_ADMINISTRATOR',
'ROLE_SINGLETON_SYSTEM_AUDITOR', 'ROLE_SINGLETON_SYSTEM_AUDITOR',
] ]
@@ -34,10 +32,6 @@ logger = logging.getLogger('awx.main.models.rbac')
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator' ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator'
ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor' ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor'
ALL_PERMISSIONS = {'create': True, 'read': True, 'update': True, 'delete': True,
'write': True, 'scm_update': True, 'use': True, 'execute': True}
tls = threading.local() # thread local storage tls = threading.local() # thread local storage
@contextlib.contextmanager @contextlib.contextmanager
@@ -65,9 +59,11 @@ def batch_role_ancestor_rebuilding(allow_nesting=False):
if not batch_role_rebuilding: if not batch_role_rebuilding:
rebuild_set = getattr(tls, 'roles_needing_rebuilding') rebuild_set = getattr(tls, 'roles_needing_rebuilding')
with transaction.atomic(): with transaction.atomic():
for role in Role.objects.filter(id__in=list(rebuild_set)).all(): Role._simultaneous_ancestry_rebuild(list(rebuild_set))
# TODO: We can reduce this to one rebuild call with our new upcoming rebuild method.. do this
role.rebuild_role_ancestor_list() #for role in Role.objects.filter(id__in=list(rebuild_set)).all():
# # TODO: We can reduce this to one rebuild call with our new upcoming rebuild method.. do this
# role.rebuild_role_ancestor_list()
delattr(tls, 'roles_needing_rebuilding') delattr(tls, 'roles_needing_rebuilding')
@@ -82,9 +78,15 @@ class Role(CommonModelNameNotUnique):
db_table = 'main_rbac_roles' db_table = 'main_rbac_roles'
singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True) singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True)
role_field = models.TextField(null=False, default='')
parents = models.ManyToManyField('Role', related_name='children') parents = models.ManyToManyField('Role', related_name='children')
implicit_parents = models.TextField(null=False, default='[]') implicit_parents = models.TextField(null=False, default='[]')
ancestors = models.ManyToManyField('Role', related_name='descendents') # auto-generated by `rebuild_role_ancestor_list` ancestors = models.ManyToManyField(
'Role',
through='RoleAncestorEntry',
through_fields=('descendent', 'ancestor'),
related_name='descendents'
) # auto-generated by `rebuild_role_ancestor_list`
members = models.ManyToManyField('auth.User', related_name='roles') members = models.ManyToManyField('auth.User', related_name='roles')
content_type = models.ForeignKey(ContentType, null=True, default=None) content_type = models.ForeignKey(ContentType, null=True, default=None)
object_id = models.PositiveIntegerField(null=True, default=None) object_id = models.PositiveIntegerField(null=True, default=None)
@@ -97,6 +99,18 @@ class Role(CommonModelNameNotUnique):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('api:role_detail', args=(self.pk,)) return reverse('api:role_detail', args=(self.pk,))
def __contains__(self, accessor):
if type(accessor) == User:
return self.ancestors.filter(members=accessor).exists()
elif accessor.__class__.__name__ == 'Team':
return self.ancestors.filter(pk=accessor.member_role.id).exists()
elif type(accessor) == Role:
return self.ancestors.filter(pk=accessor).exists()
else:
accessor_type = ContentType.objects.get_for_model(accessor)
roles = Role.objects.filter(content_type__pk=accessor_type.id,
object_id=accessor.id)
return self.ancestors.filter(pk__in=roles).exists()
def rebuild_role_ancestor_list(self): def rebuild_role_ancestor_list(self):
''' '''
@@ -115,21 +129,191 @@ class Role(CommonModelNameNotUnique):
roles_needing_rebuilding.add(self.id) roles_needing_rebuilding.add(self.id)
return return
actual_ancestors = set(Role.objects.filter(id=self.id).values_list('parents__ancestors__id', flat=True)) Role._simultaneous_ancestry_rebuild([self.id])
actual_ancestors.add(self.id)
if None in actual_ancestors:
actual_ancestors.remove(None) @staticmethod
stored_ancestors = set(self.ancestors.all().values_list('id', flat=True)) def _simultaneous_ancestry_rebuild(role_ids_to_rebuild):
#
# The simple version of what this function is doing
# =================================================
#
# When something changes in our role "hierarchy", we need to update
# the `Role.ancestors` mapping to reflect these changes. The basic
# idea, which the code in this method is modeled after, is to do
# this: When a change happens to a role's parents list, we update
# that role's ancestry list, then we recursively update any child
# roles ancestry lists. Because our role relationships are not
# strictly hierarchical, and can even have loops, this process may
# necessarily visit the same nodes more than once. To handle this
# without having to keep track of what should be updated (again) and
# in what order, we simply use the termination condition of stopping
# when our stored ancestry list matches what our list should be, eg,
# when nothing changes. This can be simply implemented:
#
# if actual_ancestors != stored_ancestors:
# for id in actual_ancestors - stored_ancestors:
# self.ancestors.add(id)
# for id in stored_ancestors - actual_ancestors:
# self.ancestors.remove(id)
#
# for child in self.children.all():
# child.rebuild_role_ancestor_list()
#
# However this results in a lot of calls to the database, so the
# optimized implementation below effectively does this same thing,
# but we update all children at once, so effectively we sweep down
# through our hierarchy one layer at a time instead of one node at a
# time. Because of how this method works, we can also start from many
# roots at once and sweep down a large set of roles, which we take
# advantage of when performing bulk operations.
#
#
# SQL Breakdown
# =============
# The Role ancestors has three columns, (id, from_role_id, to_role_id)
#
# id: Unqiue row ID
# from_role_id: Descendent role ID
# to_role_id: Ancestor role ID
#
# *NOTE* In addition to mapping roles to parents, there also
# always exists must exist an entry where
#
# from_role_id == role_id == to_role_id
#
# this makes our joins simple when we go to derive permissions or
# accessible objects.
#
#
# We operate under the assumption that our parent's ancestor list is
# correct, thus we can always compute what our ancestor list should
# be by taking the union of our parent's ancestor lists and adding
# our self reference entry from_role_id == role_id == to_role_id
#
# The inner query for the two SQL statements compute this union,
# the union of the parent's ancestors and the self referncing entry,
# for all roles in the current set of roles to rebuild.
#
# The DELETE query uses this to select all entries on disk for the
# roles we're dealing with, and removes the entries that are not in
# this list.
#
# The INSERT query uses this to select all entries in the list that
# are not in the database yet, and inserts all of the missing
# records.
#
# Once complete, we select all of the children for the roles we are
# working with, this list becomes the new role list we are working
# with.
#
# When our delete or insert query return that they have not performed
# any work, then we know that our children will also not need to be
# updated, and so we can terminate our loop.
#
#
if len(role_ids_to_rebuild) == 0:
return
cursor = connection.cursor()
loop_ct = 0
sql_params = {
'ancestors_table': Role.ancestors.through._meta.db_table,
'parents_table': Role.parents.through._meta.db_table,
'roles_table': Role._meta.db_table,
}
def split_ids_for_sqlite(role_ids):
for i in xrange(0, len(role_ids), 999):
yield role_ids[i:i + 999]
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
sql_params['ids'] = ','.join(str(x) for x in ids)
cursor.execute('''
DELETE FROM %(ancestors_table)s
WHERE ancestor_id IN (%(ids)s)
''' % sql_params)
while role_ids_to_rebuild:
if loop_ct > 1000:
raise Exception('Ancestry role rebuilding error: infinite loop detected')
loop_ct += 1
delete_ct = 0
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
sql_params['ids'] = ','.join(str(x) for x in ids)
cursor.execute('''
DELETE FROM %(ancestors_table)s
WHERE descendent_id IN (%(ids)s)
AND
id NOT IN (
SELECT %(ancestors_table)s.id FROM (
SELECT parents.from_role_id from_id, ancestors.ancestor_id to_id
FROM %(parents_table)s as parents
LEFT JOIN %(ancestors_table)s as ancestors
ON (parents.to_role_id = ancestors.descendent_id)
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL
UNION
SELECT id from_id, id to_id from %(roles_table)s WHERE id IN (%(ids)s)
) new_ancestry_list
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id
AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id)
WHERE %(ancestors_table)s.id IS NOT NULL
)
''' % sql_params)
delete_ct += cursor.rowcount
insert_ct = 0
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
sql_params['ids'] = ','.join(str(x) for x in ids)
cursor.execute('''
INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id)
SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM (
SELECT parents.from_role_id from_id,
ancestors.ancestor_id to_id,
roles.role_field,
COALESCE(roles.content_type_id, 0) content_type_id,
COALESCE(roles.object_id, 0) object_id
FROM %(parents_table)s as parents
INNER JOIN %(roles_table)s as roles ON (parents.from_role_id = roles.id)
LEFT OUTER JOIN %(ancestors_table)s as ancestors
ON (parents.to_role_id = ancestors.descendent_id)
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL
UNION
SELECT id from_id,
id to_id,
role_field,
COALESCE(content_type_id, 0) content_type_id,
COALESCE(object_id, 0) object_id
from %(roles_table)s WHERE id IN (%(ids)s)
) new_ancestry_list
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id
AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id)
WHERE %(ancestors_table)s.id IS NULL
''' % sql_params)
insert_ct += cursor.rowcount
if insert_ct == 0 and delete_ct == 0:
break
new_role_ids_to_rebuild = set()
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
sql_params['ids'] = ','.join(str(x) for x in ids)
new_role_ids_to_rebuild.update(set(Role.objects.distinct()
.filter(id__in=ids, children__id__isnull=False)
.values_list('children__id', flat=True)))
role_ids_to_rebuild = list(new_role_ids_to_rebuild)
# If it differs, update, and then update all of our children
if actual_ancestors != stored_ancestors:
for id in actual_ancestors - stored_ancestors:
self.ancestors.add(id)
for id in stored_ancestors - actual_ancestors:
self.ancestors.remove(id)
for child in self.children.all():
child.rebuild_role_ancestor_list()
@staticmethod @staticmethod
def visible_roles(user): def visible_roles(user):
@@ -143,109 +327,48 @@ class Role(CommonModelNameNotUnique):
def is_ancestor_of(self, role): def is_ancestor_of(self, role):
return role.ancestors.filter(id=self.id).exists() return role.ancestors.filter(id=self.id).exists()
class RoleAncestorEntry(models.Model):
class RolePermission(CreatedModifiedModel):
'''
Defines the permissions a role has
'''
class Meta: class Meta:
app_label = 'main' app_label = 'main'
verbose_name_plural = _('permissions') verbose_name_plural = _('role_ancestors')
db_table = 'main_rbac_permissions' db_table = 'main_rbac_role_ancestors'
index_together = [ index_together = [
('content_type', 'object_id') ("ancestor", "content_type_id", "object_id"), # used by get_roles_on_resource
("ancestor", "content_type_id", "role_field"), # used by accessible_objects
] ]
role = models.ForeignKey( descendent = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+')
Role, ancestor = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+')
null=False, role_field = models.TextField(null=False)
on_delete=models.CASCADE, #content_type_id = models.PositiveIntegerField(null=False)
related_name='permissions', #object_id = models.PositiveIntegerField(null=False)
) content_type_id = models.PositiveIntegerField(null=False)
content_type = models.ForeignKey(ContentType, null=False, default=None) object_id = models.PositiveIntegerField(null=False)
object_id = models.PositiveIntegerField(null=False, default=None)
resource = GenericForeignKey('content_type', 'object_id')
auto_generated = models.BooleanField(default=False)
create = models.IntegerField(default = 0)
read = models.IntegerField(default = 0)
write = models.IntegerField(default = 0)
delete = models.IntegerField(default = 0)
update = models.IntegerField(default = 0)
execute = models.IntegerField(default = 0)
scm_update = models.IntegerField(default = 0)
use = models.IntegerField(default = 0)
def get_roles_on_resource(resource, accessor):
def get_user_permissions_on_resource(resource, user):
''' '''
Returns a dict (or None) of the permissions a user has for a given Returns a dict (or None) of the roles a accessor has for a given resource.
resource. An accessor can be either a User, Role, or an arbitrary resource that
contains one or more Roles associated with it.
Note: Each field in the dict is the `or` of all respective permissions
that have been granted to the roles that are applicable for the given
user.
In example, if a user has been granted read access through a permission
on one role and write access through a permission on a separate role,
the returned dict will denote that the user has both read and write
access.
''' '''
if type(user) == User: if type(accessor) == User:
roles = user.roles.all() roles = accessor.roles.all()
elif type(accessor) == Role:
roles = [accessor]
else: else:
accessor_type = ContentType.objects.get_for_model(user) accessor_type = ContentType.objects.get_for_model(accessor)
roles = Role.objects.filter(content_type__pk=accessor_type.id, roles = Role.objects.filter(content_type__pk=accessor_type.id,
object_id=user.id) object_id=accessor.id)
qs = RolePermission.objects.filter( return {
content_type=ContentType.objects.get_for_model(resource), role_field: True for role_field in
object_id=resource.id, RoleAncestorEntry.objects.filter(
role__ancestors__in=roles, ancestor__in=roles,
) content_type_id=ContentType.objects.get_for_model(resource).id,
object_id=resource.id
).values_list('role_field', flat=True)
}
res = qs = qs.aggregate(
create = Max('create'),
read = Max('read'),
write = Max('write'),
update = Max('update'),
delete = Max('delete'),
scm_update = Max('scm_update'),
execute = Max('execute'),
use = Max('use')
)
if res['read'] is None:
return None
return res
def get_role_permissions_on_resource(resource, role):
'''
Returns a dict (or None) of the permissions a role has for a given
resource.
Note: Each field in the dict is the `or` of all respective permissions
that have been granted to either the role or any descendents of that role.
'''
qs = RolePermission.objects.filter(
content_type=ContentType.objects.get_for_model(resource),
object_id=resource.id,
role__ancestors=role
)
res = qs = qs.aggregate(
create = Max('create'),
read = Max('read'),
write = Max('write'),
update = Max('update'),
delete = Max('delete'),
scm_update = Max('scm_update'),
execute = Max('execute'),
use = Max('use')
)
if res['read'] is None:
return None
return res
+10 -112
View File
@@ -106,13 +106,14 @@ def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
if inventory is not None: if inventory is not None:
update_inventory_computed_fields.delay(inventory.id, True) update_inventory_computed_fields.delay(inventory.id, True)
def rebuild_role_ancestor_list(reverse, model, instance, pk_set, **kwargs): def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwargs):
'When a role parent is added or removed, update our role hierarchy list' 'When a role parent is added or removed, update our role hierarchy list'
if reverse: if action in ['post_add', 'post_remove', 'post_clear']:
for id in pk_set: if reverse:
model.objects.get(id=id).rebuild_role_ancestor_list() for id in pk_set:
else: model.objects.get(id=id).rebuild_role_ancestor_list()
instance.rebuild_role_ancestor_list() else:
instance.rebuild_role_ancestor_list()
def sync_superuser_status_to_rbac(instance, **kwargs): def sync_superuser_status_to_rbac(instance, **kwargs):
'When the is_superuser flag is changed on a user, reflect that in the membership of the System Admnistrator role' 'When the is_superuser flag is changed on a user, reflect that in the membership of the System Admnistrator role'
@@ -126,21 +127,15 @@ def create_user_role(instance, **kwargs):
Role.objects.get( Role.objects.get(
content_type=ContentType.objects.get_for_model(instance), content_type=ContentType.objects.get_for_model(instance),
object_id=instance.id, object_id=instance.id,
name = 'Owner' name = 'User Admin'
) )
except Role.DoesNotExist: except Role.DoesNotExist:
role = Role.objects.create( role = Role.objects.create(
name = 'Owner', name = 'User Admin',
role_field='admin_role',
content_object = instance, content_object = instance,
) )
role.members.add(instance) role.members.add(instance)
RolePermission.objects.create(
role = role,
resource = instance,
auto_generated = True,
create=1, read=1, write=1, delete=1, update=1,
execute=1, scm_update=1, use=1,
)
def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs): def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
content_type = ContentType.objects.get_for_model(Organization) content_type = ContentType.objects.get_for_model(Organization)
@@ -157,101 +152,6 @@ def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
if action == 'pre_remove': if action == 'pre_remove':
instance.content_object.admin_role.children.remove(user.admin_role) instance.content_object.admin_role.children.remove(user.admin_role)
def grant_host_access_to_group_roles(instance, action, model, reverse, pk_set, **kwargs):
'Add/remove RolePermission entries for Group roles that contain this host'
if action == 'post_add':
def grant(host, group):
RolePermission.objects.create(
resource=host,
role=group.admin_role,
auto_generated=True,
create=1,
read=1, write=1,
delete=1,
update=1,
execute=1,
scm_update=1,
use=1,
)
RolePermission.objects.create(
resource=host,
role=group.auditor_role,
auto_generated=True,
read=1,
)
RolePermission.objects.create(
resource=host,
role=group.updater_role,
auto_generated=True,
read=1,
write=1,
create=1,
use=1
)
RolePermission.objects.create(
resource=host,
role=group.executor_role,
auto_generated=True,
read=1,
execute=1
)
if reverse:
host = instance
for group_id in pk_set:
grant(host, Group.objects.get(id=group_id))
else:
group = instance
for host_id in pk_set:
grant(Host.objects.get(id=host_id), group)
if action == 'pre_remove':
host_content_type = ContentType.objects.get_for_model(Host)
def remove_grant(host, group):
RolePermission.objects.filter(
content_type = host_content_type,
object_id = host.id,
auto_generated = True,
role__in = [group.admin_role, group.updater_role, group.auditor_role, group.executor_role]
).delete()
if reverse:
host = instance
for group_id in pk_set:
remove_grant(host, Group.objects.get(id=group_id))
else:
group = instance
for host_id in pk_set:
remove_grant(Host.objects.get(id=host_id), group)
def grant_host_access_to_inventory(instance, **kwargs):
'Add/remove RolePermission entries for the Inventory that contains this host'
host_content_type = ContentType.objects.get_for_model(Host)
inventory_content_type = ContentType.objects.get_for_model(Inventory)
# Clear out any existing perms.. in case we switched inventory or something
qs = RolePermission.objects.filter(
content_type=host_content_type,
object_id=instance.id,
auto_generated=True,
role__content_type=inventory_content_type
)
if qs.count() == 1 and qs[0].role.object_id == instance.inventory.id:
# No change
return
qs.delete()
RolePermission.objects.create(
resource=instance,
role=instance.inventory.admin_role,
auto_generated=True,
create=1, read=1, write=1, delete=1, update=1,
execute=1, scm_update=1, use=1,
)
post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Host) post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Host) post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
@@ -269,8 +169,6 @@ post_save.connect(emit_job_event_detail, sender=JobEvent)
post_save.connect(emit_ad_hoc_command_event_detail, sender=AdHocCommandEvent) post_save.connect(emit_ad_hoc_command_event_detail, sender=AdHocCommandEvent)
m2m_changed.connect(rebuild_role_ancestor_list, Role.parents.through) m2m_changed.connect(rebuild_role_ancestor_list, Role.parents.through)
m2m_changed.connect(org_admin_edit_members, Role.members.through) m2m_changed.connect(org_admin_edit_members, Role.members.through)
m2m_changed.connect(grant_host_access_to_group_roles, Group.hosts.through)
post_save.connect(grant_host_access_to_inventory, Host)
post_save.connect(sync_superuser_status_to_rbac, sender=User) post_save.connect(sync_superuser_status_to_rbac, sender=User)
post_save.connect(create_user_role, sender=User) post_save.connect(create_user_role, sender=User)
@@ -661,4 +661,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -340,4 +340,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -270,4 +270,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -255,4 +255,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -268,4 +268,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
+1 -1
View File
@@ -257,4 +257,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -405,4 +405,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -518,4 +518,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -327,4 +327,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -331,4 +331,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -336,4 +336,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -335,4 +335,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -427,4 +427,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -388,4 +388,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -497,4 +497,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -451,4 +451,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -501,4 +501,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -464,4 +464,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -395,4 +395,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -417,4 +417,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -427,4 +427,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -442,4 +442,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -1408,4 +1408,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -792,4 +792,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -497,4 +497,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -449,4 +449,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -436,4 +436,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -437,4 +437,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -440,4 +440,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -450,4 +450,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -493,4 +493,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -510,4 +510,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -572,4 +572,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -519,4 +519,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -520,4 +520,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['main'] complete_apps = ['main']
@@ -3,6 +3,7 @@ import yaml
from awx.api.serializers import JobLaunchSerializer from awx.api.serializers import JobLaunchSerializer
from awx.main.models.credential import Credential from awx.main.models.credential import Credential
from awx.main.models.inventory import Inventory
from awx.main.models.jobs import Job, JobTemplate from awx.main.models.jobs import Job, JobTemplate
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@@ -93,8 +94,7 @@ def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, user
job_template = job_template_prompts(True) job_template = job_template_prompts(True)
admin_user = user('admin', True) admin_user = user('admin', True)
job_template.inventory.executor_role.members.add(admin_user) job_template.inventory.execute_role.members.add(admin_user)
job_template.inventory.save()
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', args=[job_template.pk]),
runtime_data, admin_user) runtime_data, admin_user)
@@ -112,20 +112,19 @@ def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, user
assert job_obj.job_tags == runtime_data['job_tags'] assert job_obj.job_tags == runtime_data['job_tags']
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.skip(reason="JT can_start without inventory needs to be fixed before passing")
@pytest.mark.job_runtime_vars @pytest.mark.job_runtime_vars
def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null, post, user): def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null, post, user):
job_template = job_template_prompts_null job_template = job_template_prompts_null
common_user = user('admin', False) common_user = user('not-admin', False)
job_template.executor_role.members.add(common_user) # Give user permission to execute the job template
job_template.save() job_template.execute_role.members.add(common_user)
job_template.project.member_role.members.add(common_user)
job_template.project.save()
# Give user permission to use inventory and credential at runtime
credential = Credential.objects.get(pk=runtime_data['credential']) credential = Credential.objects.get(pk=runtime_data['credential'])
credential.usage_role.members.add(common_user) credential.use_role.members.add(common_user)
credential.save() inventory = Inventory.objects.get(pk=runtime_data['inventory'])
inventory.use_role.members.add(common_user)
response = post(reverse('api:job_template_launch', args=[job_template.pk]), response = post(reverse('api:job_template_launch', args=[job_template.pk]),
runtime_data, common_user) runtime_data, common_user)
@@ -186,15 +185,12 @@ def test_job_launch_fails_without_inventory(deploy_jobtemplate, post, user):
def test_job_launch_fails_without_inventory_access(deploy_jobtemplate, machine_credential, post, user): def test_job_launch_fails_without_inventory_access(deploy_jobtemplate, machine_credential, post, user):
deploy_jobtemplate.ask_inventory_on_launch = True deploy_jobtemplate.ask_inventory_on_launch = True
deploy_jobtemplate.credential = machine_credential deploy_jobtemplate.credential = machine_credential
common_user = user('test-user', False)
deploy_jobtemplate.executor_role.members.add(common_user)
deploy_jobtemplate.save() deploy_jobtemplate.save()
deploy_jobtemplate.inventory.usage_role.members.add(common_user) common_user = user('test-user', False)
deploy_jobtemplate.inventory.save() deploy_jobtemplate.execute_role.members.add(common_user)
deploy_jobtemplate.inventory.use_role.members.add(common_user)
deploy_jobtemplate.project.member_role.members.add(common_user) deploy_jobtemplate.project.member_role.members.add(common_user)
deploy_jobtemplate.project.save() deploy_jobtemplate.credential.use_role.members.add(common_user)
deploy_jobtemplate.credential.usage_role.members.add(common_user)
deploy_jobtemplate.credential.save()
# Assure that the base job template can be launched to begin with # Assure that the base job template can be launched to begin with
response = post(reverse('api:job_template_launch', response = post(reverse('api:job_template_launch',
@@ -3,72 +3,118 @@ import pytest
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@pytest.fixture @pytest.fixture
def resourced_organization(organization, project, team, inventory, user): def organization_resource_creator(organization, user):
admin_user = user('test-admin', True) def rf(users, admins, job_templates, projects, inventories, teams):
member_user = user('org-member')
# Associate one resource of every type with the organization # Associate one resource of every type with the organization
organization.member_role.members.add(member_user) for i in range(users):
organization.admin_role.members.add(admin_user) member_user = user('org-member %s' % i)
# organization.teams.create(name='org-team') organization.member_role.members.add(member_user)
# inventory = organization.inventories.create(name="associated-inv") for i in range(admins):
project.jobtemplates.create(name="test-jt", admin_user = user('org-admin %s' % i)
description="test-job-template-desc", organization.admin_role.members.add(admin_user)
inventory=inventory, for i in range(teams):
playbook="test_playbook.yml") organization.teams.create(name='org-team %s' % i)
for i in range(inventories):
inventory = organization.inventories.create(name="associated-inv %s" % i)
for i in range(projects):
organization.projects.create(name="test-proj %s" % i,
description="test-proj-desc",
scm_type="git",
scm_url="https://github.com/jlaska/ansible-playbooks")
# Mix up the inventories and projects used by the job templates
i_proj = 0
i_inv = 0
for i in range(job_templates):
project = organization.projects.all()[i_proj]
inventory = organization.inventories.all()[i_inv]
project.jobtemplates.create(name="test-jt %s" % i,
description="test-job-template-desc",
inventory=inventory,
playbook="test_playbook.yml")
i_proj += 1
i_inv += 1
if i_proj >= organization.projects.count():
i_proj = 0
if i_inv >= organization.inventories.count():
i_inv = 0
return organization return organization
return rf
COUNTS_PRIMES = {
'users': 11,
'admins': 5,
'job_templates': 3,
'projects': 3,
'inventories': 7,
'teams': 5
}
COUNTS_ZEROS = {
'users': 0,
'admins': 0,
'job_templates': 0,
'projects': 0,
'inventories': 0,
'teams': 0
}
@pytest.fixture
def resourced_organization(organization_resource_creator):
return organization_resource_creator(**COUNTS_PRIMES)
@pytest.mark.django_db @pytest.mark.django_db
def test_org_counts_detail_view(resourced_organization, user, get): def test_org_counts_detail_admin(resourced_organization, user, get):
# Check that all types of resources are counted by a superuser # Check that all types of resources are counted by a superuser
external_admin = user('admin', True) external_admin = user('admin', True)
response = get(reverse('api:organization_detail', response = get(reverse('api:organization_detail',
args=[resourced_organization.pk]), external_admin) args=[resourced_organization.pk]), external_admin)
assert response.status_code == 200 assert response.status_code == 200
counts = response.data['summary_fields']['related_field_counts']
assert counts == COUNTS_PRIMES
@pytest.mark.django_db
def test_org_counts_detail_member(resourced_organization, user, get):
# Check that a non-admin org member can only see users / admin in detail view
member_user = resourced_organization.member_role.members.get(username='org-member 1')
response = get(reverse('api:organization_detail',
args=[resourced_organization.pk]), member_user)
assert response.status_code == 200
counts = response.data['summary_fields']['related_field_counts'] counts = response.data['summary_fields']['related_field_counts']
assert counts == { assert counts == {
'users': 1, 'users': COUNTS_PRIMES['users'], # Policy is that members can see other users and admins
'admins': 1, 'admins': COUNTS_PRIMES['admins'],
'job_templates': 1, 'job_templates': 0,
'projects': 1, 'projects': 0,
'inventories': 1, 'inventories': 0,
'teams': 1 'teams': 0
} }
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.skipif("True") # XXX: This needs to be implemented def test_org_counts_list_admin(resourced_organization, user, get):
def test_org_counts_admin(resourced_organization, user, get):
# Check that all types of resources are counted by a superuser # Check that all types of resources are counted by a superuser
external_admin = user('admin', True) external_admin = user('admin', True)
response = get(reverse('api:organization_list', args=[]), external_admin) response = get(reverse('api:organization_list', args=[]), external_admin)
assert response.status_code == 200 assert response.status_code == 200
counts = response.data['results'][0]['summary_fields']['related_field_counts'] counts = response.data['results'][0]['summary_fields']['related_field_counts']
assert counts == { assert counts == COUNTS_PRIMES
'users': 1,
'admins': 1,
'job_templates': 1,
'projects': 1,
'inventories': 1,
'teams': 1
}
@pytest.mark.django_db @pytest.mark.django_db
def test_org_counts_member(resourced_organization, get): def test_org_counts_list_member(resourced_organization, user, get):
# Check that a non-admin user can only see the full project and # Check that a non-admin user can only see the full project and
# user count, consistent with the RBAC rules # user count, consistent with the RBAC rules
member_user = resourced_organization.member_role.members.get(username='org-member') member_user = resourced_organization.member_role.members.get(username='org-member 1')
response = get(reverse('api:organization_list', args=[]), member_user) response = get(reverse('api:organization_list', args=[]), member_user)
assert response.status_code == 200 assert response.status_code == 200
counts = response.data['results'][0]['summary_fields']['related_field_counts'] counts = response.data['results'][0]['summary_fields']['related_field_counts']
assert counts == { assert counts == {
'users': 1, # Policy is that members can see other users and admins 'users': COUNTS_PRIMES['users'], # Policy is that members can see other users and admins
'admins': 1, 'admins': COUNTS_PRIMES['admins'],
'job_templates': 0, 'job_templates': 0,
'projects': 0, 'projects': 0,
'inventories': 0, 'inventories': 0,
@@ -86,17 +132,9 @@ def test_new_org_zero_counts(user, post):
new_org_list = post_response.render().data new_org_list = post_response.render().data
counts_dict = new_org_list['summary_fields']['related_field_counts'] counts_dict = new_org_list['summary_fields']['related_field_counts']
assert counts_dict == { assert counts_dict == COUNTS_ZEROS
'users': 0,
'admins': 0,
'job_templates': 0,
'projects': 0,
'inventories': 0,
'teams': 0
}
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.skipif("True") # XXX: This needs to be implemented
def test_two_organizations(resourced_organization, organizations, user, get): def test_two_organizations(resourced_organization, organizations, user, get):
# Check correct results for two organizations are returned # Check correct results for two organizations are returned
external_admin = user('admin', True) external_admin = user('admin', True)
@@ -111,26 +149,10 @@ def test_two_organizations(resourced_organization, organizations, user, get):
org_id = response.data['results'][i]['id'] org_id = response.data['results'][i]['id']
counts[org_id] = response.data['results'][i]['summary_fields']['related_field_counts'] counts[org_id] = response.data['results'][i]['summary_fields']['related_field_counts']
assert counts[org_id_full] == { assert counts[org_id_full] == COUNTS_PRIMES
'users': 1, assert counts[org_id_zero] == COUNTS_ZEROS
'admins': 1,
'job_templates': 1,
'projects': 1,
'inventories': 1,
'teams': 1
}
assert counts[org_id_zero] == {
'users': 0,
'admins': 0,
'job_templates': 0,
'projects': 0,
'inventories': 0,
'teams': 0
}
@pytest.mark.skip(reason="resolution planned for after RBAC merge")
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.skipif("True") # XXX: This needs to be implemented
def test_JT_associated_with_project(organizations, project, user, get): def test_JT_associated_with_project(organizations, project, user, get):
# Check that adding a project to an organization gets the project's JT # Check that adding a project to an organization gets the project's JT
# included in the organization's JT count # included in the organization's JT count
@@ -163,4 +185,3 @@ def test_JT_associated_with_project(organizations, project, user, get):
'inventories': 0, 'inventories': 0,
'teams': 0 'teams': 0
} }
+3 -1
View File
@@ -11,9 +11,11 @@ from awx.main.models import Project
# #
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
def test_user_project_list(get, project_factory, admin, alice, bob): def test_user_project_list(get, project_factory, organization, admin, alice, bob):
'List of projects a user has access to, filtered by projects you can also see' 'List of projects a user has access to, filtered by projects you can also see'
organization.member_role.members.add(alice, bob)
alice_project = project_factory('alice project') alice_project = project_factory('alice project')
alice_project.admin_role.members.add(alice) alice_project.admin_role.members.add(alice)
+24 -27
View File
@@ -176,8 +176,9 @@ def test_get_teams_roles_list(get, team, organization, admin):
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
roles = response.data roles = response.data
assert roles['count'] == 1
assert roles['results'][0]['id'] == organization.admin_role.id assert roles['count'] == 2
assert roles['results'][0]['id'] == organization.admin_role.id or roles['results'][1]['id'] == organization.admin_role.id
@pytest.mark.django_db @pytest.mark.django_db
@@ -272,13 +273,11 @@ def test_org_admin_add_user_to_job_template(post, organization, check_jobtemplat
joe = user('joe') joe = user('joe')
organization.admin_role.members.add(org_admin) organization.admin_role.members.add(org_admin)
assert check_jobtemplate.accessible_by(org_admin, {'write': True}) is True assert org_admin in check_jobtemplate.admin_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in check_jobtemplate.execute_role
res =post(reverse('api:role_users_list', args=(check_jobtemplate.executor_role.id,)), {'id': joe.id}, org_admin) post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'id': joe.id}, org_admin)
assert joe in check_jobtemplate.execute_role
print(res.data)
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
@@ -287,14 +286,14 @@ def test_org_admin_remove_user_to_job_template(post, organization, check_jobtemp
org_admin = user('org-admin') org_admin = user('org-admin')
joe = user('joe') joe = user('joe')
organization.admin_role.members.add(org_admin) organization.admin_role.members.add(org_admin)
check_jobtemplate.executor_role.members.add(joe) check_jobtemplate.execute_role.members.add(joe)
assert check_jobtemplate.accessible_by(org_admin, {'write': True}) is True assert org_admin in check_jobtemplate.admin_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in check_jobtemplate.execute_role
post(reverse('api:role_users_list', args=(check_jobtemplate.executor_role.id,)), {'disassociate': True, 'id': joe.id}, org_admin) post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'disassociate': True, 'id': joe.id}, org_admin)
assert joe not in check_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
def test_user_fail_to_add_user_to_job_template(post, organization, check_jobtemplate, user): def test_user_fail_to_add_user_to_job_template(post, organization, check_jobtemplate, user):
@@ -302,14 +301,13 @@ def test_user_fail_to_add_user_to_job_template(post, organization, check_jobtemp
rando = user('rando') rando = user('rando')
joe = user('joe') joe = user('joe')
assert check_jobtemplate.accessible_by(rando, {'write': True}) is False assert rando not in check_jobtemplate.admin_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in check_jobtemplate.execute_role
res = post(reverse('api:role_users_list', args=(check_jobtemplate.executor_role.id,)), {'id': joe.id}, rando) res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'id': joe.id}, rando)
print(res.data)
assert res.status_code == 403 assert res.status_code == 403
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in check_jobtemplate.execute_role
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
@@ -317,16 +315,15 @@ def test_user_fail_to_remove_user_to_job_template(post, organization, check_jobt
'Tests that a user without permissions to assign/revoke membership to a particular role cannot do so' 'Tests that a user without permissions to assign/revoke membership to a particular role cannot do so'
rando = user('rando') rando = user('rando')
joe = user('joe') joe = user('joe')
check_jobtemplate.executor_role.members.add(joe) check_jobtemplate.execute_role.members.add(joe)
assert check_jobtemplate.accessible_by(rando, {'write': True}) is False assert rando not in check_jobtemplate.admin_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in check_jobtemplate.execute_role
res = post(reverse('api:role_users_list', args=(check_jobtemplate.executor_role.id,)), {'disassociate': True, 'id': joe.id}, rando) res = post(reverse('api:role_users_list', args=(check_jobtemplate.execute_role.id,)), {'disassociate': True, 'id': joe.id}, rando)
assert res.status_code == 403 assert res.status_code == 403
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in check_jobtemplate.execute_role
# #
# /roles/<id>/teams/ # /roles/<id>/teams/
@@ -384,8 +381,8 @@ def test_role_children(get, team, admin, role):
url = reverse('api:role_children_list', args=(team.member_role.id,)) url = reverse('api:role_children_list', args=(team.member_role.id,))
response = get(url, admin) response = get(url, admin)
assert response.status_code == 200 assert response.status_code == 200
assert response.data['count'] == 1 assert response.data['count'] == 2
assert response.data['results'][0]['id'] == role.id assert response.data['results'][0]['id'] == role.id or response.data['results'][1]['id'] == role.id
@@ -421,7 +418,7 @@ def test_ensure_permissions_is_present(organization, get, user):
assert 'summary_fields' in org assert 'summary_fields' in org
assert 'permissions' in org['summary_fields'] assert 'permissions' in org['summary_fields']
assert org['summary_fields']['permissions']['read'] > 0 assert org['summary_fields']['permissions']['read_role'] > 0
@pytest.mark.django_db @pytest.mark.django_db
def test_ensure_role_summary_is_present(organization, get, user): def test_ensure_role_summary_is_present(organization, get, user):
+134 -61
View File
@@ -2,7 +2,6 @@ import pytest
from awx.main.models import ( from awx.main.models import (
Role, Role,
RolePermission,
Organization, Organization,
Project, Project,
) )
@@ -10,27 +9,25 @@ from awx.main.models import (
@pytest.mark.django_db @pytest.mark.django_db
def test_auto_inheritance_by_children(organization, alice): def test_auto_inheritance_by_children(organization, alice):
A = Role.objects.create(name='A') A = Role.objects.create(name='A', role_field='')
B = Role.objects.create(name='B') B = Role.objects.create(name='B', role_field='')
A.members.add(alice) A.members.add(alice)
assert alice not in organization.admin_role
assert Organization.accessible_objects(alice, 'admin_role').count() == 0
assert organization.accessible_by(alice, {'read': True}) is False
assert Organization.accessible_objects(alice, {'read': True}).count() == 0
A.children.add(B) A.children.add(B)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.admin_role
assert Organization.accessible_objects(alice, {'read': True}).count() == 0 assert Organization.accessible_objects(alice, 'admin_role').count() == 0
A.children.add(organization.admin_role) A.children.add(organization.admin_role)
assert organization.accessible_by(alice, {'read': True}) is True assert alice in organization.admin_role
assert Organization.accessible_objects(alice, {'read': True}).count() == 1 assert Organization.accessible_objects(alice, 'admin_role').count() == 1
A.children.remove(organization.admin_role) A.children.remove(organization.admin_role)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.admin_role
B.children.add(organization.admin_role) B.children.add(organization.admin_role)
assert organization.accessible_by(alice, {'read': True}) is True assert alice in organization.admin_role
B.children.remove(organization.admin_role) B.children.remove(organization.admin_role)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.admin_role
assert Organization.accessible_objects(alice, {'read': True}).count() == 0 assert Organization.accessible_objects(alice, 'admin_role').count() == 0
# We've had the case where our pre/post save init handlers in our field descriptors # We've had the case where our pre/post save init handlers in our field descriptors
# end up creating a ton of role objects because of various not-so-obvious issues # end up creating a ton of role objects because of various not-so-obvious issues
@@ -43,32 +40,19 @@ def test_auto_inheritance_by_parents(organization, alice):
B = Role.objects.create(name='B') B = Role.objects.create(name='B')
A.members.add(alice) A.members.add(alice)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.admin_role
B.parents.add(A) B.parents.add(A)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.admin_role
organization.admin_role.parents.add(A) organization.admin_role.parents.add(A)
assert organization.accessible_by(alice, {'read': True}) is True assert alice in organization.admin_role
organization.admin_role.parents.remove(A) organization.admin_role.parents.remove(A)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.admin_role
organization.admin_role.parents.add(B) organization.admin_role.parents.add(B)
assert organization.accessible_by(alice, {'read': True}) is True assert alice in organization.admin_role
organization.admin_role.parents.remove(B) organization.admin_role.parents.remove(B)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.admin_role
@pytest.mark.django_db
def test_permission_union(organization, alice):
A = Role.objects.create(name='A')
A.members.add(alice)
B = Role.objects.create(name='B')
B.members.add(alice)
assert organization.accessible_by(alice, {'read': True, 'write': True}) is False
RolePermission.objects.create(role=A, resource=organization, read=True)
assert organization.accessible_by(alice, {'read': True, 'write': True}) is False
RolePermission.objects.create(role=A, resource=organization, write=True)
assert organization.accessible_by(alice, {'read': True, 'write': True}) is True
@pytest.mark.django_db @pytest.mark.django_db
def test_accessible_objects(organization, alice, bob): def test_accessible_objects(organization, alice, bob):
@@ -78,57 +62,54 @@ def test_accessible_objects(organization, alice, bob):
B.members.add(alice) B.members.add(alice)
B.members.add(bob) B.members.add(bob)
assert Organization.accessible_objects(alice, {'read': True, 'write': True}).count() == 0 assert Organization.accessible_objects(alice, 'admin_role').count() == 0
RolePermission.objects.create(role=A, resource=organization, read=True) assert Organization.accessible_objects(bob, 'admin_role').count() == 0
assert Organization.accessible_objects(alice, {'read': True, 'write': True}).count() == 0 A.children.add(organization.admin_role)
assert Organization.accessible_objects(bob, {'read': True, 'write': True}).count() == 0 assert Organization.accessible_objects(alice, 'admin_role').count() == 1
RolePermission.objects.create(role=B, resource=organization, write=True) assert Organization.accessible_objects(bob, 'admin_role').count() == 0
assert Organization.accessible_objects(alice, {'read': True, 'write': True}).count() == 1
assert Organization.accessible_objects(bob, {'read': True, 'write': True}).count() == 0
assert Organization.accessible_objects(bob, {'read': True, 'write': True}).count() == 0
@pytest.mark.django_db @pytest.mark.django_db
def test_team_symantics(organization, team, alice): def test_team_symantics(organization, team, alice):
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.auditor_role
team.member_role.children.add(organization.auditor_role) team.member_role.children.add(organization.auditor_role)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.auditor_role
team.member_role.members.add(alice) team.member_role.members.add(alice)
assert organization.accessible_by(alice, {'read': True}) is True assert alice in organization.auditor_role
team.member_role.members.remove(alice) team.member_role.members.remove(alice)
assert organization.accessible_by(alice, {'read': True}) is False assert alice not in organization.auditor_role
@pytest.mark.django_db @pytest.mark.django_db
def test_auto_m2m_adjuments(organization, inventory, group, alice): def test_auto_m2m_adjustments(organization, inventory, group, alice):
'Ensures the auto role reparenting is working correctly through m2m maps' 'Ensures the auto role reparenting is working correctly through m2m maps'
g1 = group(name='g1') g1 = group(name='g1')
g1.admin_role.members.add(alice) g1.admin_role.members.add(alice)
assert g1.accessible_by(alice, {'read': True}) is True assert alice in g1.admin_role
g2 = group(name='g2') g2 = group(name='g2')
assert g2.accessible_by(alice, {'read': True}) is False assert alice not in g2.admin_role
g2.parents.add(g1) g2.parents.add(g1)
assert g2.accessible_by(alice, {'read': True}) is True assert alice in g2.admin_role
g2.parents.remove(g1) g2.parents.remove(g1)
assert g2.accessible_by(alice, {'read': True}) is False assert alice not in g2.admin_role
g1.children.add(g2) g1.children.add(g2)
assert g2.accessible_by(alice, {'read': True}) is True assert alice in g2.admin_role
g1.children.remove(g2) g1.children.remove(g2)
assert g2.accessible_by(alice, {'read': True}) is False assert alice not in g2.admin_role
@pytest.mark.django_db @pytest.mark.django_db
def test_auto_field_adjuments(organization, inventory, team, alice): def test_auto_field_adjustments(organization, inventory, team, alice):
'Ensures the auto role reparenting is working correctly through non m2m fields' 'Ensures the auto role reparenting is working correctly through non m2m fields'
org2 = Organization.objects.create(name='Org 2', description='org 2') org2 = Organization.objects.create(name='Org 2', description='org 2')
org2.admin_role.members.add(alice) org2.admin_role.members.add(alice)
assert inventory.accessible_by(alice, {'read': True}) is False assert alice not in inventory.admin_role
inventory.organization = org2 inventory.organization = org2
inventory.save() inventory.save()
assert inventory.accessible_by(alice, {'read': True}) is True assert alice in inventory.admin_role
inventory.organization = organization inventory.organization = organization
inventory.save() inventory.save()
assert inventory.accessible_by(alice, {'read': True}) is False assert alice not in inventory.admin_role
#assert False #assert False
@pytest.mark.django_db @pytest.mark.django_db
@@ -147,14 +128,12 @@ def test_implicit_deletes(alice):
assert Role.objects.filter(id=auditor_role_id).count() == 1 assert Role.objects.filter(id=auditor_role_id).count() == 1
n_alice_roles = alice.roles.count() n_alice_roles = alice.roles.count()
n_system_admin_children = Role.singleton('System Administrator').children.count() n_system_admin_children = Role.singleton('System Administrator').children.count()
rp = RolePermission.objects.create(role=delorg.admin_role, resource=delorg, read=True)
delorg.delete() delorg.delete()
assert Role.objects.filter(id=admin_role_id).count() == 0 assert Role.objects.filter(id=admin_role_id).count() == 0
assert Role.objects.filter(id=auditor_role_id).count() == 0 assert Role.objects.filter(id=auditor_role_id).count() == 0
assert alice.roles.count() == (n_alice_roles - 1) assert alice.roles.count() == (n_alice_roles - 1)
assert RolePermission.objects.filter(id=rp.id).count() == 0
assert Role.singleton('System Administrator').children.count() == (n_system_admin_children - 1) assert Role.singleton('System Administrator').children.count() == (n_system_admin_children - 1)
assert child.ancestors.count() == 1 assert child.ancestors.count() == 1
assert child.ancestors.all()[0] == child assert child.ancestors.all()[0] == child
@@ -168,8 +147,8 @@ def test_content_object(user):
assert org.admin_role.content_object.id == org.id assert org.admin_role.content_object.id == org.id
@pytest.mark.django_db @pytest.mark.django_db
def test_hierarchy_rebuilding(): def test_hierarchy_rebuilding_multi_path():
'Tests some subdtle cases around role hierarchy rebuilding' 'Tests a subdtle cases around role hierarchy rebuilding when you have multiple paths to the same role of different length'
X = Role.objects.create(name='X') X = Role.objects.create(name='X')
A = Role.objects.create(name='A') A = Role.objects.create(name='A')
@@ -196,6 +175,100 @@ def test_hierarchy_rebuilding():
assert X.is_ancestor_of(D) is False assert X.is_ancestor_of(D) is False
@pytest.mark.django_db
def test_hierarchy_rebuilding_loops1(organization, team):
'Tests ancestry rebuilding loops are involved'
assert team.admin_role.is_ancestor_of(organization.admin_role) is False
assert organization.admin_role.is_ancestor_of(team.admin_role)
team.admin_role.children.add(organization.admin_role)
assert team.admin_role.is_ancestor_of(organization.admin_role)
assert organization.admin_role.is_ancestor_of(team.admin_role)
team.admin_role.children.remove(organization.admin_role)
assert team.admin_role.is_ancestor_of(organization.admin_role) is False
assert organization.admin_role.is_ancestor_of(team.admin_role)
team.admin_role.children.add(organization.admin_role)
X = Role.objects.create(name='X')
X.children.add(organization.admin_role)
assert X.is_ancestor_of(team.admin_role)
assert X.is_ancestor_of(organization.admin_role)
assert organization.admin_role.is_ancestor_of(X) is False
assert team.admin_role.is_ancestor_of(X) is False
#print(X.descendents.filter(id=organization.admin_role.id).count())
#print(X.children.filter(id=organization.admin_role.id).count())
X.children.remove(organization.admin_role)
X.rebuild_role_ancestor_list()
#print(X.descendents.filter(id=organization.admin_role.id).count())
#print(X.children.filter(id=organization.admin_role.id).count())
assert X.is_ancestor_of(team.admin_role) is False
assert X.is_ancestor_of(organization.admin_role) is False
@pytest.mark.django_db
def test_hierarchy_rebuilding_loops():
'Tests ancestry rebuilding loops are involved'
X = Role.objects.create(name='X')
A = Role.objects.create(name='A')
B = Role.objects.create(name='B')
C = Role.objects.create(name='C')
A.children.add(B)
B.children.add(C)
C.children.add(A)
X.children.add(A)
assert X.is_ancestor_of(A)
assert X.is_ancestor_of(B)
assert X.is_ancestor_of(C)
assert A.is_ancestor_of(B)
assert A.is_ancestor_of(C)
assert B.is_ancestor_of(C)
assert B.is_ancestor_of(A)
assert C.is_ancestor_of(A)
assert C.is_ancestor_of(B)
X.children.remove(A)
X.rebuild_role_ancestor_list()
assert X.is_ancestor_of(A) is False
assert X.is_ancestor_of(B) is False
assert X.is_ancestor_of(C) is False
X.children.add(A)
assert X.is_ancestor_of(A)
assert X.is_ancestor_of(B)
assert X.is_ancestor_of(C)
C.children.remove(A)
assert A.is_ancestor_of(B)
assert A.is_ancestor_of(C)
assert B.is_ancestor_of(C)
assert B.is_ancestor_of(A) is False
assert C.is_ancestor_of(A) is False
assert C.is_ancestor_of(B) is False
assert X.is_ancestor_of(A)
assert X.is_ancestor_of(B)
assert X.is_ancestor_of(C)
@pytest.mark.django_db @pytest.mark.django_db
def test_auto_parenting(): def test_auto_parenting():
org1 = Organization.objects.create(name='org1') org1 = Organization.objects.create(name='org1')
@@ -16,13 +16,13 @@ def test_credential_migration_user(credential, user, permissions):
rbac.migrate_credential(apps, None) rbac.migrate_credential(apps, None)
assert credential.accessible_by(u, permissions['admin']) assert u in credential.owner_role
@pytest.mark.django_db @pytest.mark.django_db
def test_credential_usage_role(credential, user, permissions): def test_credential_use_role(credential, user, permissions):
u = user('user', False) u = user('user', False)
credential.usage_role.members.add(u) credential.use_role.members.add(u)
assert credential.accessible_by(u, permissions['usage']) assert u in credential.use_role
@pytest.mark.django_db @pytest.mark.django_db
def test_credential_migration_team_member(credential, team, user, permissions): def test_credential_migration_team_member(credential, team, user, permissions):
@@ -34,13 +34,13 @@ def test_credential_migration_team_member(credential, team, user, permissions):
# No permissions pre-migration (this happens automatically so we patch this) # No permissions pre-migration (this happens automatically so we patch this)
team.admin_role.children.remove(credential.owner_role) team.admin_role.children.remove(credential.owner_role)
team.member_role.children.remove(credential.usage_role) team.member_role.children.remove(credential.use_role)
assert not credential.accessible_by(u, permissions['admin']) assert u not in credential.owner_role
rbac.migrate_credential(apps, None) rbac.migrate_credential(apps, None)
# Admin permissions post migration # Admin permissions post migration
assert credential.accessible_by(u, permissions['admin']) assert u in credential.owner_role
@pytest.mark.django_db @pytest.mark.django_db
def test_credential_migration_team_admin(credential, team, user, permissions): def test_credential_migration_team_admin(credential, team, user, permissions):
@@ -49,11 +49,11 @@ def test_credential_migration_team_admin(credential, team, user, permissions):
credential.deprecated_team = team credential.deprecated_team = team
credential.save() credential.save()
assert not credential.accessible_by(u, permissions['usage']) assert u not in credential.use_role
# Usage permissions post migration # Usage permissions post migration
rbac.migrate_credential(apps, None) rbac.migrate_credential(apps, None)
assert credential.accessible_by(u, permissions['usage']) assert u in credential.use_role
def test_credential_access_superuser(): def test_credential_access_superuser():
u = User(username='admin', is_superuser=True) u = User(username='admin', is_superuser=True)
@@ -166,10 +166,10 @@ def test_cred_inventory_source(user, inventory, credential):
inventory=inventory, inventory=inventory,
) )
assert not credential.accessible_by(u, {'use':True}) assert u not in credential.use_role
rbac.migrate_credential(apps, None) rbac.migrate_credential(apps, None)
assert credential.accessible_by(u, {'use':True}) assert u in credential.use_role
@pytest.mark.django_db @pytest.mark.django_db
def test_cred_project(user, credential, project): def test_cred_project(user, credential, project):
@@ -178,10 +178,10 @@ def test_cred_project(user, credential, project):
project.credential = credential project.credential = credential
project.save() project.save()
assert not credential.accessible_by(u, {'use':True}) assert u not in credential.use_role
rbac.migrate_credential(apps, None) rbac.migrate_credential(apps, None)
assert credential.accessible_by(u, {'use':True}) assert u in credential.use_role
@pytest.mark.django_db @pytest.mark.django_db
def test_cred_no_org(user, credential): def test_cred_no_org(user, credential):
@@ -196,7 +196,7 @@ def test_cred_team(user, team, credential):
credential.deprecated_team = team credential.deprecated_team = team
credential.save() credential.save()
assert not credential.accessible_by(u, {'use':True}) assert u not in credential.use_role
rbac.migrate_credential(apps, None) rbac.migrate_credential(apps, None)
assert credential.accessible_by(u, {'use':True}) assert u in credential.use_role
@@ -6,7 +6,7 @@ from awx.main.models import (
Host, Host,
CustomInventoryScript, CustomInventoryScript,
) )
from awx.main.access import InventoryAccess from awx.main.access import InventoryAccess, HostAccess
from django.apps import apps from django.apps import apps
@pytest.mark.django_db @pytest.mark.django_db
@@ -16,10 +16,10 @@ def test_custom_inv_script_access(organization, user):
custom_inv = CustomInventoryScript.objects.create(name='test', script='test', description='test') custom_inv = CustomInventoryScript.objects.create(name='test', script='test', description='test')
custom_inv.organization = organization custom_inv.organization = organization
custom_inv.save() custom_inv.save()
assert not custom_inv.accessible_by(u, {'read':True}) assert u not in custom_inv.read_role
organization.member_role.members.add(u) organization.member_role.members.add(u)
assert custom_inv.accessible_by(u, {'read':True}) assert u in custom_inv.read_role
@pytest.mark.django_db @pytest.mark.django_db
def test_inventory_admin_user(inventory, permissions, user): def test_inventory_admin_user(inventory, permissions, user):
@@ -27,13 +27,13 @@ def test_inventory_admin_user(inventory, permissions, user):
perm = Permission(user=u, inventory=inventory, permission_type='admin') perm = Permission(user=u, inventory=inventory, permission_type='admin')
perm.save() perm.save()
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert inventory.accessible_by(u, permissions['admin']) assert u in inventory.admin_role
assert inventory.executor_role.members.filter(id=u.id).exists() is False assert inventory.execute_role.members.filter(id=u.id).exists() is False
assert inventory.updater_role.members.filter(id=u.id).exists() is False assert inventory.update_role.members.filter(id=u.id).exists() is False
@pytest.mark.django_db @pytest.mark.django_db
def test_inventory_auditor_user(inventory, permissions, user): def test_inventory_auditor_user(inventory, permissions, user):
@@ -41,15 +41,15 @@ def test_inventory_auditor_user(inventory, permissions, user):
perm = Permission(user=u, inventory=inventory, permission_type='read') perm = Permission(user=u, inventory=inventory, permission_type='read')
perm.save() perm.save()
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is False assert u not in inventory.auditor_role
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is True assert u in inventory.auditor_role
assert inventory.executor_role.members.filter(id=u.id).exists() is False assert inventory.execute_role.members.filter(id=u.id).exists() is False
assert inventory.updater_role.members.filter(id=u.id).exists() is False assert inventory.update_role.members.filter(id=u.id).exists() is False
@pytest.mark.django_db @pytest.mark.django_db
def test_inventory_updater_user(inventory, permissions, user): def test_inventory_updater_user(inventory, permissions, user):
@@ -57,14 +57,14 @@ def test_inventory_updater_user(inventory, permissions, user):
perm = Permission(user=u, inventory=inventory, permission_type='write') perm = Permission(user=u, inventory=inventory, permission_type='write')
perm.save() perm.save()
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is False assert u not in inventory.auditor_role
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.executor_role.members.filter(id=u.id).exists() is False assert inventory.execute_role.members.filter(id=u.id).exists() is False
assert inventory.updater_role.members.filter(id=u.id).exists() assert inventory.update_role.members.filter(id=u.id).exists()
@pytest.mark.django_db @pytest.mark.django_db
def test_inventory_executor_user(inventory, permissions, user): def test_inventory_executor_user(inventory, permissions, user):
@@ -72,15 +72,15 @@ def test_inventory_executor_user(inventory, permissions, user):
perm = Permission(user=u, inventory=inventory, permission_type='read', run_ad_hoc_commands=True) perm = Permission(user=u, inventory=inventory, permission_type='read', run_ad_hoc_commands=True)
perm.save() perm.save()
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is False assert u not in inventory.auditor_role
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is True assert u in inventory.read_role
assert inventory.executor_role.members.filter(id=u.id).exists() assert inventory.execute_role.members.filter(id=u.id).exists()
assert inventory.updater_role.members.filter(id=u.id).exists() is False assert inventory.update_role.members.filter(id=u.id).exists() is False
@@ -91,7 +91,7 @@ def test_inventory_admin_team(inventory, permissions, user, team):
perm.save() perm.save()
team.deprecated_users.add(u) team.deprecated_users.add(u)
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
rbac.migrate_team(apps, None) rbac.migrate_team(apps, None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
@@ -99,10 +99,10 @@ def test_inventory_admin_team(inventory, permissions, user, team):
assert team.member_role.members.count() == 1 assert team.member_role.members.count() == 1
assert inventory.admin_role.members.filter(id=u.id).exists() is False assert inventory.admin_role.members.filter(id=u.id).exists() is False
assert inventory.auditor_role.members.filter(id=u.id).exists() is False assert inventory.auditor_role.members.filter(id=u.id).exists() is False
assert inventory.executor_role.members.filter(id=u.id).exists() is False assert inventory.execute_role.members.filter(id=u.id).exists() is False
assert inventory.updater_role.members.filter(id=u.id).exists() is False assert inventory.update_role.members.filter(id=u.id).exists() is False
assert inventory.accessible_by(u, permissions['auditor']) assert u in inventory.read_role
assert inventory.accessible_by(u, permissions['admin']) assert u in inventory.admin_role
@pytest.mark.django_db @pytest.mark.django_db
@@ -112,8 +112,8 @@ def test_inventory_auditor(inventory, permissions, user, team):
perm.save() perm.save()
team.deprecated_users.add(u) team.deprecated_users.add(u)
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is False assert u not in inventory.auditor_role
rbac.migrate_team(apps,None) rbac.migrate_team(apps,None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
@@ -121,10 +121,10 @@ def test_inventory_auditor(inventory, permissions, user, team):
assert team.member_role.members.count() == 1 assert team.member_role.members.count() == 1
assert inventory.admin_role.members.filter(id=u.id).exists() is False assert inventory.admin_role.members.filter(id=u.id).exists() is False
assert inventory.auditor_role.members.filter(id=u.id).exists() is False assert inventory.auditor_role.members.filter(id=u.id).exists() is False
assert inventory.executor_role.members.filter(id=u.id).exists() is False assert inventory.execute_role.members.filter(id=u.id).exists() is False
assert inventory.updater_role.members.filter(id=u.id).exists() is False assert inventory.update_role.members.filter(id=u.id).exists() is False
assert inventory.accessible_by(u, permissions['auditor']) assert u in inventory.read_role
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
@pytest.mark.django_db @pytest.mark.django_db
def test_inventory_updater(inventory, permissions, user, team): def test_inventory_updater(inventory, permissions, user, team):
@@ -133,8 +133,8 @@ def test_inventory_updater(inventory, permissions, user, team):
perm.save() perm.save()
team.deprecated_users.add(u) team.deprecated_users.add(u)
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is False assert u not in inventory.auditor_role
rbac.migrate_team(apps,None) rbac.migrate_team(apps,None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
@@ -142,10 +142,10 @@ def test_inventory_updater(inventory, permissions, user, team):
assert team.member_role.members.count() == 1 assert team.member_role.members.count() == 1
assert inventory.admin_role.members.filter(id=u.id).exists() is False assert inventory.admin_role.members.filter(id=u.id).exists() is False
assert inventory.auditor_role.members.filter(id=u.id).exists() is False assert inventory.auditor_role.members.filter(id=u.id).exists() is False
assert inventory.executor_role.members.filter(id=u.id).exists() is False assert inventory.execute_role.members.filter(id=u.id).exists() is False
assert inventory.updater_role.members.filter(id=u.id).exists() is False assert inventory.update_role.members.filter(id=u.id).exists() is False
assert team.member_role.is_ancestor_of(inventory.updater_role) assert team.member_role.is_ancestor_of(inventory.update_role)
assert team.member_role.is_ancestor_of(inventory.executor_role) is False assert team.member_role.is_ancestor_of(inventory.execute_role) is False
@pytest.mark.django_db @pytest.mark.django_db
@@ -155,8 +155,8 @@ def test_inventory_executor(inventory, permissions, user, team):
perm.save() perm.save()
team.deprecated_users.add(u) team.deprecated_users.add(u)
assert inventory.accessible_by(u, permissions['admin']) is False assert u not in inventory.admin_role
assert inventory.accessible_by(u, permissions['auditor']) is False assert u not in inventory.auditor_role
rbac.migrate_team(apps, None) rbac.migrate_team(apps, None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
@@ -164,10 +164,10 @@ def test_inventory_executor(inventory, permissions, user, team):
assert team.member_role.members.count() == 1 assert team.member_role.members.count() == 1
assert inventory.admin_role.members.filter(id=u.id).exists() is False assert inventory.admin_role.members.filter(id=u.id).exists() is False
assert inventory.auditor_role.members.filter(id=u.id).exists() is False assert inventory.auditor_role.members.filter(id=u.id).exists() is False
assert inventory.executor_role.members.filter(id=u.id).exists() is False assert inventory.execute_role.members.filter(id=u.id).exists() is False
assert inventory.updater_role.members.filter(id=u.id).exists() is False assert inventory.update_role.members.filter(id=u.id).exists() is False
assert team.member_role.is_ancestor_of(inventory.updater_role) is False assert team.member_role.is_ancestor_of(inventory.update_role) is False
assert team.member_role.is_ancestor_of(inventory.executor_role) assert team.member_role.is_ancestor_of(inventory.execute_role)
@pytest.mark.django_db @pytest.mark.django_db
def test_group_parent_admin(group, permissions, user): def test_group_parent_admin(group, permissions, user):
@@ -177,21 +177,21 @@ def test_group_parent_admin(group, permissions, user):
childA = group('child-1') childA = group('child-1')
parent1.admin_role.members.add(u) parent1.admin_role.members.add(u)
assert parent1.accessible_by(u, permissions['admin']) assert u in parent1.admin_role
assert not parent2.accessible_by(u, permissions['admin']) assert u not in parent2.admin_role
assert not childA.accessible_by(u, permissions['admin']) assert u not in childA.admin_role
childA.parents.add(parent1) childA.parents.add(parent1)
assert childA.accessible_by(u, permissions['admin']) assert u in childA.admin_role
childA.parents.remove(parent1) childA.parents.remove(parent1)
assert not childA.accessible_by(u, permissions['admin']) assert u not in childA.admin_role
parent2.children.add(childA) parent2.children.add(childA)
assert not childA.accessible_by(u, permissions['admin']) assert u not in childA.admin_role
parent2.admin_role.members.add(u) parent2.admin_role.members.add(u)
assert childA.accessible_by(u, permissions['admin']) assert u in childA.admin_role
@pytest.mark.django_db @pytest.mark.django_db
def test_access_admin(organization, inventory, user): def test_access_admin(organization, inventory, user):
@@ -237,33 +237,35 @@ def test_host_access(organization, inventory, user, group):
not_my_group = group('not-my-group') not_my_group = group('not-my-group')
group_admin = user('group_admin', False) group_admin = user('group_admin', False)
inventory_admin_access = HostAccess(inventory_admin)
group_admin_access = HostAccess(group_admin)
h1 = Host.objects.create(inventory=inventory, name='host1') h1 = Host.objects.create(inventory=inventory, name='host1')
h2 = Host.objects.create(inventory=inventory, name='host2') h2 = Host.objects.create(inventory=inventory, name='host2')
h1.groups.add(my_group) h1.groups.add(my_group)
h2.groups.add(not_my_group) h2.groups.add(not_my_group)
assert h1.accessible_by(inventory_admin, {'read': True}) is False assert inventory_admin_access.can_read(h1) is False
assert h1.accessible_by(group_admin, {'read': True}) is False assert group_admin_access.can_read(h1) is False
inventory.admin_role.members.add(inventory_admin) inventory.admin_role.members.add(inventory_admin)
my_group.admin_role.members.add(group_admin) my_group.admin_role.members.add(group_admin)
assert h1.accessible_by(inventory_admin, {'read': True}) assert inventory_admin_access.can_read(h1)
assert h2.accessible_by(inventory_admin, {'read': True}) assert inventory_admin_access.can_read(h2)
assert h1.accessible_by(group_admin, {'read': True}) assert group_admin_access.can_read(h1)
assert h2.accessible_by(group_admin, {'read': True}) is False assert group_admin_access.can_read(h2) is False
my_group.hosts.remove(h1) my_group.hosts.remove(h1)
assert h1.accessible_by(inventory_admin, {'read': True}) assert inventory_admin_access.can_read(h1)
assert h1.accessible_by(group_admin, {'read': True}) is False assert group_admin_access.can_read(h1) is False
h1.inventory = other_inventory h1.inventory = other_inventory
h1.save() h1.save()
assert h1.accessible_by(inventory_admin, {'read': True}) is False assert inventory_admin_access.can_read(h1) is False
assert h1.accessible_by(group_admin, {'read': True}) is False assert group_admin_access.can_read(h1) is False
@@ -24,7 +24,7 @@ def test_admin_executing_permissions(deploy_jobtemplate, inventory, machine_cred
def test_job_template_start_access(deploy_jobtemplate, user): def test_job_template_start_access(deploy_jobtemplate, user):
common_user = user('test-user', False) common_user = user('test-user', False)
deploy_jobtemplate.executor_role.members.add(common_user) deploy_jobtemplate.execute_role.members.add(common_user)
assert common_user.can_access(JobTemplate, 'start', deploy_jobtemplate) assert common_user.can_access(JobTemplate, 'start', deploy_jobtemplate)
@@ -33,7 +33,7 @@ def test_job_template_start_access(deploy_jobtemplate, user):
def test_credential_use_access(machine_credential, user): def test_credential_use_access(machine_credential, user):
common_user = user('test-user', False) common_user = user('test-user', False)
machine_credential.usage_role.members.add(common_user) machine_credential.use_role.members.add(common_user)
assert common_user.can_access(Credential, 'use', machine_credential) assert common_user.can_access(Credential, 'use', machine_credential)
@@ -42,6 +42,6 @@ def test_credential_use_access(machine_credential, user):
def test_inventory_use_access(inventory, user): def test_inventory_use_access(inventory, user):
common_user = user('test-user', False) common_user = user('test-user', False)
inventory.usage_role.members.add(common_user) inventory.use_role.members.add(common_user)
assert common_user.can_access(Inventory, 'use', inventory) assert common_user.can_access(Inventory, 'use', inventory)
@@ -27,16 +27,16 @@ def test_job_template_migration_check(deploy_jobtemplate, check_jobtemplate, use
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert check_jobtemplate.project.accessible_by(joe, {'read': True}) assert joe in check_jobtemplate.project.read_role
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in check_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in check_jobtemplate.execute_role
rbac.migrate_job_templates(apps, None) rbac.migrate_job_templates(apps, None)
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in check_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in check_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in deploy_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in deploy_jobtemplate.execute_role
@pytest.mark.django_db @pytest.mark.django_db
def test_job_template_migration_deploy(deploy_jobtemplate, check_jobtemplate, user): def test_job_template_migration_deploy(deploy_jobtemplate, check_jobtemplate, user):
@@ -55,16 +55,16 @@ def test_job_template_migration_deploy(deploy_jobtemplate, check_jobtemplate, us
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert deploy_jobtemplate.project.accessible_by(joe, {'read': True}) assert joe in deploy_jobtemplate.project.read_role
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in deploy_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in deploy_jobtemplate.execute_role
rbac.migrate_job_templates(apps, None) rbac.migrate_job_templates(apps, None)
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in deploy_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in deploy_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in check_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in check_jobtemplate.execute_role
@pytest.mark.django_db @pytest.mark.django_db
@@ -87,17 +87,17 @@ def test_job_template_team_migration_check(deploy_jobtemplate, check_jobtemplate
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert check_jobtemplate.project.accessible_by(joe, {'read': True}) assert joe in check_jobtemplate.read_role
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in check_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in check_jobtemplate.execute_role
rbac.migrate_job_templates(apps, None) rbac.migrate_job_templates(apps, None)
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in check_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in check_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in deploy_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in deploy_jobtemplate.execute_role
@pytest.mark.django_db @pytest.mark.django_db
@@ -120,17 +120,17 @@ def test_job_template_team_deploy_migration(deploy_jobtemplate, check_jobtemplat
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
rbac.migrate_inventory(apps, None) rbac.migrate_inventory(apps, None)
assert deploy_jobtemplate.project.accessible_by(joe, {'read': True}) assert joe in deploy_jobtemplate.read_role
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in deploy_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False assert joe not in deploy_jobtemplate.execute_role
rbac.migrate_job_templates(apps, None) rbac.migrate_job_templates(apps, None)
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in deploy_jobtemplate.execute_role
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in deploy_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True assert admin in check_jobtemplate.execute_role
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True assert joe in check_jobtemplate.execute_role
@mock.patch.object(BaseAccess, 'check_license', return_value=None) @mock.patch.object(BaseAccess, 'check_license', return_value=None)
@@ -16,11 +16,11 @@ def test_organization_migration_admin(organization, permissions, user):
# Undo some automatic work that we're supposed to be testing with our migration # Undo some automatic work that we're supposed to be testing with our migration
organization.admin_role.members.remove(u) organization.admin_role.members.remove(u)
assert not organization.accessible_by(u, permissions['admin']) assert u not in organization.admin_role
rbac.migrate_organization(apps, None) rbac.migrate_organization(apps, None)
assert organization.accessible_by(u, permissions['admin']) assert u in organization.admin_role
@pytest.mark.django_db @pytest.mark.django_db
def test_organization_migration_user(organization, permissions, user): def test_organization_migration_user(organization, permissions, user):
@@ -29,11 +29,11 @@ def test_organization_migration_user(organization, permissions, user):
# Undo some automatic work that we're supposed to be testing with our migration # Undo some automatic work that we're supposed to be testing with our migration
organization.member_role.members.remove(u) organization.member_role.members.remove(u)
assert not organization.accessible_by(u, permissions['auditor']) assert u not in organization.read_role
rbac.migrate_organization(apps, None) rbac.migrate_organization(apps, None)
assert organization.accessible_by(u, permissions['auditor']) assert u in organization.read_role
@mock.patch.object(BaseAccess, 'check_license', return_value=None) @mock.patch.object(BaseAccess, 'check_license', return_value=None)
+16 -16
View File
@@ -138,11 +138,11 @@ def test_project_user_project(user_project, project, user):
assert old_access.check_user_access(u, user_project.__class__, 'read', user_project) assert old_access.check_user_access(u, user_project.__class__, 'read', user_project)
assert old_access.check_user_access(u, project.__class__, 'read', project) is False assert old_access.check_user_access(u, project.__class__, 'read', project) is False
assert user_project.accessible_by(u, {'read': True}) is False assert u not in user_project.read_role
assert project.accessible_by(u, {'read': True}) is False assert u not in project.read_role
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
assert user_project.accessible_by(u, {'read': True}) is True assert u in user_project.read_role
assert project.accessible_by(u, {'read': True}) is False assert u not in project.read_role
@pytest.mark.django_db @pytest.mark.django_db
def test_project_accessible_by_sa(user, project): def test_project_accessible_by_sa(user, project):
@@ -150,21 +150,21 @@ def test_project_accessible_by_sa(user, project):
# This gets setup by a signal, but we want to test the migration which will set this up too, so remove it # This gets setup by a signal, but we want to test the migration which will set this up too, so remove it
Role.singleton('System Administrator').members.remove(u) Role.singleton('System Administrator').members.remove(u)
assert project.accessible_by(u, {'read': True}) is False assert u not in project.read_role
rbac.migrate_organization(apps, None) rbac.migrate_organization(apps, None)
rbac.migrate_users(apps, None) rbac.migrate_users(apps, None)
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
print(project.admin_role.ancestors.all()) print(project.admin_role.ancestors.all())
print(project.admin_role.ancestors.all()) print(project.admin_role.ancestors.all())
assert project.accessible_by(u, {'read': True, 'write': True}) is True assert u in project.admin_role
@pytest.mark.django_db @pytest.mark.django_db
def test_project_org_members(user, organization, project): def test_project_org_members(user, organization, project):
admin = user('orgadmin') admin = user('orgadmin')
member = user('orgmember') member = user('orgmember')
assert project.accessible_by(admin, {'read': True}) is False assert admin not in project.read_role
assert project.accessible_by(member, {'read': True}) is False assert member not in project.read_role
organization.deprecated_admins.add(admin) organization.deprecated_admins.add(admin)
organization.deprecated_users.add(member) organization.deprecated_users.add(member)
@@ -172,8 +172,8 @@ def test_project_org_members(user, organization, project):
rbac.migrate_organization(apps, None) rbac.migrate_organization(apps, None)
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
assert project.accessible_by(admin, {'read': True, 'write': True}) is True assert admin in project.admin_role
assert project.accessible_by(member, {'read': True}) assert member in project.read_role
@pytest.mark.django_db @pytest.mark.django_db
def test_project_team(user, team, project): def test_project_team(user, team, project):
@@ -183,15 +183,15 @@ def test_project_team(user, team, project):
team.deprecated_users.add(member) team.deprecated_users.add(member)
project.deprecated_teams.add(team) project.deprecated_teams.add(team)
assert project.accessible_by(nonmember, {'read': True}) is False assert nonmember not in project.read_role
assert project.accessible_by(member, {'read': True}) is False assert member not in project.read_role
rbac.migrate_team(apps, None) rbac.migrate_team(apps, None)
rbac.migrate_organization(apps, None) rbac.migrate_organization(apps, None)
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
assert project.accessible_by(member, {'read': True}) is True assert member in project.read_role
assert project.accessible_by(nonmember, {'read': True}) is False assert nonmember not in project.read_role
@pytest.mark.django_db @pytest.mark.django_db
def test_project_explicit_permission(user, team, project, organization): def test_project_explicit_permission(user, team, project, organization):
@@ -203,9 +203,9 @@ def test_project_explicit_permission(user, team, project, organization):
p = Permission(user=u, project=project, permission_type='create', name='Perm name') p = Permission(user=u, project=project, permission_type='create', name='Perm name')
p.save() p.save()
assert project.accessible_by(u, {'read': True}) is False assert u not in project.read_role
rbac.migrate_organization(apps, None) rbac.migrate_organization(apps, None)
rbac.migrate_projects(apps, None) rbac.migrate_projects(apps, None)
assert project.accessible_by(u, {'read': True}) is True assert u in project.read_role
+6 -6
View File
@@ -54,20 +54,20 @@ def test_team_accessible_by(team, user, project):
u = user('team_member', False) u = user('team_member', False)
team.member_role.children.add(project.member_role) team.member_role.children.add(project.member_role)
assert project.accessible_by(team, {'read':True}) assert team in project.read_role
assert not project.accessible_by(u, {'read':True}) assert u not in project.read_role
team.member_role.members.add(u) team.member_role.members.add(u)
assert project.accessible_by(u, {'read':True}) assert u in project.read_role
@pytest.mark.django_db @pytest.mark.django_db
def test_team_accessible_objects(team, user, project): def test_team_accessible_objects(team, user, project):
u = user('team_member', False) u = user('team_member', False)
team.member_role.children.add(project.member_role) team.member_role.children.add(project.member_role)
assert len(Project.accessible_objects(team, {'read':True})) == 1 assert len(Project.accessible_objects(team, 'read_role')) == 1
assert not Project.accessible_objects(u, {'read':True}) assert not Project.accessible_objects(u, 'read_role')
team.member_role.members.add(u) team.member_role.members.add(u)
assert len(Project.accessible_objects(u, {'read':True})) == 1 assert len(Project.accessible_objects(u, 'read_role')) == 1
+8 -8
View File
@@ -40,14 +40,14 @@ def test_user_queryset(user):
def test_user_accessible_objects(user, organization): def test_user_accessible_objects(user, organization):
admin = user('admin', False) admin = user('admin', False)
u = user('john', False) u = user('john', False)
assert User.accessible_objects(admin, {'read':True}).count() == 1 assert User.accessible_objects(admin, 'admin_role').count() == 1
organization.member_role.members.add(u) organization.member_role.members.add(u)
organization.admin_role.members.add(admin) organization.admin_role.members.add(admin)
assert User.accessible_objects(admin, {'read':True}).count() == 2 assert User.accessible_objects(admin, 'admin_role').count() == 2
organization.member_role.members.remove(u) organization.member_role.members.remove(u)
assert User.accessible_objects(admin, {'read':True}).count() == 1 assert User.accessible_objects(admin, 'admin_role').count() == 1
@pytest.mark.django_db @pytest.mark.django_db
def test_org_user_admin(user, organization): def test_org_user_admin(user, organization):
@@ -55,13 +55,13 @@ def test_org_user_admin(user, organization):
member = user('orgmember') member = user('orgmember')
organization.member_role.members.add(member) organization.member_role.members.add(member)
assert not member.accessible_by(admin, {'write':True}) assert admin not in member.admin_role
organization.admin_role.members.add(admin) organization.admin_role.members.add(admin)
assert member.accessible_by(admin, {'write':True}) assert admin in member.admin_role
organization.admin_role.members.remove(admin) organization.admin_role.members.remove(admin)
assert not member.accessible_by(admin, {'write':True}) assert admin not in member.admin_role
@pytest.mark.django_db @pytest.mark.django_db
def test_org_user_removed(user, organization): def test_org_user_removed(user, organization):
@@ -71,7 +71,7 @@ def test_org_user_removed(user, organization):
organization.admin_role.members.add(admin) organization.admin_role.members.add(admin)
organization.member_role.members.add(member) organization.member_role.members.add(member)
assert member.accessible_by(admin, {'write':True}) assert admin in member.admin_role
organization.member_role.members.remove(member) organization.member_role.members.remove(member)
assert not member.accessible_by(admin, {'write':True}) assert admin not in member.admin_role
+10
View File
@@ -0,0 +1,10 @@
import pytest
@pytest.mark.django_db()
def test_admin_not_member(team):
"Test to ensure we don't add admin_role as a parent to team.member_role, as "
"this creates a cycle with organization administration, which we've decided "
"to remove support for"
assert team.admin_role.is_ancestor_of(team.member_role) is False
+11 -11
View File
@@ -295,14 +295,14 @@ class BaseJobTestMixin(BaseTestMixin):
password='ASK', password='ASK',
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_bob.usage_role.members.add(self.user_bob) self.cred_bob.use_role.members.add(self.user_bob)
self.cred_chuck = Credential.objects.create( self.cred_chuck = Credential.objects.create(
username='chuck', username='chuck',
ssh_key_data=TEST_SSH_KEY_DATA, ssh_key_data=TEST_SSH_KEY_DATA,
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_chuck.usage_role.members.add(self.user_chuck) self.cred_chuck.use_role.members.add(self.user_chuck)
self.cred_doug = Credential.objects.create( self.cred_doug = Credential.objects.create(
username='doug', username='doug',
@@ -310,7 +310,7 @@ class BaseJobTestMixin(BaseTestMixin):
'is why we dont\'t let doug actually run jobs.', 'is why we dont\'t let doug actually run jobs.',
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_doug.usage_role.members.add(self.user_doug) self.cred_doug.use_role.members.add(self.user_doug)
self.cred_eve = Credential.objects.create( self.cred_eve = Credential.objects.create(
username='eve', username='eve',
@@ -320,14 +320,14 @@ class BaseJobTestMixin(BaseTestMixin):
become_password='ASK', become_password='ASK',
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_eve.usage_role.members.add(self.user_eve) self.cred_eve.use_role.members.add(self.user_eve)
self.cred_frank = Credential.objects.create( self.cred_frank = Credential.objects.create(
username='frank', username='frank',
password='fr@nk the t@nk', password='fr@nk the t@nk',
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_frank.usage_role.members.add(self.user_frank) self.cred_frank.use_role.members.add(self.user_frank)
self.cred_greg = Credential.objects.create( self.cred_greg = Credential.objects.create(
username='greg', username='greg',
@@ -335,21 +335,21 @@ class BaseJobTestMixin(BaseTestMixin):
ssh_key_unlock='ASK', ssh_key_unlock='ASK',
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_greg.usage_role.members.add(self.user_greg) self.cred_greg.use_role.members.add(self.user_greg)
self.cred_holly = Credential.objects.create( self.cred_holly = Credential.objects.create(
username='holly', username='holly',
password='holly rocks', password='holly rocks',
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_holly.usage_role.members.add(self.user_holly) self.cred_holly.use_role.members.add(self.user_holly)
self.cred_iris = Credential.objects.create( self.cred_iris = Credential.objects.create(
username='iris', username='iris',
password='ASK', password='ASK',
created_by=self.user_sue, created_by=self.user_sue,
) )
self.cred_iris.usage_role.members.add(self.user_iris) self.cred_iris.use_role.members.add(self.user_iris)
# Each operations team also has shared credentials they can use. # Each operations team also has shared credentials they can use.
self.cred_ops_east = Credential.objects.create( self.cred_ops_east = Credential.objects.create(
@@ -358,14 +358,14 @@ class BaseJobTestMixin(BaseTestMixin):
ssh_key_unlock=TEST_SSH_KEY_DATA_UNLOCK, ssh_key_unlock=TEST_SSH_KEY_DATA_UNLOCK,
created_by = self.user_sue, created_by = self.user_sue,
) )
self.team_ops_east.member_role.children.add(self.cred_ops_east.usage_role) self.team_ops_east.member_role.children.add(self.cred_ops_east.use_role)
self.cred_ops_west = Credential.objects.create( self.cred_ops_west = Credential.objects.create(
username='west', username='west',
password='Heading270', password='Heading270',
created_by = self.user_sue, created_by = self.user_sue,
) )
self.team_ops_west.member_role.children.add(self.cred_ops_west.usage_role) self.team_ops_west.member_role.children.add(self.cred_ops_west.use_role)
# FIXME: This code can be removed (probably) # FIXME: This code can be removed (probably)
@@ -391,7 +391,7 @@ class BaseJobTestMixin(BaseTestMixin):
password='HeadingNone', password='HeadingNone',
created_by = self.user_sue, created_by = self.user_sue,
) )
self.team_ops_testers.member_role.children.add(self.cred_ops_test.usage_role) self.team_ops_testers.member_role.children.add(self.cred_ops_test.use_role)
self.ops_east_permission = Permission.objects.create( self.ops_east_permission = Permission.objects.create(
inventory = self.inv_ops_east, inventory = self.inv_ops_east,
+6 -6
View File
@@ -463,7 +463,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
# not allowed to run ad hoc commands). # not allowed to run ad hoc commands).
user_roles_list_url = reverse('api:user_roles_list', args=(self.other_django_user.pk,)) user_roles_list_url = reverse('api:user_roles_list', args=(self.other_django_user.pk,))
with self.current_user('admin'): with self.current_user('admin'):
response = self.post(user_roles_list_url, {"id": self.inventory.updater_role.id}, expect=204) response = self.post(user_roles_list_url, {"id": self.inventory.update_role.id}, expect=204)
with self.current_user('other'): with self.current_user('other'):
self.run_test_ad_hoc_command(expect=403) self.run_test_ad_hoc_command(expect=403)
self.check_get_list(url, 'other', qs) self.check_get_list(url, 'other', qs)
@@ -471,7 +471,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
# Add executor role permissions to other. Fails # Add executor role permissions to other. Fails
# when other user can't read credential. # when other user can't read credential.
with self.current_user('admin'): with self.current_user('admin'):
response = self.post(user_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) response = self.post(user_roles_list_url, {"id": self.inventory.execute_role.id}, expect=204)
with self.current_user('other'): with self.current_user('other'):
self.run_test_ad_hoc_command(expect=403) self.run_test_ad_hoc_command(expect=403)
@@ -504,7 +504,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
# Give the nobody user the run_ad_hoc_commands flag, and can now see # Give the nobody user the run_ad_hoc_commands flag, and can now see
# the one ad hoc command previously run. # the one ad hoc command previously run.
with self.current_user('admin'): with self.current_user('admin'):
response = self.post(nobody_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) response = self.post(nobody_roles_list_url, {"id": self.inventory.execute_role.id}, expect=204)
qs = AdHocCommand.objects.filter(credential_id=nobody_cred.pk) qs = AdHocCommand.objects.filter(credential_id=nobody_cred.pk)
self.assertEqual(qs.count(), 1) self.assertEqual(qs.count(), 1)
self.check_get_list(url, 'nobody', qs) self.check_get_list(url, 'nobody', qs)
@@ -1006,7 +1006,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
# can_run_ad_hoc_commands = True when we shouldn't. # can_run_ad_hoc_commands = True when we shouldn't.
nobody_roles_list_url = reverse('api:user_roles_list', args=(self.nobody_django_user.pk,)) nobody_roles_list_url = reverse('api:user_roles_list', args=(self.nobody_django_user.pk,))
with self.current_user('admin'): with self.current_user('admin'):
response = self.post(nobody_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) response = self.post(nobody_roles_list_url, {"id": self.inventory.execute_role.id}, expect=204)
# Create a credential for the other user and explicitly give other # Create a credential for the other user and explicitly give other
# user admin permission on the inventory (still not allowed to run ad # user admin permission on the inventory (still not allowed to run ad
@@ -1014,7 +1014,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
other_cred = self.create_test_credential(user=self.other_django_user) other_cred = self.create_test_credential(user=self.other_django_user)
user_roles_list_url = reverse('api:user_roles_list', args=(self.other_django_user.pk,)) user_roles_list_url = reverse('api:user_roles_list', args=(self.other_django_user.pk,))
with self.current_user('admin'): with self.current_user('admin'):
response = self.post(user_roles_list_url, {"id": self.inventory.updater_role.id}, expect=204) response = self.post(user_roles_list_url, {"id": self.inventory.update_role.id}, expect=204)
with self.current_user('other'): with self.current_user('other'):
response = self.get(url, expect=200) response = self.get(url, expect=200)
self.assertEqual(response['count'], 0) self.assertEqual(response['count'], 0)
@@ -1025,7 +1025,7 @@ class AdHocCommandApiTest(BaseAdHocCommandTest):
# Update permission to allow other user to run ad hoc commands. Can # Update permission to allow other user to run ad hoc commands. Can
# only see his own ad hoc commands (because of credential permission). # only see his own ad hoc commands (because of credential permission).
with self.current_user('admin'): with self.current_user('admin'):
response = self.post(user_roles_list_url, {"id": self.inventory.executor_role.id}, expect=204) response = self.post(user_roles_list_url, {"id": self.inventory.adhoc_role.id}, expect=204)
with self.current_user('other'): with self.current_user('other'):
response = self.get(url, expect=200) response = self.get(url, expect=200)
self.assertEqual(response['count'], 0) self.assertEqual(response['count'], 0)
@@ -952,7 +952,8 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
self.assertNotEqual(new_inv.groups.count(), 0) self.assertNotEqual(new_inv.groups.count(), 0)
self.assertNotEqual(new_inv.total_hosts, 0) self.assertNotEqual(new_inv.total_hosts, 0)
self.assertNotEqual(new_inv.total_groups, 0) self.assertNotEqual(new_inv.total_groups, 0)
self.assertElapsedLessThan(600) self.assertElapsedLessThan(1800) # TODO: We need to revisit this again to see if we can optimize this back to the sub-600 second range - anoek 2016-04-18
def _get_ngroups_for_nhosts(self, n): def _get_ngroups_for_nhosts(self, n):
if n > 0: if n > 0:
@@ -977,7 +978,7 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
self.assertEqual(new_inv.groups.count(), ngroups) self.assertEqual(new_inv.groups.count(), ngroups)
self.assertEqual(new_inv.total_hosts, nhosts) self.assertEqual(new_inv.total_hosts, nhosts)
self.assertEqual(new_inv.total_groups, ngroups) self.assertEqual(new_inv.total_groups, ngroups)
self.assertElapsedLessThan(1200) # FIXME: This should be < 120, will drop back down next sprint during our performance tuning work - anoek 2016-03-22 self.assertElapsedLessThan(180) # TODO: We need to revisit this again to see if we can optimize this back to the sub-120 second range - anoek 2016-04-18
@unittest.skipIf(getattr(settings, 'LOCAL_DEVELOPMENT', False), @unittest.skipIf(getattr(settings, 'LOCAL_DEVELOPMENT', False),
'Skip this test in local development environments, ' 'Skip this test in local development environments, '
+2 -2
View File
@@ -77,7 +77,7 @@ class InventoryTest(BaseTest):
self.check_get_list(url, self.normal_django_user, normal_qs) self.check_get_list(url, self.normal_django_user, normal_qs)
# a user who is on a team who has a read permissions on an inventory can see filtered inventories # a user who is on a team who has a read permissions on an inventory can see filtered inventories
other_qs = Inventory.accessible_objects(self.other_django_user, {'read': True}).distinct() other_qs = Inventory.accessible_objects(self.other_django_user, 'read_role').distinct()
self.check_get_list(url, self.other_django_user, other_qs) self.check_get_list(url, self.other_django_user, other_qs)
# a regular user not part of anything cannot see any inventories # a regular user not part of anything cannot see any inventories
@@ -401,7 +401,7 @@ class InventoryTest(BaseTest):
del_children_url = reverse('api:group_children_list', args=(del_group.pk,)) del_children_url = reverse('api:group_children_list', args=(del_group.pk,))
nondel_url = reverse('api:group_detail', nondel_url = reverse('api:group_detail',
args=(Group.objects.get(name='nondel').pk,)) args=(Group.objects.get(name='nondel').pk,))
assert(inv.accessible_by(self.normal_django_user, {'read': True})) assert self.normal_django_user in inv.read_role
del_group.delete() del_group.delete()
nondel_detail = self.get(nondel_url, expect=200, auth=self.get_normal_credentials()) nondel_detail = self.get(nondel_url, expect=200, auth=self.get_normal_credentials())
self.post(del_children_url, data=nondel_detail, expect=400, auth=self.get_normal_credentials()) self.post(del_children_url, data=nondel_detail, expect=400, auth=self.get_normal_credentials())
+1 -1
View File
@@ -289,7 +289,7 @@ class OrganizationsTest(BaseTest):
# post a completely new user to verify we can add users to the subcollection directly # post a completely new user to verify we can add users to the subcollection directly
new_user = dict(username='NewUser9000', password='NewPassword9000') new_user = dict(username='NewUser9000', password='NewPassword9000')
which_org = Organization.accessible_objects(self.normal_django_user, {'read': True, 'write': True})[0] which_org = Organization.accessible_objects(self.normal_django_user, 'admin_role')[0]
url = reverse('api:organization_users_list', args=(which_org.pk,)) url = reverse('api:organization_users_list', args=(which_org.pk,))
self.post(url, new_user, expect=201, auth=self.get_normal_credentials()) self.post(url, new_user, expect=201, auth=self.get_normal_credentials())
+2 -2
View File
@@ -420,10 +420,10 @@ class UsersTest(BaseTest):
self.assertEquals(data1['count'], 3) self.assertEquals(data1['count'], 3)
# Normal user can no longer see all users after the organization he # Normal user can no longer see all users after the organization he
# admins is marked inactive, nor can he see any other users that were # admins is marked inactive, nor can he see any other users that were
# in that org, so he only sees himself. # in that org, so he only sees himself and the system admin.
self.organizations[0].delete() self.organizations[0].delete()
data3 = self.get(url, expect=200, auth=self.get_normal_credentials()) data3 = self.get(url, expect=200, auth=self.get_normal_credentials())
self.assertEquals(data3['count'], 1) self.assertEquals(data3['count'], 2)
# Test no longer relevant since we've moved away from active / inactive. # Test no longer relevant since we've moved away from active / inactive.
# However there was talk about keeping is_active for users, so this test will # However there was talk about keeping is_active for users, so this test will
-1
View File
@@ -9,4 +9,3 @@ Alias /munin /var/www/html/munin/
require valid-user require valid-user
</Directory> </Directory>
ScriptAlias /munin-cgi/munin-cgi-graph /var/www/cgi-bin/munin-cgi-graph
+402
View File
@@ -0,0 +1,402 @@
#!/usr/bin/env python
# Copyright (c) 2016 Ansible, Inc.
# All Rights Reserved
import os
import sys
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings.development") # noqa
import django
django.setup() # noqa
# Python
from collections import defaultdict
from optparse import make_option, OptionParser
# Django
from django.utils.timezone import now
from django.contrib.auth.models import User
from django.db import transaction
# awx
from awx.main.models import * # noqa
option_list = [
make_option('--organizations', action='store', type='int', default=3,
help='Number of organizations to create'),
make_option('--users', action='store', type='int', default=10,
help='Number of users to create'),
make_option('--teams', action='store', type='int', default=5,
help='Number of teams to create'),
make_option('--projects', action='store', type='int', default=10,
help='Number of projects to create'),
make_option('--job-templates', action='store', type='int', default=20,
help='Number of job templates to create'),
make_option('--credentials', action='store', type='int', default=5,
help='Number of credentials to create'),
make_option('--inventories', action='store', type='int', default=5,
help='Number of credentials to create'),
make_option('--inventory-groups', action='store', type='int', default=10,
help='Number of credentials to create'),
make_option('--inventory-hosts', action='store', type='int', default=40,
help='number of credentials to create'),
make_option('--jobs', action='store', type='int', default=200,
help='number of job entries to create'),
make_option('--job-events', action='store', type='int', default=500,
help='number of job event entries to create'),
make_option('--pretend', action='store_true',
help="Don't commit the data to the database"),
make_option('--prefix', action='store', type='string', default='',
help="Prefix generated names with this string"),
#make_option('--spread-bias', action='store', type='string', default='exponential',
# help='"exponential" to bias associations exponentially front loaded for - for ex'),
]
parser = OptionParser(option_list=option_list)
options, remainder = parser.parse_args()
options = vars(options)
n_organizations = int(options['organizations'])
n_users = int(options['users'])
n_teams = int(options['teams'])
n_projects = int(options['projects'])
n_job_templates = int(options['job_templates'])
n_credentials = int(options['credentials'])
n_inventories = int(options['inventories'])
n_inventory_groups = int(options['inventory_groups'])
n_inventory_hosts = int(options['inventory_hosts'])
n_jobs = int(options['jobs'])
n_job_events = int(options['job_events'])
prefix = options['prefix']
organizations = []
users = []
teams = []
projects = []
job_templates = []
credentials = []
inventories = []
inventory_groups = []
inventory_hosts = []
jobs = []
#job_events = []
def spread(n, m):
ret = []
# At least one in each slot, split up the rest exponentially so the first
# buckets contain a lot of entries
for i in xrange(m):
if n > 0:
ret.append(1)
n -= 1
else:
ret.append(0)
for i in xrange(m):
n_in_this_slot = n // 2
n-= n_in_this_slot
ret[i] += n_in_this_slot
if n > 0 and len(ret):
ret[0] += n
return ret
ids = defaultdict(lambda: 0)
class Rollback(Exception):
pass
try:
with batch_role_ancestor_rebuilding():
with transaction.atomic():
admin, _ = User.objects.get_or_create(username = 'admin', is_superuser=True)
org_admin, _ = User.objects.get_or_create(username = 'org_admin')
org_member, _ = User.objects.get_or_create(username = 'org_member')
prj_admin, _ = User.objects.get_or_create(username = 'prj_admin')
jt_admin, _ = User.objects.get_or_create(username = 'jt_admin')
inv_admin, _ = User.objects.get_or_create(username = 'inv_admin')
admin.is_superuser = True
admin.save()
admin.set_password('test')
admin.save()
org_admin.set_password('test')
org_admin.save()
org_member.set_password('test')
org_member.save()
prj_admin.set_password('test')
prj_admin.save()
jt_admin.set_password('test')
jt_admin.save()
inv_admin.set_password('test')
inv_admin.save()
print('# Creating %d organizations' % n_organizations)
for i in xrange(n_organizations):
sys.stdout.write('\r%d ' % (i + 1))
sys.stdout.flush()
org = Organization.objects.create(name='%s Organization %d' % (prefix, i))
organizations.append(org)
if i == 0:
org.admin_role.members.add(org_admin)
org.member_role.members.add(org_admin)
org.member_role.members.add(org_member)
org.member_role.members.add(prj_admin)
org.member_role.members.add(jt_admin)
org.member_role.members.add(inv_admin)
print('')
print('# Creating %d users' % n_users)
org_idx = 0
for n in spread(n_users, n_organizations):
for i in range(n):
ids['user'] += 1
user_id = ids['user']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, organizations[org_idx].name, i+ 1))
sys.stdout.flush()
user = User.objects.create(username='%suser-%d' % (prefix, user_id))
organizations[org_idx].member_role.members.add(user)
users.append(user)
org_idx += 1
print('')
print('# Creating %d teams' % n_teams)
org_idx = 0
for n in spread(n_teams, n_organizations):
org = organizations[org_idx]
for i in range(n):
ids['team'] += 1
team_id = ids['team']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, org.name, i+ 1))
sys.stdout.flush()
team = Team.objects.create(name='%s Team %d Org %d' % (prefix, team_id, org_idx), organization=org)
teams.append(team)
org_idx += 1
print('')
print('# Adding users to teams')
for org in organizations:
org_teams = [t for t in org.teams.all()]
org_users = [u for u in org.member_role.members.all()]
print(' Spreading %d users accross %d teams for %s' % (len(org_users), len(org_teams), org.name))
# Our normal spread for most users
cur_user_idx = 0
cur_team_idx = 0
for n in spread(len(org_users), len(org_teams)):
team = org_teams[cur_team_idx]
for i in range(n):
if cur_user_idx < len(org_users):
user = org_users[cur_user_idx]
team.member_role.members.add(user)
cur_user_idx += 1
cur_team_idx += 1
# First user gets added to all teams
for team in org_teams:
team.member_role.members.add(org_users[0])
print('# Creating %d credentials for users' % (n_credentials - n_credentials // 2))
user_idx = 0
for n in spread(n_credentials - n_credentials // 2, n_users):
user = users[user_idx]
for i in range(n):
ids['credential'] += 1
sys.stdout.write('\r %d ' % (ids['credential']))
sys.stdout.flush()
credential_id = ids['credential']
credential = Credential.objects.create(name='%s Credential %d User %d' % (prefix, credential_id, user_idx))
credential.owner_role.members.add(user)
credentials.append(credential)
user_idx += 1
print('')
print('# Creating %d credentials for teams' % (n_credentials // 2))
team_idx = 0
starting_credential_id = ids['credential']
for n in spread(n_credentials - n_credentials // 2, n_teams):
team = teams[team_idx]
for i in range(n):
ids['credential'] += 1
sys.stdout.write('\r %d ' % (ids['credential'] - starting_credential_id))
sys.stdout.flush()
credential_id = ids['credential']
credential = Credential.objects.create(name='%s Credential %d team %d' % (prefix, credential_id, team_idx))
credential.owner_role.parents.add(team.member_role)
credentials.append(credential)
team_idx += 1
print('')
print('# Creating %d projects' % n_projects)
org_idx = 0
for n in spread(n_projects, n_organizations):
org = organizations[org_idx]
for i in range(n):
ids['project'] += 1
project_id = ids['project']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, org.name, i+ 1))
sys.stdout.flush()
project = Project.objects.create(name='%s Project %d Org %d' % (prefix, project_id, org_idx), organization=org)
projects.append(project)
if org_idx == 0 and i == 0:
project.admin_role.members.add(prj_admin)
org_idx += 1
print('')
print('# Creating %d inventories' % n_inventories)
org_idx = 0
for n in spread(n_inventories, min(n_inventories // 4 + 1, n_organizations)):
org = organizations[org_idx]
for i in range(n):
ids['inventory'] += 1
inventory_id = ids['inventory']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, org.name, i+ 1))
sys.stdout.flush()
inventory = Inventory.objects.create(name='%s Inventory %d Org %d' % (prefix, inventory_id, org_idx), organization=org)
inventories.append(inventory)
if org_idx == 0 and i == 0:
inventory.admin_role.members.add(inv_admin)
org_idx += 1
print('')
print('# Creating %d inventory_groups' % n_inventory_groups)
inv_idx = 0
for n in spread(n_inventory_groups, n_inventories):
inventory = inventories[inv_idx]
parent_list = [None] * 3
for i in range(n):
ids['group'] += 1
group_id = ids['group']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, inventory.name, i+ 1))
sys.stdout.flush()
group = Group.objects.create(
name='%s Group %d Inventory %d' % (prefix, group_id, inv_idx),
inventory=inventory,
)
# Have each group have up to 3 parent groups
for parent_n in range(3):
if i // 4 + parent_n < len(parent_list) and parent_list[i // 4 + parent_n]:
group.parents.add(parent_list[i // 4 + parent_n])
if parent_list[i // 4] is None:
parent_list[i // 4] = group
else:
parent_list.append(group)
inventory_groups.append(group)
inv_idx += 1
print('')
print('# Creating %d inventory_hosts' % n_inventory_hosts)
group_idx = 0
for n in spread(n_inventory_hosts, n_inventory_groups):
group = inventory_groups[group_idx]
for i in range(n):
ids['host'] += 1
host_id = ids['host']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, group.name, i+ 1))
sys.stdout.flush()
host = Host.objects.create(name='%s.host-%06d.group-%05d.dummy' % (prefix, host_id, group_idx), inventory=group.inventory)
# Add the host to up to 3 groups
host.groups.add(group)
for m in range(2):
if group_idx + m < len(inventory_groups) and group.inventory.id == inventory_groups[group_idx + m].inventory.id:
host.groups.add(inventory_groups[group_idx + m])
inventory_hosts.append(host)
group_idx += 1
print('')
print('# Creating %d job_templates' % n_job_templates)
project_idx = 0
inv_idx = 0
for n in spread(n_job_templates, n_projects):
project = projects[project_idx]
for i in range(n):
ids['job_template'] += 1
job_template_id = ids['job_template']
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, project.name, i+ 1))
sys.stdout.flush()
inventory = None
org_inv_count = project.organization.inventories.count()
if org_inv_count > 0:
inventory = project.organization.inventories.all()[inv_idx % org_inv_count]
job_template = JobTemplate.objects.create(
name='%s Job Template %d Project %d' % (prefix, job_template_id, project_idx),
inventory=inventory,
project=project,
)
job_templates.append(job_template)
inv_idx += 1
if project_idx == 0 and i == 0:
job_template.admin_role.members.add(jt_admin)
project_idx += 1
print('')
print('# Creating %d jobs' % n_jobs)
group_idx = 0
job_template_idx = 0
for n in spread(n_jobs, n_job_templates):
job_template = job_templates[job_template_idx]
for i in range(n):
sys.stdout.write('\r Assigning %d to %s: %d ' % (n, job_template.name, i+ 1))
sys.stdout.flush()
job = Job.objects.create(job_template=job_template)
jobs.append(job)
if job_template.inventory:
inv_groups = [g for g in job_template.inventory.groups.all()]
if len(inv_groups):
JobHostSummary.objects.bulk_create([
JobHostSummary(
job=job, host=h, host_name=h.name, processed=1,
created=now(), modified=now()
)
for h in inv_groups[group_idx % len(inv_groups)].hosts.all()[:100]
])
group_idx += 1
job_template_idx += 1
if n:
print('')
print('# Creating %d job events' % n_job_events)
job_idx = 0
for n in spread(n_job_events, n_jobs):
job = jobs[job_idx]
sys.stdout.write('\r Creating %d job events for job %d' % (n, job.id))
sys.stdout.flush()
JobEvent.objects.bulk_create([
JobEvent(
created=now(),
modified=now(),
job=job,
event='runner_on_ok'
)
for i in range(n)
])
job_idx += 1
if n:
print('')
if options['pretend']:
raise Rollback()
except Rollback:
print('Rolled back changes')
pass