mirror of
https://github.com/ansible/awx.git
synced 2026-01-17 20:51:21 -03:30
Merge pull request #1256 from anoek/rbac
Misc RBAC fixes and enhancements
This commit is contained in:
commit
63d8efc268
@ -1555,7 +1555,7 @@ class InventoryRootGroupsList(SubListCreateAttachDetachAPIView):
|
||||
def get_queryset(self):
|
||||
parent = self.get_parent_object()
|
||||
self.check_parent_access(parent)
|
||||
qs = self.request.user.get_queryset(self.model)
|
||||
qs = self.request.user.get_queryset(self.model).distinct() # need distinct for '&' operator
|
||||
return qs & parent.root_groups
|
||||
|
||||
class BaseVariableData(RetrieveUpdateAPIView):
|
||||
|
||||
@ -322,7 +322,7 @@ class InventoryAccess(BaseAccess):
|
||||
model = Inventory
|
||||
|
||||
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': True})
|
||||
qs = qs.select_related('created_by', 'modified_by', 'organization')
|
||||
return qs
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ from django.db.transaction import TransactionManagementError
|
||||
|
||||
|
||||
# AWX
|
||||
from awx.main.models.rbac import RolePermission, Role
|
||||
from awx.main.models.rbac import RolePermission, Role, batch_role_ancestor_rebuilding
|
||||
|
||||
|
||||
__all__ = ['AutoOneToOneField', 'ImplicitRoleField']
|
||||
@ -126,7 +126,8 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
||||
if self.permissions is not None:
|
||||
permissions = RolePermission(
|
||||
role=role,
|
||||
resource=instance
|
||||
resource=instance,
|
||||
auto_generated=True
|
||||
)
|
||||
|
||||
if 'all' in self.permissions and self.permissions['all']:
|
||||
@ -279,12 +280,11 @@ class ImplicitRoleField(models.ForeignKey):
|
||||
for parent in parents:
|
||||
new_parents.add(parent)
|
||||
|
||||
Role.pause_role_ancestor_rebuilding()
|
||||
for role in original_parents - new_parents:
|
||||
this_role.parents.remove(role)
|
||||
for role in new_parents - original_parents:
|
||||
this_role.parents.add(role)
|
||||
Role.unpause_role_ancestor_rebuilding()
|
||||
with batch_role_ancestor_rebuilding():
|
||||
for role in original_parents - new_parents:
|
||||
this_role.parents.remove(role)
|
||||
for role in new_parents - original_parents:
|
||||
this_role.parents.add(role)
|
||||
|
||||
setattr(self, '__original_parent_roles', new_parents)
|
||||
|
||||
@ -293,4 +293,4 @@ class ImplicitRoleField(models.ForeignKey):
|
||||
children = [c for c in this_role.children.all()]
|
||||
this_role.delete()
|
||||
for child in children:
|
||||
children.rebuild_role_ancestor_list()
|
||||
child.rebuild_role_ancestor_list()
|
||||
|
||||
352
awx/main/management/commands/generate_dummy_data.py
Normal file
352
awx/main/management/commands/generate_dummy_data.py
Normal file
@ -0,0 +1,352 @@
|
||||
# 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), user=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), team=team)
|
||||
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
|
||||
@ -63,6 +63,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('created', models.DateTimeField(default=None, editable=False)),
|
||||
('modified', models.DateTimeField(default=None, editable=False)),
|
||||
('auto_generated', models.BooleanField(default=False)),
|
||||
('object_id', models.PositiveIntegerField(default=None)),
|
||||
('create', models.IntegerField(default=0)),
|
||||
('read', models.IntegerField(default=0)),
|
||||
@ -85,6 +86,11 @@ class Migration(migrations.Migration):
|
||||
name='owner_role',
|
||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='credential',
|
||||
name='auditor_role',
|
||||
field=awx.main.fields.ImplicitRoleField(related_name='+', to='main.Role', null=b'True'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='credential',
|
||||
name='usage_role',
|
||||
|
||||
@ -16,6 +16,10 @@ from awx.main.constants import CLOUD_PROVIDERS
|
||||
from awx.main.utils import decrypt_field
|
||||
from awx.main.models.base import * # noqa
|
||||
from awx.main.models.mixins import ResourceMixin
|
||||
from awx.main.models.rbac import (
|
||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||
ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||
)
|
||||
|
||||
__all__ = ['Credential']
|
||||
|
||||
@ -158,9 +162,20 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
||||
owner_role = ImplicitRoleField(
|
||||
role_name='Credential Owner',
|
||||
role_description='Owner of the credential',
|
||||
parent_role='team.admin_role',
|
||||
parent_role=[
|
||||
'team.admin_role',
|
||||
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||
],
|
||||
permissions = {'all': True}
|
||||
)
|
||||
auditor_role = ImplicitRoleField(
|
||||
role_name='Credential Auditor',
|
||||
role_description='Auditor of the credential',
|
||||
parent_role=[
|
||||
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||
],
|
||||
permissions = {'read': True}
|
||||
)
|
||||
usage_role = ImplicitRoleField(
|
||||
role_name='Credential User',
|
||||
role_description='May use this credential, but not read sensitive portions or modify it',
|
||||
|
||||
@ -3,9 +3,11 @@
|
||||
|
||||
# Python
|
||||
import logging
|
||||
import threading
|
||||
import contextlib
|
||||
|
||||
# Django
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.db.models.aggregates import Max
|
||||
from django.core.urlresolvers import reverse
|
||||
@ -19,6 +21,7 @@ from awx.main.models.base import * # noqa
|
||||
__all__ = [
|
||||
'Role',
|
||||
'RolePermission',
|
||||
'batch_role_ancestor_rebuilding',
|
||||
'get_user_permissions_on_resource',
|
||||
'get_role_permissions_on_resource',
|
||||
'ROLE_SINGLETON_SYSTEM_ADMINISTRATOR',
|
||||
@ -30,12 +33,43 @@ logger = logging.getLogger('awx.main.models.rbac')
|
||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator'
|
||||
ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor'
|
||||
|
||||
role_rebuilding_paused = False
|
||||
roles_needing_rebuilding = set()
|
||||
|
||||
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
|
||||
|
||||
@contextlib.contextmanager
|
||||
def batch_role_ancestor_rebuilding(allow_nesting=False):
|
||||
'''
|
||||
Batches the role ancestor rebuild work necessary whenever role-role
|
||||
relations change. This can result in a big speedup when performing
|
||||
any bulk manipulation.
|
||||
|
||||
WARNING: Calls to anything related to checking access/permissions
|
||||
while within the context of the batch_role_ancestor_rebuilding will
|
||||
likely not work.
|
||||
'''
|
||||
|
||||
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
||||
|
||||
try:
|
||||
setattr(tls, 'batch_role_rebuilding', True)
|
||||
if not batch_role_rebuilding:
|
||||
setattr(tls, 'roles_needing_rebuilding', set())
|
||||
yield
|
||||
|
||||
finally:
|
||||
setattr(tls, 'batch_role_rebuilding', batch_role_rebuilding)
|
||||
if not batch_role_rebuilding:
|
||||
rebuild_set = getattr(tls, 'roles_needing_rebuilding')
|
||||
with transaction.atomic():
|
||||
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')
|
||||
|
||||
|
||||
class Role(CommonModelNameNotUnique):
|
||||
'''
|
||||
Role model
|
||||
@ -61,35 +95,6 @@ class Role(CommonModelNameNotUnique):
|
||||
def get_absolute_url(self):
|
||||
return reverse('api:role_detail', args=(self.pk,))
|
||||
|
||||
@staticmethod
|
||||
def pause_role_ancestor_rebuilding():
|
||||
'''
|
||||
Pauses role ancestor list updating. This is useful when you're making
|
||||
many changes to the same roles, for example doing bulk inserts or
|
||||
making many changes to the same object in succession.
|
||||
|
||||
Note that the unpause_role_ancestor_rebuilding MUST be called within
|
||||
the same execution context (preferably within the same transaction),
|
||||
otherwise the RBAC role ancestor hierarchy will not be properly
|
||||
updated.
|
||||
'''
|
||||
|
||||
global role_rebuilding_paused
|
||||
role_rebuilding_paused = True
|
||||
|
||||
@staticmethod
|
||||
def unpause_role_ancestor_rebuilding():
|
||||
'''
|
||||
Unpauses the role ancestor list updating. This will will rebuild all
|
||||
roles that need updating since the last call to
|
||||
pause_role_ancestor_rebuilding and bring everything back into sync.
|
||||
'''
|
||||
global role_rebuilding_paused
|
||||
global roles_needing_rebuilding
|
||||
role_rebuilding_paused = False
|
||||
for role in Role.objects.filter(id__in=list(roles_needing_rebuilding)).all():
|
||||
role.rebuild_role_ancestor_list()
|
||||
roles_needing_rebuilding = set()
|
||||
|
||||
def rebuild_role_ancestor_list(self):
|
||||
'''
|
||||
@ -100,9 +105,11 @@ class Role(CommonModelNameNotUnique):
|
||||
|
||||
Note that this method relies on any parents' ancestor list being correct.
|
||||
'''
|
||||
global role_rebuilding_paused, roles_needing_rebuilding
|
||||
global tls
|
||||
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
||||
|
||||
if role_rebuilding_paused:
|
||||
if batch_role_rebuilding:
|
||||
roles_needing_rebuilding = getattr(tls, 'roles_needing_rebuilding')
|
||||
roles_needing_rebuilding.add(self.id)
|
||||
return
|
||||
|
||||
@ -160,6 +167,7 @@ class RolePermission(CreatedModifiedModel):
|
||||
content_type = models.ForeignKey(ContentType, null=False, default=None)
|
||||
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)
|
||||
|
||||
@ -133,6 +133,7 @@ def create_user_role(instance, **kwargs):
|
||||
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,
|
||||
)
|
||||
@ -152,6 +153,102 @@ def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
|
||||
if action == 'pre_remove':
|
||||
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_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
|
||||
post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Group)
|
||||
@ -168,6 +265,8 @@ post_save.connect(emit_job_event_detail, sender=JobEvent)
|
||||
post_save.connect(emit_ad_hoc_command_event_detail, sender=AdHocCommandEvent)
|
||||
m2m_changed.connect(rebuild_role_ancestor_list, Role.parents.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(create_user_role, sender=User)
|
||||
|
||||
|
||||
@ -134,11 +134,14 @@ def test_auto_field_adjuments(organization, inventory, team, alice):
|
||||
def test_implicit_deletes(alice):
|
||||
'Ensures implicit resources and roles delete themselves'
|
||||
delorg = Organization.objects.create(name='test-org')
|
||||
child = Role.objects.create(name='child-role')
|
||||
child.parents.add(delorg.admin_role)
|
||||
delorg.admin_role.members.add(alice)
|
||||
|
||||
admin_role_id = delorg.admin_role.id
|
||||
auditor_role_id = delorg.auditor_role.id
|
||||
|
||||
assert child.ancestors.count() > 1
|
||||
assert Role.objects.filter(id=admin_role_id).count() == 1
|
||||
assert Role.objects.filter(id=auditor_role_id).count() == 1
|
||||
n_alice_roles = alice.roles.count()
|
||||
@ -152,6 +155,9 @@ def test_implicit_deletes(alice):
|
||||
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 child.ancestors.count() == 1
|
||||
assert child.ancestors.all()[0] == child
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_content_object(user):
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from awx.main.migrations import _rbac as rbac
|
||||
from awx.main.models import Permission
|
||||
from awx.main.models import Permission, Host
|
||||
from awx.main.access import InventoryAccess
|
||||
from django.apps import apps
|
||||
|
||||
@ -232,3 +232,42 @@ def test_access_auditor(organization, inventory, user):
|
||||
assert not access.can_run_ad_hoc_commands(inventory)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_host_access(organization, inventory, user, group):
|
||||
other_inventory = organization.inventories.create(name='other-inventory')
|
||||
inventory_admin = user('inventory_admin', False)
|
||||
my_group = group('my-group')
|
||||
not_my_group = group('not-my-group')
|
||||
group_admin = user('group_admin', False)
|
||||
|
||||
|
||||
h1 = Host.objects.create(inventory=inventory, name='host1')
|
||||
h2 = Host.objects.create(inventory=inventory, name='host2')
|
||||
h1.groups.add(my_group)
|
||||
h2.groups.add(not_my_group)
|
||||
|
||||
assert h1.accessible_by(inventory_admin, {'read': True}) is False
|
||||
assert h1.accessible_by(group_admin, {'read': True}) is False
|
||||
|
||||
inventory.admin_role.members.add(inventory_admin)
|
||||
my_group.admin_role.members.add(group_admin)
|
||||
|
||||
assert h1.accessible_by(inventory_admin, {'read': True})
|
||||
assert h2.accessible_by(inventory_admin, {'read': True})
|
||||
assert h1.accessible_by(group_admin, {'read': True})
|
||||
assert h2.accessible_by(group_admin, {'read': True}) is False
|
||||
|
||||
my_group.hosts.remove(h1)
|
||||
|
||||
assert h1.accessible_by(inventory_admin, {'read': True})
|
||||
assert h1.accessible_by(group_admin, {'read': True}) is False
|
||||
|
||||
h1.inventory = other_inventory
|
||||
h1.save()
|
||||
|
||||
assert h1.accessible_by(inventory_admin, {'read': True}) is False
|
||||
assert h1.accessible_by(group_admin, {'read': True}) is False
|
||||
|
||||
|
||||
|
||||
|
||||
@ -66,68 +66,68 @@ class BaseJobTestMixin(BaseTestMixin):
|
||||
# Alex is Sue's IT assistant who can also administer all of the
|
||||
# organizations.
|
||||
self.user_alex = self.make_user('alex')
|
||||
self.org_eng.admins.add(self.user_alex)
|
||||
self.org_sup.admins.add(self.user_alex)
|
||||
self.org_ops.admins.add(self.user_alex)
|
||||
self.org_eng.admin_role.members.add(self.user_alex)
|
||||
self.org_sup.admin_role.members.add(self.user_alex)
|
||||
self.org_ops.admin_role.members.add(self.user_alex)
|
||||
|
||||
# Bob is the head of engineering. He's an admin for engineering, but
|
||||
# also a user within the operations organization (so he can see the
|
||||
# results if things go wrong in production).
|
||||
self.user_bob = self.make_user('bob')
|
||||
self.org_eng.admins.add(self.user_bob)
|
||||
self.org_ops.users.add(self.user_bob)
|
||||
self.org_eng.admin_role.members.add(self.user_bob)
|
||||
self.org_ops.member_role.members.add(self.user_bob)
|
||||
|
||||
# Chuck is the lead engineer. He has full reign over engineering, but
|
||||
# no other organizations.
|
||||
self.user_chuck = self.make_user('chuck')
|
||||
self.org_eng.admins.add(self.user_chuck)
|
||||
self.org_eng.admin_role.members.add(self.user_chuck)
|
||||
|
||||
# Doug is the other engineer working under Chuck. He can write
|
||||
# playbooks and check them, but Chuck doesn't quite think he's ready to
|
||||
# run them yet. Poor Doug.
|
||||
self.user_doug = self.make_user('doug')
|
||||
self.org_eng.users.add(self.user_doug)
|
||||
self.org_eng.member_role.members.add(self.user_doug)
|
||||
|
||||
# Juan is another engineer working under Chuck. He has a little more freedom
|
||||
# to run playbooks but can't create job templates
|
||||
self.user_juan = self.make_user('juan')
|
||||
self.org_eng.users.add(self.user_juan)
|
||||
self.org_eng.member_role.members.add(self.user_juan)
|
||||
|
||||
# Hannibal is Chuck's right-hand man. Chuck usually has him create the job
|
||||
# templates that the rest of the team will use
|
||||
self.user_hannibal = self.make_user('hannibal')
|
||||
self.org_eng.users.add(self.user_hannibal)
|
||||
self.org_eng.member_role.members.add(self.user_hannibal)
|
||||
|
||||
# Eve is the head of support. She can also see what goes on in
|
||||
# operations to help them troubleshoot problems.
|
||||
self.user_eve = self.make_user('eve')
|
||||
self.org_sup.admins.add(self.user_eve)
|
||||
self.org_ops.users.add(self.user_eve)
|
||||
self.org_sup.admin_role.members.add(self.user_eve)
|
||||
self.org_ops.member_role.members.add(self.user_eve)
|
||||
|
||||
# Frank is the other support guy.
|
||||
self.user_frank = self.make_user('frank')
|
||||
self.org_sup.users.add(self.user_frank)
|
||||
self.org_sup.member_role.members.add(self.user_frank)
|
||||
|
||||
# Greg is the head of operations.
|
||||
self.user_greg = self.make_user('greg')
|
||||
self.org_ops.admins.add(self.user_greg)
|
||||
self.org_ops.admin_role.members.add(self.user_greg)
|
||||
|
||||
# Holly is an operations engineer.
|
||||
self.user_holly = self.make_user('holly')
|
||||
self.org_ops.users.add(self.user_holly)
|
||||
self.org_ops.member_role.members.add(self.user_holly)
|
||||
|
||||
# Iris is another operations engineer.
|
||||
self.user_iris = self.make_user('iris')
|
||||
self.org_ops.users.add(self.user_iris)
|
||||
self.org_ops.member_role.members.add(self.user_iris)
|
||||
|
||||
# Randall and Billybob are new ops interns that ops uses to test
|
||||
# their playbooks and inventory
|
||||
self.user_randall = self.make_user('randall')
|
||||
self.org_ops.users.add(self.user_randall)
|
||||
self.org_ops.member_role.members.add(self.user_randall)
|
||||
|
||||
# He works with Randall
|
||||
self.user_billybob = self.make_user('billybob')
|
||||
self.org_ops.users.add(self.user_billybob)
|
||||
self.org_ops.member_role.members.add(self.user_billybob)
|
||||
|
||||
# Jim is the newest intern. He can login, but can't do anything quite yet
|
||||
# except make everyone else fresh coffee.
|
||||
@ -218,15 +218,15 @@ class BaseJobTestMixin(BaseTestMixin):
|
||||
created_by=self.user_sue)
|
||||
self.team_ops_east.projects.add(self.proj_prod)
|
||||
self.team_ops_east.projects.add(self.proj_prod_east)
|
||||
self.team_ops_east.users.add(self.user_greg)
|
||||
self.team_ops_east.users.add(self.user_holly)
|
||||
self.team_ops_east.member_role.members.add(self.user_greg)
|
||||
self.team_ops_east.member_role.members.add(self.user_holly)
|
||||
self.team_ops_west = self.org_ops.teams.create(
|
||||
name='westerners',
|
||||
created_by=self.user_sue)
|
||||
self.team_ops_west.projects.add(self.proj_prod)
|
||||
self.team_ops_west.projects.add(self.proj_prod_west)
|
||||
self.team_ops_west.users.add(self.user_greg)
|
||||
self.team_ops_west.users.add(self.user_iris)
|
||||
self.team_ops_west.member_role.members.add(self.user_greg)
|
||||
self.team_ops_west.member_role.members.add(self.user_iris)
|
||||
|
||||
# The south team is no longer active having been folded into the east team
|
||||
# FIXME: This code can be removed (probably)
|
||||
@ -240,7 +240,7 @@ class BaseJobTestMixin(BaseTestMixin):
|
||||
# active=False,
|
||||
#)
|
||||
#self.team_ops_south.projects.add(self.proj_prod)
|
||||
#self.team_ops_south.users.add(self.user_greg)
|
||||
#self.team_ops_south.member_role.members.add(self.user_greg)
|
||||
|
||||
# The north team is going to be deleted
|
||||
self.team_ops_north = self.org_ops.teams.create(
|
||||
@ -248,7 +248,7 @@ class BaseJobTestMixin(BaseTestMixin):
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.team_ops_north.projects.add(self.proj_prod)
|
||||
self.team_ops_north.users.add(self.user_greg)
|
||||
self.team_ops_north.member_role.members.add(self.user_greg)
|
||||
|
||||
# The testers team are interns that can only check playbooks but can't
|
||||
# run them
|
||||
@ -257,8 +257,8 @@ class BaseJobTestMixin(BaseTestMixin):
|
||||
created_by=self.user_sue,
|
||||
)
|
||||
self.team_ops_testers.projects.add(self.proj_prod)
|
||||
self.team_ops_testers.users.add(self.user_randall)
|
||||
self.team_ops_testers.users.add(self.user_billybob)
|
||||
self.team_ops_testers.member_role.members.add(self.user_randall)
|
||||
self.team_ops_testers.member_role.members.add(self.user_billybob)
|
||||
|
||||
# Each user has his/her own set of credentials.
|
||||
from awx.main.tests.data.ssh import (TEST_SSH_KEY_DATA,
|
||||
|
||||
@ -446,7 +446,7 @@ class ProjectsTest(BaseTransactionTest):
|
||||
self.post(team_users, data=dict(username='attempted_superuser_create', password='thepassword',
|
||||
is_superuser=True), expect=201, auth=self.get_super_credentials())
|
||||
|
||||
self.assertEqual(Team.objects.get(pk=team.pk).users.count(), 5)
|
||||
self.assertEqual(Team.objects.get(pk=team.pk).member_role.members.count(), 5)
|
||||
|
||||
# can remove users from teams
|
||||
for x in all_users['results']:
|
||||
|
||||
@ -210,11 +210,11 @@ def on_populate_user(sender, **kwargs):
|
||||
remove = bool(org_opts.get('remove', False))
|
||||
admins_opts = org_opts.get('admins', None)
|
||||
remove_admins = bool(org_opts.get('remove_admins', remove))
|
||||
_update_m2m_from_groups(user, ldap_user, org.admins, admins_opts,
|
||||
_update_m2m_from_groups(user, ldap_user, org.admin_role.members, admins_opts,
|
||||
remove_admins)
|
||||
users_opts = org_opts.get('users', None)
|
||||
remove_users = bool(org_opts.get('remove_users', remove))
|
||||
_update_m2m_from_groups(user, ldap_user, org.users, users_opts,
|
||||
_update_m2m_from_groups(user, ldap_user, org.member_role.members, users_opts,
|
||||
remove_users)
|
||||
|
||||
# Update team membership based on group memberships.
|
||||
@ -226,7 +226,7 @@ def on_populate_user(sender, **kwargs):
|
||||
team, created = Team.objects.get_or_create(name=team_name, organization=org)
|
||||
users_opts = team_opts.get('users', None)
|
||||
remove = bool(team_opts.get('remove', False))
|
||||
_update_m2m_from_groups(user, ldap_user, team.users, users_opts,
|
||||
_update_m2m_from_groups(user, ldap_user, team.member_role.users, users_opts,
|
||||
remove)
|
||||
|
||||
# Update user profile to store LDAP DN.
|
||||
|
||||
@ -98,12 +98,12 @@ def update_user_orgs(backend, details, user=None, *args, **kwargs):
|
||||
remove = bool(org_opts.get('remove', False))
|
||||
admins_expr = org_opts.get('admins', None)
|
||||
remove_admins = bool(org_opts.get('remove_admins', remove))
|
||||
_update_m2m_from_expression(user, org.admins, admins_expr, remove_admins)
|
||||
_update_m2m_from_expression(user, org.admin_role.members, admins_expr, remove_admins)
|
||||
|
||||
# Update org users from expression(s).
|
||||
users_expr = org_opts.get('users', None)
|
||||
remove_users = bool(org_opts.get('remove_users', remove))
|
||||
_update_m2m_from_expression(user, org.users, users_expr, remove_users)
|
||||
_update_m2m_from_expression(user, org.member_role.members, users_expr, remove_users)
|
||||
|
||||
|
||||
def update_user_teams(backend, details, user=None, *args, **kwargs):
|
||||
@ -134,4 +134,4 @@ def update_user_teams(backend, details, user=None, *args, **kwargs):
|
||||
team = Team.objects.get_or_create(name=team_name, organization=org)[0]
|
||||
users_expr = team_opts.get('users', None)
|
||||
remove = bool(team_opts.get('remove', False))
|
||||
_update_m2m_from_expression(user, team.users, users_expr, remove)
|
||||
_update_m2m_from_expression(user, team.member_role.members, users_expr, remove)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user