Merge pull request #1173 from wwitzel3/rbac

RBAC Access Updates
This commit is contained in:
Wayne Witzel III
2016-03-08 14:10:27 -05:00
13 changed files with 266 additions and 607 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0004_rbac_migrations'),
]
operations = [
migrations.RemoveField(
model_name='organization',
name='admins',
),
migrations.RemoveField(
model_name='organization',
name='users',
),
migrations.RemoveField(
model_name='team',
name='users',
),
]

View File

@@ -195,19 +195,24 @@ def migrate_job_templates(apps, schema_editor):
Permission = apps.get_model('main', 'Permission')
for jt in JobTemplate.objects.all():
permission = Permission.objects.filter(
inventory=jt.inventory,
project=jt.project,
active=True,
permission_type__in=['create', 'check', 'run'] if jt.job_type == 'check' else ['create', 'run'],
)
for team in Team.objects.all():
if Permission.objects.filter(
team=team,
inventory=jt.inventory,
project=jt.project,
active=True,
permission_type__in=['create', 'check', 'run'] if jt.job_type == 'check' else ['create', 'run']
):
team.member_role.children.add(jt.executor_role);
if permission.filter(team=team).exists():
team.member_role.children.add(jt.executor_role)
migrations[jt.name]['teams'].add(team)
for user in User.objects.all():
if permission.filter(user=user).exists():
jt.executor_role.members.add(user)
migrations[jt.name]['users'].add(user)
if jt.accessible_by(user, {'execute': True}):
# 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

View File

@@ -39,7 +39,6 @@ _PythonSerializer.handle_m2m_field = _new_handle_m2m_field
from django.contrib.auth.models import User # noqa
from awx.main.access import * # noqa
User.add_to_class('get_queryset', get_user_queryset)
User.add_to_class('can_access', check_user_access)
# Import signal handlers only after models have been defined.
import awx.main.signals # noqa

View File

@@ -51,7 +51,7 @@ class ResourceMixin(models.Model):
'''
perms = self.get_permissions(user)
if not perms:
if perms is None:
return False
for k in permissions:
if k not in perms or perms[k] < permissions[k]:

View File

@@ -18,7 +18,11 @@ from django.utils.translation import ugettext_lazy as _
# AWX
from awx.main.fields import AutoOneToOneField, ImplicitRoleField
from awx.main.models.base import * # noqa
from awx.main.models.rbac import ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR
from awx.main.models.rbac import (
ALL_PERMISSIONS,
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
ROLE_SINGLETON_SYSTEM_AUDITOR,
)
from awx.main.models.mixins import ResourceMixin
from awx.main.conf import tower_settings
@@ -53,7 +57,7 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
role_name='Organization Administrator',
role_description='May manage all aspects of this organization',
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
permissions = {'all': True}
permissions = ALL_PERMISSIONS,
)
auditor_role = ImplicitRoleField(
role_name='Organization Auditor',
@@ -113,7 +117,7 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
role_name='Team Administrator',
role_description='May manage this team',
parent_role='organization.admin_role',
permissions = {'all': True}
permissions = ALL_PERMISSIONS,
)
auditor_role = ImplicitRoleField(
role_name='Team Auditor',

View File

@@ -32,7 +32,8 @@ 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}
class Role(CommonModelNameNotUnique):
'''
@@ -123,18 +124,6 @@ class Role(CommonModelNameNotUnique):
def grant(self, resource, permissions):
# take either the raw Resource or something that includes the ResourceMixin
resource = resource if type(resource) is Resource else resource.resource
if 'all' in permissions and permissions['all']:
del permissions['all']
permissions['create'] = True
permissions['read'] = True
permissions['write'] = True
permissions['update'] = True
permissions['delete'] = True
permissions['scm_update'] = True
permissions['use'] = True
permissions['execute'] = True
permission = RolePermission(role=self, resource=resource)
for k in permissions:
setattr(permission, k, int(permissions[k]))
@@ -261,8 +250,8 @@ class RolePermission(CreatedModifiedModel):
create = models.IntegerField(default = 0)
read = models.IntegerField(default = 0)
write = models.IntegerField(default = 0)
update = 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)

View File

@@ -130,58 +130,6 @@ def sync_superuser_status_to_rbac(sender, instance, **kwargs):
else:
Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).members.remove(instance)
def sync_user_to_team_members_role(sender, reverse, model, instance, pk_set, action, **kwargs):
'When a user is added or removed from Team.users, ensure that is reflected in Team.member_role'
if action == 'post_add' or action == 'pre_remove':
if reverse:
for team in Team.objects.filter(id__in=pk_set).all():
if action == 'post_add':
team.member_role.members.add(instance)
if action == 'pre_remove':
team.member_role.members.remove(instance)
else:
for user in User.objects.filter(id__in=pk_set).all():
if action == 'post_add':
instance.member_role.members.add(user)
if action == 'pre_remove':
instance.member_role.members.remove(user)
def sync_admin_to_org_admin_role(sender, reverse, model, instance, pk_set, action, **kwargs):
'When a user is added or removed from Organization.admins, ensure that is reflected in Organization.admin_role'
if action == 'post_add' or action == 'pre_remove':
if reverse:
for org in Organization.objects.filter(id__in=pk_set).all():
if action == 'post_add':
org.admin_role.members.add(instance)
if action == 'pre_remove':
org.admin_role.members.remove(instance)
else:
for user in User.objects.filter(id__in=pk_set).all():
if action == 'post_add':
instance.admin_role.members.add(user)
if action == 'pre_remove':
instance.admin_role.members.remove(user)
def sync_user_to_org_members_role(sender, reverse, model, instance, pk_set, action, **kwargs):
'When a user is added or removed from Organization.users, ensure that is reflected in Organization.member_role'
if action == 'post_add' or action == 'pre_remove':
if reverse:
for org in Organization.objects.filter(id__in=pk_set).all():
if action == 'post_add':
org.member_role.members.add(instance)
org.admin_role.children.add(instance.resource.admin_role)
if action == 'pre_remove':
org.member_role.members.remove(instance)
org.admin_role.children.remove(instance.resource.admin_role)
else:
for user in User.objects.filter(id__in=pk_set).all():
if action == 'post_add':
instance.member_role.members.add(user)
instance.admin_role.children.add(user.resource.admin_role)
if action == 'pre_remove':
instance.member_role.members.remove(user)
instance.admin_role.children.remove(user.resource.admin_role)
def create_user_resource(sender, **kwargs):
instance = kwargs['instance']
try:
@@ -210,10 +158,7 @@ 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)
post_save.connect(sync_superuser_status_to_rbac, sender=User)
m2m_changed.connect(sync_user_to_team_members_role, Team.users.through)
post_save.connect(create_user_resource, sender=User)
m2m_changed.connect(sync_user_to_org_members_role, Organization.users.through)
m2m_changed.connect(sync_admin_to_org_admin_role, Organization.admins.through)
# Migrate hosts, groups to parent group(s) whenever a group is deleted or
# marked as inactive.

View File

@@ -69,11 +69,9 @@ def test_credential_access_superuser():
assert access.can_delete(credential)
@pytest.mark.django_db
def test_credential_access_admin(user, organization, team, credential):
def test_credential_access_admin(user, team, credential):
u = user('org-admin', False)
organization.admins.add(u)
team.organization = organization
team.save()
team.organization.admin_role.members.add(u)
access = CredentialAccess(u)
@@ -85,10 +83,16 @@ def test_credential_access_admin(user, organization, team, credential):
# unowned credential can be deleted
assert access.can_delete(credential)
team.users.add(u)
assert not access.can_change(credential, {'user': u.pk})
# credential is now part of a team
# that is part of an organization
# that I am an admin for
credential.team = team
credential.save()
credential.owner_role.rebuild_role_ancestor_list()
cred = Credential.objects.create(kind='aws', name='test-cred')
cred.team = team
cred.save()
# should have can_change access as org-admin
assert access.can_change(credential, {'user': u.pk})

View File

@@ -2,6 +2,7 @@ import pytest
from awx.main.migrations import _rbac as rbac
from awx.main.models import Permission
from awx.main.access import InventoryAccess
from django.apps import apps
@pytest.mark.django_db
@@ -195,3 +196,39 @@ def test_group_parent_admin(group, permissions, user):
parent2.admin_role.members.add(u)
assert childA.accessible_by(u, permissions['admin'])
@pytest.mark.django_db
def test_access_admin(organization, inventory, user):
a = user('admin', False)
inventory.organization = organization
organization.admin_role.members.add(a)
access = InventoryAccess(a)
assert access.can_read(inventory)
assert access.can_add(None)
assert access.can_add({'organization': organization.id})
assert access.can_change(inventory, None)
assert access.can_change(inventory, {'organization': organization.id})
assert access.can_admin(inventory, None)
assert access.can_admin(inventory, {'organization': organization.id})
assert access.can_delete(inventory)
assert access.can_run_ad_hoc_commands(inventory)
@pytest.mark.django_db
def test_access_auditor(organization, inventory, user):
u = user('admin', False)
inventory.organization = organization
organization.auditor_role.members.add(u)
access = InventoryAccess(u)
assert access.can_read(inventory)
assert not access.can_add(None)
assert not access.can_add({'organization': organization.id})
assert not access.can_change(inventory, None)
assert not access.can_change(inventory, {'organization': organization.id})
assert not access.can_admin(inventory, None)
assert not access.can_admin(inventory, {'organization': organization.id})
assert not access.can_delete(inventory)
assert not access.can_run_ad_hoc_commands(inventory)

View File

@@ -57,27 +57,27 @@ def test_organization_access_superuser(cl, organization, user):
def test_organization_access_admin(cl, organization, user):
'''can_change because I am an admin of that org'''
a = user('admin', False)
organization.admins.add(a)
organization.users.add(user('user', False))
organization.admin_role.members.add(a)
organization.member_role.members.add(user('user', False))
access = OrganizationAccess(a)
assert access.can_change(organization, None)
assert access.can_delete(organization)
org = access.get_queryset()[0]
assert len(org.admins.all()) == 1
assert len(org.users.all()) == 1
assert len(org.admin_role.members.all()) == 1
assert len(org.member_role.members.all()) == 1
@mock.patch.object(BaseAccess, 'check_license', return_value=None)
@pytest.mark.django_db
def test_organization_access_user(cl, organization, user):
access = OrganizationAccess(user('user', False))
organization.users.add(user('user', False))
organization.member_role.members.add(user('user', False))
assert not access.can_change(organization, None)
assert not access.can_delete(organization)
org = access.get_queryset()[0]
assert len(org.admins.all()) == 0
assert len(org.users.all()) == 1
assert len(org.admin_role.members.all()) == 0
assert len(org.member_role.members.all()) == 1

View File

@@ -22,7 +22,7 @@ def test_team_migration_user(team, user, permissions):
@pytest.mark.django_db
def test_team_access_superuser(team, user):
team.users.add(user('member', False))
team.member_role.members.add(user('member', False))
access = TeamAccess(user('admin', True))
@@ -31,13 +31,13 @@ def test_team_access_superuser(team, user):
assert access.can_delete(team)
t = access.get_queryset()[0]
assert len(t.users.all()) == 1
assert len(t.organization.admins.all()) == 0
assert len(t.member_role.members.all()) == 1
assert len(t.organization.admin_role.members.all()) == 0
@pytest.mark.django_db
def test_team_access_org_admin(organization, team, user):
a = user('admin', False)
organization.admins.add(a)
organization.admin_role.members.add(a)
team.organization = organization
team.save()
@@ -47,13 +47,13 @@ def test_team_access_org_admin(organization, team, user):
assert access.can_delete(team)
t = access.get_queryset()[0]
assert len(t.users.all()) == 0
assert len(t.organization.admins.all()) == 1
assert len(t.member_role.members.all()) == 0
assert len(t.organization.admin_role.members.all()) == 1
@pytest.mark.django_db
def test_team_access_member(organization, team, user):
u = user('member', False)
team.users.add(u)
team.member_role.members.add(u)
team.organization = organization
team.save()
@@ -63,6 +63,6 @@ def test_team_access_member(organization, team, user):
assert not access.can_delete(team)
t = access.get_queryset()[0]
assert len(t.users.all()) == 1
assert len(t.organization.admins.all()) == 0
assert len(t.member_role.members.all()) == 1
assert len(t.organization.admin_role.members.all()) == 0

View File

@@ -19,13 +19,13 @@ def test_org_user_admin(user, organization):
admin = user('orgadmin')
member = user('orgmember')
organization.users.add(member)
organization.member_role.members.add(member)
assert not member.resource.accessible_by(admin, {'write':True})
organization.admins.add(admin)
organization.admin_role.members.add(admin)
assert member.resource.accessible_by(admin, {'write':True})
organization.admins.remove(admin)
organization.admin_role.members.remove(admin)
assert not member.resource.accessible_by(admin, {'write':True})
@pytest.mark.django_db
@@ -33,10 +33,10 @@ def test_org_user_removed(user, organization):
admin = user('orgadmin')
member = user('orgmember')
organization.admins.add(admin)
organization.users.add(member)
organization.admin_role.members.add(admin)
organization.member_role.members.add(member)
assert member.resource.accessible_by(admin, {'write':True})
organization.users.remove(member)
organization.member_role.members.remove(member)
assert not member.resource.accessible_by(admin, {'write':True})