Dropped stored role name/description and other superflous fields

For name and description, we'll derive these from the role_field and
content type, which is much better for lots of reasons (eg changing text
the future). Also ditched the rest of the fields comming from the
standard common base model, we didn't use them and they cost several
indexes on the table.
This commit is contained in:
Akita Noek 2016-04-20 22:38:32 -04:00
parent aae548fbdc
commit 280993a15d
15 changed files with 88 additions and 133 deletions

View File

@ -92,9 +92,7 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
class ImplicitRoleField(models.ForeignKey):
"""Implicitly creates a role entry for a resource"""
def __init__(self, role_name=None, role_description=None, parent_role=None, *args, **kwargs):
self.role_name = role_name
self.role_description = role_description if role_description else ""
def __init__(self, parent_role=None, *args, **kwargs):
self.parent_role = parent_role
kwargs.setdefault('to', 'Role')
@ -104,8 +102,6 @@ class ImplicitRoleField(models.ForeignKey):
def deconstruct(self):
name, path, args, kwargs = super(ImplicitRoleField, self).deconstruct()
kwargs['role_name'] = self.role_name
kwargs['role_description'] = self.role_description
kwargs['parent_role'] = self.parent_role
return name, path, args, kwargs
@ -190,11 +186,7 @@ class ImplicitRoleField(models.ForeignKey):
if cur_role is None:
missing_roles.append(
Role_(
created=now(),
modified=now(),
role_field=implicit_role_field.name,
name=implicit_role_field.role_name,
description=implicit_role_field.role_description,
content_type_id=ct_id,
object_id=instance.id
)
@ -247,12 +239,7 @@ class ImplicitRoleField(models.ForeignKey):
if qs.count() >= 1:
role = qs[0]
else:
role = Role_.objects.create(created=now(),
modified=now(),
role_field=path,
singleton_name=singleton_name,
name=singleton_name,
description=singleton_name)
role = Role_.objects.create(singleton_name=singleton_name, role_field=singleton_name)
parents = [role.id]
else:
parents = resolve_role_field(instance, path)

View File

@ -44,9 +44,7 @@ def migrate_users(apps, schema_editor):
logger.info(smart_text(u"found existing role for user: {}".format(user.username)))
except Role.DoesNotExist:
role = Role.objects.create(
created=now(),
modified=now(),
singleton_name = smart_text(u'{}-admin_role'.format(user.username)),
role_field='admin_role',
content_type = user_content_type,
object_id = user.id
)
@ -54,14 +52,12 @@ def migrate_users(apps, schema_editor):
logger.info(smart_text(u"migrating to new role for user: {}".format(user.username)))
if user.is_superuser:
if Role.objects.filter(singleton_name='System Administrator').exists():
sa_role = Role.objects.get(singleton_name='System Administrator')
if Role.objects.filter(singleton_name='system_administrator').exists():
sa_role = Role.objects.get(singleton_name='system_administrator')
else:
sa_role = Role.objects.create(
created=now(),
modified=now(),
singleton_name='System Administrator',
name='System Administrator'
singleton_name='system_administrator',
role_field='system_administrator'
)
sa_role.members.add(user)

View File

@ -204,27 +204,19 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
help_text=_('Tenant identifier for this credential'),
)
owner_role = ImplicitRoleField(
role_name='Credential Owner',
role_description='Owner of the credential',
parent_role=[
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
],
)
auditor_role = ImplicitRoleField(
role_name='Credential Auditor',
role_description='Auditor of the credential',
parent_role=[
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
],
)
use_role = ImplicitRoleField(
role_name='Credential User',
role_description='May use this credential, but not read sensitive portions or modify it',
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'
],

View File

@ -97,39 +97,25 @@ class Inventory(CommonModel, ResourceMixin):
help_text=_('Number of external inventory sources in this inventory with failures.'),
)
admin_role = ImplicitRoleField(
role_name='Inventory Administrator',
role_description='May manage this inventory',
parent_role='organization.admin_role',
)
auditor_role = ImplicitRoleField(
role_name='Inventory Auditor',
role_description='May view but not modify this inventory',
parent_role='organization.auditor_role',
)
update_role = ImplicitRoleField(
role_name='Inventory Updater',
role_description='May update the inventory',
parent_role=['admin_role'],
)
use_role = ImplicitRoleField(
role_name='Inventory User',
role_description='May use this inventory, but not read sensitive portions or modify it',
parent_role=['admin_role'],
)
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_description='May execute jobs against this inventory',
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):
@ -531,28 +517,21 @@ class Group(CommonModelNameNotUnique, ResourceMixin):
help_text=_('Inventory source(s) that created or modified this group.'),
)
admin_role = ImplicitRoleField(
role_name='Inventory Group Administrator',
parent_role=['inventory.admin_role', 'parents.admin_role'],
)
auditor_role = ImplicitRoleField(
role_name='Inventory Group Auditor',
parent_role=['inventory.auditor_role', 'parents.auditor_role'],
)
update_role = ImplicitRoleField(
role_name='Inventory Group Updater',
parent_role=['inventory.update_role', 'parents.update_role', 'admin_role'],
)
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',
parent_role=['inventory.execute_role', 'parents.execute_role', 'adhoc_role'],
)
read_role = ImplicitRoleField(
role_name='Inventory Group Executor',
parent_role=['execute_role', 'update_role', 'auditor_role', 'admin_role'],
)
@ -1321,25 +1300,15 @@ class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):
)
admin_role = ImplicitRoleField(
role_name='CustomInventory Administrator',
role_description='May manage this inventory',
parent_role='organization.admin_role',
)
member_role = ImplicitRoleField(
role_name='CustomInventory Member',
role_description='May view but not modify this inventory',
parent_role='organization.member_role',
)
auditor_role = ImplicitRoleField(
role_name='CustomInventory Auditor',
role_description='May view but not modify this inventory',
parent_role='organization.auditor_role',
)
read_role = ImplicitRoleField(
role_name='CustomInventory Read',
role_description='May view but not modify this inventory',
parent_role=['auditor_role', 'member_role', 'admin_role'],
)

View File

@ -226,23 +226,15 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
default={},
)
admin_role = ImplicitRoleField(
role_name='Job Template Administrator',
role_description='Full access to all settings',
parent_role=[('project.admin_role', 'inventory.admin_role')]
)
auditor_role = ImplicitRoleField(
role_name='Job Template Auditor',
role_description='Read-only access to all settings',
parent_role=[('project.auditor_role', 'inventory.auditor_role')]
)
execute_role = ImplicitRoleField(
role_name='Job Template Runner',
role_description='May run the job template',
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'],
)

View File

@ -53,23 +53,15 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
related_name='deprecated_organizations',
)
admin_role = ImplicitRoleField(
role_name='Organization Administrator',
role_description='May manage all aspects of this organization',
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
)
auditor_role = ImplicitRoleField(
role_name='Organization Auditor',
role_description='May read all settings associated with this organization',
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
)
member_role = ImplicitRoleField(
role_name='Organization Member',
role_description='A member of this organization',
parent_role='admin_role',
)
read_role = ImplicitRoleField(
role_name='Organization Read Access',
role_description='Read an organization',
parent_role=['member_role', 'auditor_role'],
)
@ -110,22 +102,13 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
related_name='deprecated_teams',
)
admin_role = ImplicitRoleField(
role_name='Team Administrator',
role_description='May manage this team',
parent_role='organization.admin_role',
)
auditor_role = ImplicitRoleField(
role_name='Team Auditor',
role_description='May read all settings associated with this team',
parent_role='organization.auditor_role',
)
member_role = ImplicitRoleField(
role_name='Team Member',
role_description='A member of this team',
)
member_role = ImplicitRoleField()
read_role = ImplicitRoleField(
role_name='Read',
role_description='Can view this team',
parent_role=['admin_role', 'auditor_role', 'member_role'],
)

View File

@ -221,34 +221,24 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
blank=True,
)
admin_role = ImplicitRoleField(
role_name='Project Administrator',
role_description='May manage this project',
parent_role=[
'organization.admin_role',
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
],
)
auditor_role = ImplicitRoleField(
role_name='Project Auditor',
role_description='May read all settings associated with this project',
parent_role=[
'organization.auditor_role',
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
],
)
member_role = ImplicitRoleField(
role_name='Project Member',
role_description='Implies membership within this project',
parent_role='admin_role',
)
scm_update_role = ImplicitRoleField(
role_name='Project Updater',
role_description='May update this project from the source control management system',
parent_role='admin_role',
)
read_role = ImplicitRoleField(
role_name='Project Read Access',
role_description='Read access to this project',
parent_role=['member_role', 'auditor_role', 'scm_update_role'],
)

View File

@ -29,8 +29,39 @@ __all__ = [
logger = logging.getLogger('awx.main.models.rbac')
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator'
ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor'
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='system_administrator'
ROLE_SINGLETON_SYSTEM_AUDITOR='system_auditor'
role_names = {
'system_administrator' : 'System Administrator',
'system_auditor' : 'System Auditor',
'adhoc_role' : 'Ad Hoc',
'admin_role' : 'Admin',
'auditor_role' : 'Auditor',
'execute_role' : 'Execute',
'member_role' : 'Member',
'owner_role' : 'Owner',
'read_role' : 'Read',
'scm_update_role' : 'SCM Update',
'update_role' : 'Update',
'use_role' : 'Use',
}
role_descriptions = {
'system_administrator' : '[TODO] System Administrator',
'system_auditor' : '[TODO] System Auditor',
'adhoc_role' : '[TODO] Ad Hoc',
'admin_role' : '[TODO] Admin',
'auditor_role' : '[TODO] Auditor',
'execute_role' : '[TODO] Execute',
'member_role' : '[TODO] Member',
'owner_role' : '[TODO] Owner',
'read_role' : '[TODO] Read',
'scm_update_role' : '[TODO] SCM Update',
'update_role' : '[TODO] Update',
'use_role' : '[TODO] Use',
}
tls = threading.local() # thread local storage
@ -67,7 +98,7 @@ def batch_role_ancestor_rebuilding(allow_nesting=False):
delattr(tls, 'roles_needing_rebuilding')
class Role(CommonModelNameNotUnique):
class Role(models.Model):
'''
Role model
'''
@ -77,8 +108,8 @@ class Role(CommonModelNameNotUnique):
verbose_name_plural = _('roles')
db_table = 'main_rbac_roles'
role_field = models.TextField(null=False)
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')
implicit_parents = models.TextField(null=False, default='[]')
ancestors = models.ManyToManyField(
@ -123,6 +154,17 @@ class Role(CommonModelNameNotUnique):
'''
Role._simultaneous_ancestry_rebuild([self.id])
@property
def name(self):
global role_names
return role_names[self.role_field]
@property
def description(self):
global role_descriptions
return role_descriptions[self.role_field]
@staticmethod
def _simultaneous_ancestry_rebuild(role_ids_to_rebuild):
@ -226,12 +268,18 @@ class Role(CommonModelNameNotUnique):
'roles_table': Role._meta.db_table,
}
# SQLlite has a 1M sql statement limit.. since the django sqllite
# driver isn't letting us pass in the ids through the preferred
# parameter binding system, this function exists to obey this.
# est max 12 bytes per number, used up to 3 times in a query,
# minus 4k of padding for the other parts of the query, leads us
# to the magic number of 20748, or 20500 for a nice round number
def split_ids_for_sqlite(role_ids):
for i in xrange(0, len(role_ids), 999):
yield role_ids[i:i + 999]
for i in xrange(0, len(role_ids), 20500):
yield role_ids[i:i + 20500]
while role_ids_to_rebuild:
if loop_ct > 1000:
if loop_ct > 100:
raise Exception('Ancestry role rebuilding error: infinite loop detected')
loop_ct += 1
@ -313,7 +361,7 @@ class Role(CommonModelNameNotUnique):
@staticmethod
def singleton(name):
role, _ = Role.objects.get_or_create(singleton_name=name, name=name)
role, _ = Role.objects.get_or_create(singleton_name=name, role_field=name)
return role
def is_ancestor_of(self, role):

View File

@ -127,11 +127,10 @@ def create_user_role(instance, **kwargs):
Role.objects.get(
content_type=ContentType.objects.get_for_model(instance),
object_id=instance.id,
name = 'User Admin'
role_field='admin_role'
)
except Role.DoesNotExist:
role = Role.objects.create(
name = 'User Admin',
role_field='admin_role',
content_object = instance,
)

View File

@ -1,6 +1,7 @@
import pytest
from django.core.urlresolvers import reverse
from awx.main.models import Role
@pytest.mark.django_db
def test_indirect_access_list(get, organization, project, team_factory, user, admin):
@ -53,5 +54,5 @@ def test_indirect_access_list(get, organization, project, team_factory, user, ad
assert org_admin_team_member_entry['team_name'] == org_admin_team.name
admin_entry = admin_res['summary_fields']['indirect_access'][0]['role']
assert admin_entry['name'] == 'System Administrator'
assert admin_entry['name'] == Role.singleton('system_administrator').name

View File

@ -36,7 +36,6 @@ from awx.main.models.organization import (
Team,
)
from awx.main.models.rbac import Role
from awx.main.models.notifications import Notifier
'''
@ -193,11 +192,6 @@ def notifier(organization):
notification_type="webhook",
notification_configuration=dict(url="http://localhost",
headers={"Test": "Header"}))
@pytest.fixture
def role():
return Role.objects.create(name='role')
@pytest.fixture
def admin(user):
return user('admin', True)

View File

@ -10,6 +10,10 @@ def mock_feature_enabled(feature, bypass_database=None):
#@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.fixture
def role():
return Role.objects.create()
#
# /roles
@ -85,7 +89,7 @@ def test_get_user_roles_list(get, admin):
response = get(url, admin)
assert response.status_code == 200
roles = response.data
assert roles['count'] > 0 # 'System Administrator' role if nothing else
assert roles['count'] > 0 # 'system_administrator' role if nothing else
@pytest.mark.django_db
def test_user_view_other_user_roles(organization, inventory, team, get, alice, bob):

View File

@ -11,8 +11,8 @@ from awx.main.models import (
@pytest.mark.django_db
def test_auto_inheritance_by_children(organization, alice):
A = Role.objects.create(name='A', role_field='')
B = Role.objects.create(name='B', role_field='')
A = Role.objects.create()
B = Role.objects.create()
A.members.add(alice)
assert alice not in organization.admin_role
@ -38,8 +38,8 @@ def test_auto_inheritance_by_children(organization, alice):
@pytest.mark.django_db
def test_auto_inheritance_by_parents(organization, alice):
A = Role.objects.create(name='A')
B = Role.objects.create(name='B')
A = Role.objects.create()
B = Role.objects.create()
A.members.add(alice)
assert alice not in organization.admin_role
@ -58,9 +58,9 @@ def test_auto_inheritance_by_parents(organization, alice):
@pytest.mark.django_db
def test_accessible_objects(organization, alice, bob):
A = Role.objects.create(name='A')
A = Role.objects.create()
A.members.add(alice)
B = Role.objects.create(name='B')
B = Role.objects.create()
B.members.add(alice)
B.members.add(bob)
@ -118,7 +118,7 @@ def test_auto_field_adjustments(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 = Role.objects.create()
child.parents.add(delorg.admin_role)
delorg.admin_role.members.add(alice)
@ -129,14 +129,14 @@ def test_implicit_deletes(alice):
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()
n_system_admin_children = Role.singleton('System Administrator').children.count()
n_system_admin_children = Role.singleton('system_administrator').children.count()
delorg.delete()
assert Role.objects.filter(id=admin_role_id).count() == 0
assert Role.objects.filter(id=auditor_role_id).count() == 0
assert alice.roles.count() == (n_alice_roles - 1)
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.all()[0] == child
@ -152,11 +152,11 @@ def test_content_object(user):
def test_hierarchy_rebuilding_multi_path():
'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')
A = Role.objects.create(name='A')
B = Role.objects.create(name='B')
C = Role.objects.create(name='C')
D = Role.objects.create(name='D')
X = Role.objects.create()
A = Role.objects.create()
B = Role.objects.create()
C = Role.objects.create()
D = Role.objects.create()
A.children.add(B)
A.children.add(D)

View File

@ -148,7 +148,7 @@ def test_project_user_project(user_project, project, user):
def test_project_accessible_by_sa(user, project):
u = user('systemadmin', is_superuser=True)
# 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 u not in project.read_role
rbac.migrate_organization(apps, None)

View File

@ -13,7 +13,7 @@ def test_user_admin(user_project, project, user):
joe = user(username, is_superuser = False)
admin = user('admin', is_superuser = True)
sa = Role.singleton('System Administrator')
sa = Role.singleton('system_administrator')
# this should happen automatically with our signal
assert sa.members.filter(id=admin.id).exists() is True