Merge pull request #1256 from anoek/rbac

Misc RBAC fixes and enhancements
This commit is contained in:
Wayne Witzel III 2016-03-16 10:18:13 -04:00
commit 63d8efc268
14 changed files with 605 additions and 80 deletions

View File

@ -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):

View File

@ -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

View File

@ -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()

View 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

View File

@ -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',

View File

@ -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',

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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,

View File

@ -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']:

View File

@ -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.

View File

@ -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)