mirror of
https://github.com/ansible/awx.git
synced 2026-03-21 10:57:36 -02:30
Merge pull request #962 from anoek/rbac
"Completion" of RBAC migrations; resource_field elimination
This commit is contained in:
@@ -91,9 +91,8 @@ class ImplicitResourceField(models.ForeignKey):
|
|||||||
class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
||||||
"""Descriptor Implict Role Fields. Auto-creates the appropriate role entry on first access"""
|
"""Descriptor Implict Role Fields. Auto-creates the appropriate role entry on first access"""
|
||||||
|
|
||||||
def __init__(self, role_name, resource_field, permissions, parent_role, *args, **kwargs):
|
def __init__(self, role_name, permissions, parent_role, *args, **kwargs):
|
||||||
self.role_name = role_name
|
self.role_name = role_name
|
||||||
self.resource_field = resource_field
|
|
||||||
self.permissions = permissions
|
self.permissions = permissions
|
||||||
self.parent_role = parent_role
|
self.parent_role = parent_role
|
||||||
|
|
||||||
@@ -143,10 +142,10 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
|||||||
setattr(instance, self.field.name, role)
|
setattr(instance, self.field.name, role)
|
||||||
instance.save(update_fields=[self.field.name,])
|
instance.save(update_fields=[self.field.name,])
|
||||||
|
|
||||||
if self.resource_field and self.permissions:
|
if self.permissions is not None:
|
||||||
permissions = RolePermission(
|
permissions = RolePermission(
|
||||||
role=role,
|
role=role,
|
||||||
resource=getattr(instance, self.resource_field)
|
resource=instance.resource
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'all' in self.permissions and self.permissions['all']:
|
if 'all' in self.permissions and self.permissions['all']:
|
||||||
@@ -170,9 +169,8 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
|||||||
class ImplicitRoleField(models.ForeignKey):
|
class ImplicitRoleField(models.ForeignKey):
|
||||||
"""Implicitly creates a role entry for a resource"""
|
"""Implicitly creates a role entry for a resource"""
|
||||||
|
|
||||||
def __init__(self, role_name=None, resource_field=None, permissions=None, parent_role=None, *args, **kwargs):
|
def __init__(self, role_name=None, permissions=None, parent_role=None, *args, **kwargs):
|
||||||
self.role_name = role_name
|
self.role_name = role_name
|
||||||
self.resource_field = resource_field
|
|
||||||
self.permissions = permissions
|
self.permissions = permissions
|
||||||
self.parent_role = parent_role
|
self.parent_role = parent_role
|
||||||
|
|
||||||
@@ -187,7 +185,6 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
self.name,
|
self.name,
|
||||||
ImplicitRoleDescriptor(
|
ImplicitRoleDescriptor(
|
||||||
self.role_name,
|
self.role_name,
|
||||||
self.resource_field,
|
|
||||||
self.permissions,
|
self.permissions,
|
||||||
self.parent_role,
|
self.parent_role,
|
||||||
self
|
self
|
||||||
@@ -211,7 +208,8 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
first_field_name = field_name.split('.')[0]
|
first_field_name = field_name.split('.')[0]
|
||||||
field = getattr(cls, first_field_name)
|
field = getattr(cls, first_field_name)
|
||||||
|
|
||||||
if type(field) is ReverseManyRelatedObjectsDescriptor:
|
if type(field) is ReverseManyRelatedObjectsDescriptor or \
|
||||||
|
type(field) is ManyRelatedObjectsDescriptor:
|
||||||
if found_m2m_field:
|
if found_m2m_field:
|
||||||
# This limitation is due to a lack of understanding on my part, the
|
# This limitation is due to a lack of understanding on my part, the
|
||||||
# trouble being that I can't seem to get m2m_changed to call anything that
|
# trouble being that I can't seem to get m2m_changed to call anything that
|
||||||
@@ -227,14 +225,17 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
found_m2m_field = True
|
found_m2m_field = True
|
||||||
self.m2m_field_name = first_field_name
|
self.m2m_field_name = first_field_name
|
||||||
self.m2m_field_attr = field_name.split('.',1)[1]
|
self.m2m_field_attr = field_name.split('.',1)[1]
|
||||||
m2m_changed.connect(self.m2m_update, field.through)
|
|
||||||
|
|
||||||
if type(field) is ManyRelatedObjectsDescriptor:
|
if type(field) is ReverseManyRelatedObjectsDescriptor:
|
||||||
raise Exception('ManyRelatedObjectsDescriptor references are currently unsupported ' +
|
m2m_changed.connect(self.m2m_update, field.through)
|
||||||
'(but the reverse is, so supporting this is probably easy to add)): %s.%s' %
|
else:
|
||||||
(cls.__name__, first_field_name))
|
m2m_changed.connect(self.m2m_update_related, field.related.through)
|
||||||
|
|
||||||
|
|
||||||
|
def m2m_update_related(self, **kwargs):
|
||||||
|
kwargs['reverse'] = not kwargs['reverse']
|
||||||
|
self.m2m_update(**kwargs)
|
||||||
|
|
||||||
def m2m_update(self, sender, instance, action, reverse, model, pk_set, **kwargs):
|
def m2m_update(self, sender, instance, action, reverse, model, pk_set, **kwargs):
|
||||||
if action == 'post_add' or action == 'pre_remove':
|
if action == 'post_add' or action == 'pre_remove':
|
||||||
if reverse:
|
if reverse:
|
||||||
|
|||||||
1693
awx/main/migrations/_old_access.py
Normal file
1693
awx/main/migrations/_old_access.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import _old_access as old_access
|
||||||
|
|
||||||
def migrate_users(apps, schema_editor):
|
def migrate_users(apps, schema_editor):
|
||||||
migrations = list()
|
migrations = list()
|
||||||
@@ -52,7 +53,7 @@ def migrate_inventory(apps, schema_editor):
|
|||||||
|
|
||||||
for inventory in Inventory.objects.all():
|
for inventory in Inventory.objects.all():
|
||||||
teams, users = [], []
|
teams, users = [], []
|
||||||
for perm in Permission.objects.filter(inventory=inventory):
|
for perm in Permission.objects.filter(inventory=inventory, active=True):
|
||||||
role = None
|
role = None
|
||||||
execrole = None
|
execrole = None
|
||||||
if perm.permission_type == 'admin':
|
if perm.permission_type == 'admin':
|
||||||
@@ -64,6 +65,10 @@ def migrate_inventory(apps, schema_editor):
|
|||||||
elif perm.permission_type == 'write':
|
elif perm.permission_type == 'write':
|
||||||
role = inventory.updater_role
|
role = inventory.updater_role
|
||||||
pass
|
pass
|
||||||
|
elif perm.permission_type == 'check':
|
||||||
|
pass
|
||||||
|
elif perm.permission_type == 'run':
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
raise Exception('Unhandled permission type for inventory: %s' % perm.permission_type)
|
raise Exception('Unhandled permission type for inventory: %s' % perm.permission_type)
|
||||||
if perm.run_ad_hoc_commands:
|
if perm.run_ad_hoc_commands:
|
||||||
@@ -108,7 +113,7 @@ def migrate_projects(apps, schema_editor):
|
|||||||
Permission = apps.get_model('main', 'Permission')
|
Permission = apps.get_model('main', 'Permission')
|
||||||
|
|
||||||
for project in Project.objects.all():
|
for project in Project.objects.all():
|
||||||
if project.organization is None and project.created_by is not None:
|
if project.organizations.count() == 0 and project.created_by is not None:
|
||||||
project.admin_role.members.add(project.created_by)
|
project.admin_role.members.add(project.created_by)
|
||||||
migrations[project.name]['users'].add(project.created_by)
|
migrations[project.name]['users'].add(project.created_by)
|
||||||
|
|
||||||
@@ -116,19 +121,98 @@ def migrate_projects(apps, schema_editor):
|
|||||||
team.member_role.children.add(project.member_role)
|
team.member_role.children.add(project.member_role)
|
||||||
migrations[project.name]['teams'].add(team)
|
migrations[project.name]['teams'].add(team)
|
||||||
|
|
||||||
if project.organization is not None:
|
if project.organizations.count() > 0:
|
||||||
for user in project.organization.users.all():
|
for org in project.organizations.all():
|
||||||
project.member_role.members.add(user)
|
for user in org.users.all():
|
||||||
migrations[project.name]['users'].add(user)
|
project.member_role.members.add(user)
|
||||||
|
migrations[project.name]['users'].add(user)
|
||||||
|
|
||||||
for perm in Permission.objects.filter(project=project):
|
for perm in Permission.objects.filter(project=project, active=True):
|
||||||
# All perms at this level just imply a user or team can read
|
# All perms at this level just imply a user or team can read
|
||||||
if perm.team:
|
if perm.team:
|
||||||
team.member_role.children.add(project.member_role)
|
perm.team.member_role.children.add(project.member_role)
|
||||||
migrations[project.name]['teams'].add(team)
|
migrations[project.name]['teams'].add(perm.team)
|
||||||
|
|
||||||
if perm.user:
|
if perm.user:
|
||||||
project.member_role.members.add(perm.user)
|
project.member_role.members.add(perm.user)
|
||||||
migrations[project.name]['users'].add(perm.user)
|
migrations[project.name]['users'].add(perm.user)
|
||||||
|
|
||||||
return migrations
|
return migrations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_job_templates(apps, schema_editor):
|
||||||
|
'''
|
||||||
|
NOTE: This must be run after orgs, inventory, projects, credential, and
|
||||||
|
users have been migrated
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
I can see job templates when:
|
||||||
|
X I am a superuser.
|
||||||
|
- I can read the inventory, project and credential (which means I am an
|
||||||
|
org admin or member of a team with access to all of the above).
|
||||||
|
- I have permission explicitly granted to check/deploy with the inventory
|
||||||
|
and project.
|
||||||
|
|
||||||
|
|
||||||
|
#This does not mean I would be able to launch a job from the template or
|
||||||
|
#edit the template.
|
||||||
|
- access.py can_read for JobTemplate enforces that you can only
|
||||||
|
see it if you can launch it, so the above imply launch too
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Tower administrators, organization administrators, and project
|
||||||
|
administrators, within a project under their purview, may create and modify
|
||||||
|
new job templates for that project.
|
||||||
|
|
||||||
|
When editing a job template, they may select among the inventory groups and
|
||||||
|
credentials in the organization for which they have usage permissions, or
|
||||||
|
they may leave either blank to be selected at runtime.
|
||||||
|
|
||||||
|
Additionally, they may specify one or more users/teams that have execution
|
||||||
|
permission for that job template, among the users/teams that are a member
|
||||||
|
of that project.
|
||||||
|
|
||||||
|
That execution permission is valid irrespective of any explicit permissions
|
||||||
|
the user has or has not been granted to the inventory group or credential
|
||||||
|
specified in the job template.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
migrations = defaultdict(lambda: defaultdict(set))
|
||||||
|
|
||||||
|
User = apps.get_model('auth', 'User')
|
||||||
|
JobTemplate = apps.get_model('main', 'JobTemplate')
|
||||||
|
Team = apps.get_model('main', 'Team')
|
||||||
|
Permission = apps.get_model('main', 'Permission')
|
||||||
|
|
||||||
|
for jt in JobTemplate.objects.all():
|
||||||
|
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);
|
||||||
|
migrations[jt.name]['teams'].add(team)
|
||||||
|
|
||||||
|
|
||||||
|
for user in User.objects.all():
|
||||||
|
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
|
||||||
|
# role entry for them
|
||||||
|
continue
|
||||||
|
|
||||||
|
if old_access.check_user_access(user, jt.__class__, 'start', jt, False):
|
||||||
|
jt.executor_role.members.add(user)
|
||||||
|
migrations[jt.name]['users'].add(user)
|
||||||
|
|
||||||
|
|
||||||
|
return migrations
|
||||||
|
|||||||
@@ -158,12 +158,10 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
|||||||
owner_role = ImplicitRoleField(
|
owner_role = ImplicitRoleField(
|
||||||
role_name='Credential Owner',
|
role_name='Credential Owner',
|
||||||
parent_role='team.admin_role',
|
parent_role='team.admin_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'all': True}
|
permissions = {'all': True}
|
||||||
)
|
)
|
||||||
usage_role = ImplicitRoleField(
|
usage_role = ImplicitRoleField(
|
||||||
role_name='Credential User',
|
role_name='Credential User',
|
||||||
resource_field='resource',
|
|
||||||
parent_role= 'team.member_role',
|
parent_role= 'team.member_role',
|
||||||
permissions = {'use': True}
|
permissions = {'use': True}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -96,13 +96,11 @@ class Inventory(CommonModel, ResourceMixin):
|
|||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Inventory Administrator',
|
role_name='Inventory Administrator',
|
||||||
parent_role='organization.admin_role',
|
parent_role='organization.admin_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'all': True}
|
permissions = {'all': True}
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Inventory Auditor',
|
role_name='Inventory Auditor',
|
||||||
parent_role='organization.auditor_role',
|
parent_role='organization.auditor_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
updater_role = ImplicitRoleField(
|
updater_role = ImplicitRoleField(
|
||||||
@@ -545,25 +543,21 @@ class Group(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Administrator',
|
role_name='Inventory Group Administrator',
|
||||||
parent_role=['inventory.admin_role', 'parents.admin_role'],
|
parent_role=['inventory.admin_role', 'parents.admin_role'],
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'all': True}
|
permissions = {'all': True}
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Auditor',
|
role_name='Inventory Group Auditor',
|
||||||
parent_role=['inventory.auditor_role', 'parents.auditor_role'],
|
parent_role=['inventory.auditor_role', 'parents.auditor_role'],
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
updater_role = ImplicitRoleField(
|
updater_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Updater',
|
role_name='Inventory Group Updater',
|
||||||
parent_role=['inventory.updater_role', 'parents.updater_role'],
|
parent_role=['inventory.updater_role', 'parents.updater_role'],
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True, 'write': True, 'create': True, 'use': True},
|
permissions = {'read': True, 'write': True, 'create': True, 'use': True},
|
||||||
)
|
)
|
||||||
executor_role = ImplicitRoleField(
|
executor_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Executor',
|
role_name='Inventory Group Executor',
|
||||||
parent_role=['inventory.executor_role', 'parents.executor_role'],
|
parent_role=['inventory.executor_role', 'parents.executor_role'],
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read':True, 'execute':True},
|
permissions = {'read':True, 'execute':True},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -184,20 +184,16 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
|||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Job Template Administrator',
|
role_name='Job Template Administrator',
|
||||||
parent_role='project.admin_role',
|
parent_role='project.admin_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'all': True}
|
permissions = {'all': True}
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Job Template Auditor',
|
role_name='Job Template Auditor',
|
||||||
parent_role='project.auditor_role',
|
parent_role='project.auditor_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
executor_role = ImplicitRoleField(
|
executor_role = ImplicitRoleField(
|
||||||
role_name='Job Template Executor',
|
role_name='Job Template Executor',
|
||||||
parent_role='project.auditor_role',
|
permissions = {'read': True, 'execute': True}
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'execute': True}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -43,10 +43,6 @@ class Organization(CommonModel, ResourceMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
related_name='admin_of_organizations',
|
related_name='admin_of_organizations',
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: This field is deprecated. In 3.0 all projects will have exactly one
|
|
||||||
# organization parent, the foreign key field representing that has been
|
|
||||||
# moved to the Project model.
|
|
||||||
projects = models.ManyToManyField(
|
projects = models.ManyToManyField(
|
||||||
'Project',
|
'Project',
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -55,18 +51,15 @@ class Organization(CommonModel, ResourceMixin):
|
|||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Organization Administrator',
|
role_name='Organization Administrator',
|
||||||
parent_role='singleton:System Administrator',
|
parent_role='singleton:System Administrator',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'all': True}
|
permissions = {'all': True}
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Organization Auditor',
|
role_name='Organization Auditor',
|
||||||
parent_role='singleton:System Auditor',
|
parent_role='singleton:System Auditor',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
member_role = ImplicitRoleField(
|
member_role = ImplicitRoleField(
|
||||||
role_name='Organization Member',
|
role_name='Organization Member',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,19 +107,16 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Team Administrator',
|
role_name='Team Administrator',
|
||||||
parent_role='organization.admin_role',
|
parent_role='organization.admin_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'all': True}
|
permissions = {'all': True}
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Team Auditor',
|
role_name='Team Auditor',
|
||||||
parent_role='organization.auditor_role',
|
parent_role='organization.auditor_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
member_role = ImplicitRoleField(
|
member_role = ImplicitRoleField(
|
||||||
role_name='Team Member',
|
role_name='Team Member',
|
||||||
parent_role='admin_role',
|
parent_role='admin_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read':True},
|
permissions = {'read':True},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -196,14 +196,6 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
|
|||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
|
|
||||||
organization = models.ForeignKey(
|
|
||||||
'Organization',
|
|
||||||
blank=False,
|
|
||||||
null=True,
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
related_name='project_list', # TODO: this should eventually be refactored
|
|
||||||
# back to 'projects' - anoek 2016-01-28
|
|
||||||
)
|
|
||||||
scm_delete_on_next_update = models.BooleanField(
|
scm_delete_on_next_update = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
editable=False,
|
editable=False,
|
||||||
@@ -217,25 +209,21 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
|
|||||||
)
|
)
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Project Administrator',
|
role_name='Project Administrator',
|
||||||
parent_role='organization.admin_role',
|
parent_role='organizations.admin_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'all': True}
|
permissions = {'all': True}
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Project Auditor',
|
role_name='Project Auditor',
|
||||||
parent_role='organization.auditor_role',
|
parent_role='organizations.auditor_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
member_role = ImplicitRoleField(
|
member_role = ImplicitRoleField(
|
||||||
role_name='Project Member',
|
role_name='Project Member',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'read': True}
|
permissions = {'read': True}
|
||||||
)
|
)
|
||||||
scm_update_role = ImplicitRoleField(
|
scm_update_role = ImplicitRoleField(
|
||||||
role_name='Project Updater',
|
role_name='Project Updater',
|
||||||
parent_role='admin_role',
|
parent_role='admin_role',
|
||||||
resource_field='resource',
|
|
||||||
permissions = {'scm_update': True}
|
permissions = {'scm_update': True}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from awx.main.models.inventory import (
|
|||||||
Group,
|
Group,
|
||||||
)
|
)
|
||||||
from awx.main.models.projects import Project
|
from awx.main.models.projects import Project
|
||||||
|
from awx.main.models.jobs import JobTemplate
|
||||||
from awx.main.models.organization import (
|
from awx.main.models.organization import (
|
||||||
Organization,
|
Organization,
|
||||||
Team,
|
Team,
|
||||||
@@ -23,13 +24,37 @@ def user():
|
|||||||
return user
|
return user
|
||||||
return u
|
return u
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def check_jobtemplate(project, inventory, credential):
|
||||||
|
return \
|
||||||
|
JobTemplate.objects.create(
|
||||||
|
job_type='check',
|
||||||
|
project=project,
|
||||||
|
inventory=inventory,
|
||||||
|
credential=credential,
|
||||||
|
name='check-job-template'
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def deploy_jobtemplate(project, inventory, credential):
|
||||||
|
return \
|
||||||
|
JobTemplate.objects.create(
|
||||||
|
job_type='run',
|
||||||
|
project=project,
|
||||||
|
inventory=inventory,
|
||||||
|
credential=credential,
|
||||||
|
name='deploy-job-template'
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def team(organization):
|
def team(organization):
|
||||||
return Team.objects.create(organization=organization, name='test-team')
|
return Team.objects.create(organization=organization, name='test-team')
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def project(organization):
|
def project(organization):
|
||||||
return Project.objects.create(name="test-project", organization=organization, description="test-project-desc")
|
prj = Project.objects.create(name="test-project", description="test-project-desc")
|
||||||
|
prj.organizations.add(organization)
|
||||||
|
return prj
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user_project(user):
|
def user_project(user):
|
||||||
|
|||||||
133
awx/main/tests/functional/test_rbac_job_templates.py
Normal file
133
awx/main/tests/functional/test_rbac_job_templates.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from awx.main.migrations import _rbac as rbac
|
||||||
|
from awx.main.models import Permission
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_job_template_migration_check(deploy_jobtemplate, check_jobtemplate, user):
|
||||||
|
admin = user('admin', is_superuser=True)
|
||||||
|
joe = user('joe')
|
||||||
|
|
||||||
|
|
||||||
|
check_jobtemplate.project.organizations.all()[0].users.add(joe)
|
||||||
|
|
||||||
|
Permission(user=joe, inventory=check_jobtemplate.inventory, permission_type='read').save()
|
||||||
|
Permission(user=joe, inventory=check_jobtemplate.inventory,
|
||||||
|
project=check_jobtemplate.project, permission_type='check').save()
|
||||||
|
|
||||||
|
rbac.migrate_users(apps, None)
|
||||||
|
rbac.migrate_organization(apps, None)
|
||||||
|
rbac.migrate_projects(apps, None)
|
||||||
|
rbac.migrate_inventory(apps, None)
|
||||||
|
|
||||||
|
assert check_jobtemplate.project.accessible_by(joe, {'read': True})
|
||||||
|
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False
|
||||||
|
|
||||||
|
migrations = rbac.migrate_job_templates(apps, None)
|
||||||
|
|
||||||
|
assert len(migrations[check_jobtemplate.name]['users']) == 1
|
||||||
|
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True
|
||||||
|
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_job_template_migration_deploy(deploy_jobtemplate, check_jobtemplate, user):
|
||||||
|
admin = user('admin', is_superuser=True)
|
||||||
|
joe = user('joe')
|
||||||
|
|
||||||
|
|
||||||
|
deploy_jobtemplate.project.organizations.all()[0].users.add(joe)
|
||||||
|
|
||||||
|
Permission(user=joe, inventory=deploy_jobtemplate.inventory, permission_type='read').save()
|
||||||
|
Permission(user=joe, inventory=deploy_jobtemplate.inventory,
|
||||||
|
project=deploy_jobtemplate.project, permission_type='run').save()
|
||||||
|
|
||||||
|
rbac.migrate_users(apps, None)
|
||||||
|
rbac.migrate_organization(apps, None)
|
||||||
|
rbac.migrate_projects(apps, None)
|
||||||
|
rbac.migrate_inventory(apps, None)
|
||||||
|
|
||||||
|
assert deploy_jobtemplate.project.accessible_by(joe, {'read': True})
|
||||||
|
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False
|
||||||
|
|
||||||
|
migrations = rbac.migrate_job_templates(apps, None)
|
||||||
|
|
||||||
|
assert len(migrations[deploy_jobtemplate.name]['users']) == 1
|
||||||
|
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is True
|
||||||
|
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_job_template_team_migration_check(deploy_jobtemplate, check_jobtemplate, organization, team, user):
|
||||||
|
admin = user('admin', is_superuser=True)
|
||||||
|
joe = user('joe')
|
||||||
|
team.users.add(joe)
|
||||||
|
team.organization = organization
|
||||||
|
team.save()
|
||||||
|
|
||||||
|
check_jobtemplate.project.organizations.all()[0].users.add(joe)
|
||||||
|
|
||||||
|
Permission(team=team, inventory=check_jobtemplate.inventory, permission_type='read').save()
|
||||||
|
Permission(team=team, inventory=check_jobtemplate.inventory,
|
||||||
|
project=check_jobtemplate.project, permission_type='check').save()
|
||||||
|
|
||||||
|
rbac.migrate_users(apps, None)
|
||||||
|
rbac.migrate_team(apps, None)
|
||||||
|
rbac.migrate_organization(apps, None)
|
||||||
|
rbac.migrate_projects(apps, None)
|
||||||
|
rbac.migrate_inventory(apps, None)
|
||||||
|
|
||||||
|
assert check_jobtemplate.project.accessible_by(joe, {'read': True})
|
||||||
|
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is False
|
||||||
|
|
||||||
|
migrations = rbac.migrate_job_templates(apps, None)
|
||||||
|
|
||||||
|
assert len(migrations[check_jobtemplate.name]['users']) == 0
|
||||||
|
assert len(migrations[check_jobtemplate.name]['teams']) == 1
|
||||||
|
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True
|
||||||
|
|
||||||
|
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_job_template_team_deploy_migration(deploy_jobtemplate, check_jobtemplate, organization, team, user):
|
||||||
|
admin = user('admin', is_superuser=True)
|
||||||
|
joe = user('joe')
|
||||||
|
team.users.add(joe)
|
||||||
|
team.organization = organization
|
||||||
|
team.save()
|
||||||
|
|
||||||
|
deploy_jobtemplate.project.organizations.all()[0].users.add(joe)
|
||||||
|
|
||||||
|
Permission(team=team, inventory=deploy_jobtemplate.inventory, permission_type='read').save()
|
||||||
|
Permission(team=team, inventory=deploy_jobtemplate.inventory,
|
||||||
|
project=deploy_jobtemplate.project, permission_type='run').save()
|
||||||
|
|
||||||
|
rbac.migrate_users(apps, None)
|
||||||
|
rbac.migrate_team(apps, None)
|
||||||
|
rbac.migrate_organization(apps, None)
|
||||||
|
rbac.migrate_projects(apps, None)
|
||||||
|
rbac.migrate_inventory(apps, None)
|
||||||
|
|
||||||
|
assert deploy_jobtemplate.project.accessible_by(joe, {'read': True})
|
||||||
|
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is False
|
||||||
|
|
||||||
|
migrations = rbac.migrate_job_templates(apps, None)
|
||||||
|
|
||||||
|
assert len(migrations[deploy_jobtemplate.name]['users']) == 0
|
||||||
|
assert len(migrations[deploy_jobtemplate.name]['teams']) == 1
|
||||||
|
assert deploy_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert deploy_jobtemplate.accessible_by(joe, {'execute': True}) is True
|
||||||
|
|
||||||
|
assert check_jobtemplate.accessible_by(admin, {'execute': True}) is True
|
||||||
|
assert check_jobtemplate.accessible_by(joe, {'execute': True}) is True
|
||||||
@@ -3,10 +3,16 @@ import pytest
|
|||||||
from awx.main.migrations import _rbac as rbac
|
from awx.main.migrations import _rbac as rbac
|
||||||
from awx.main.models import Permission
|
from awx.main.models import Permission
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from awx.main.migrations import _old_access as old_access
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_project_user_project(user_project, project, user):
|
def test_project_user_project(user_project, project, user):
|
||||||
u = user('owner')
|
u = user('owner')
|
||||||
|
|
||||||
|
assert old_access.check_user_access(u, user_project.__class__, 'read', user_project)
|
||||||
|
assert old_access.check_user_access(u, project.__class__, 'read', project) is False
|
||||||
|
|
||||||
assert user_project.accessible_by(u, {'read': True}) is False
|
assert user_project.accessible_by(u, {'read': True}) is False
|
||||||
assert project.accessible_by(u, {'read': True}) is False
|
assert project.accessible_by(u, {'read': True}) is False
|
||||||
migrations = rbac.migrate_projects(apps, None)
|
migrations = rbac.migrate_projects(apps, None)
|
||||||
@@ -20,11 +26,14 @@ def test_project_accessible_by_sa(user, project):
|
|||||||
u = user('systemadmin', is_superuser=True)
|
u = user('systemadmin', is_superuser=True)
|
||||||
|
|
||||||
assert project.accessible_by(u, {'read': True}) is False
|
assert project.accessible_by(u, {'read': True}) is False
|
||||||
|
rbac.migrate_organization(apps, None)
|
||||||
su_migrations = rbac.migrate_users(apps, None)
|
su_migrations = rbac.migrate_users(apps, None)
|
||||||
migrations = rbac.migrate_projects(apps, None)
|
migrations = rbac.migrate_projects(apps, None)
|
||||||
assert len(su_migrations) == 1
|
assert len(su_migrations) == 1
|
||||||
assert len(migrations[project.name]['users']) == 0
|
assert len(migrations[project.name]['users']) == 0
|
||||||
assert len(migrations[project.name]['teams']) == 0
|
assert len(migrations[project.name]['teams']) == 0
|
||||||
|
print(project.admin_role.ancestors.all())
|
||||||
|
print(project.admin_role.ancestors.all())
|
||||||
assert project.accessible_by(u, {'read': True, 'write': True}) is True
|
assert project.accessible_by(u, {'read': True, 'write': True}) is True
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -58,6 +67,7 @@ def test_project_team(user, team, project):
|
|||||||
assert project.accessible_by(member, {'read': True}) is False
|
assert project.accessible_by(member, {'read': True}) is False
|
||||||
|
|
||||||
rbac.migrate_team(apps, None)
|
rbac.migrate_team(apps, None)
|
||||||
|
rbac.migrate_organization(apps, None)
|
||||||
migrations = rbac.migrate_projects(apps, None)
|
migrations = rbac.migrate_projects(apps, None)
|
||||||
|
|
||||||
assert len(migrations[project.name]['users']) == 0
|
assert len(migrations[project.name]['users']) == 0
|
||||||
@@ -66,13 +76,18 @@ def test_project_team(user, team, project):
|
|||||||
assert project.accessible_by(nonmember, {'read': True}) is False
|
assert project.accessible_by(nonmember, {'read': True}) is False
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_project_explicit_permission(user, team, project):
|
def test_project_explicit_permission(user, team, project, organization):
|
||||||
u = user('user')
|
u = user('prjuser')
|
||||||
p = Permission(user=u, project=project, permission_type='check')
|
|
||||||
|
assert old_access.check_user_access(u, project.__class__, 'read', project) is False
|
||||||
|
|
||||||
|
organization.users.add(u)
|
||||||
|
p = Permission(user=u, project=project, permission_type='create', name='Perm name')
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
assert project.accessible_by(u, {'read': True}) is False
|
assert project.accessible_by(u, {'read': True}) is False
|
||||||
|
|
||||||
|
rbac.migrate_organization(apps, None)
|
||||||
migrations = rbac.migrate_projects(apps, None)
|
migrations = rbac.migrate_projects(apps, None)
|
||||||
|
|
||||||
assert len(migrations[project.name]['users']) == 1
|
assert len(migrations[project.name]['users']) == 1
|
||||||
|
|||||||
11
docs/rbac.md
11
docs/rbac.md
@@ -103,7 +103,16 @@ The `singleton` static method is a helper method on the `Role` model that helps
|
|||||||
|
|
||||||
`role_name` is the display name of the role. This is useful when generating reports or looking the results of queries.
|
`role_name` is the display name of the role. This is useful when generating reports or looking the results of queries.
|
||||||
|
|
||||||
`permissions` is a dictionary of set permissions that a user with this role will gain to your `Resource`. A permission defaults to `False` if not explicitly provided. Below is a list of available permissions. The special permission `all` is a shortcut for generating a dict with all of the explicit permissions listed below set to `True`.
|
`permissions` can be used when the model that contains the
|
||||||
|
`ImplicitRoleField` utilizs the `ResourceMixin`. When present, a
|
||||||
|
`RolePermission` entry will be automatically created to grant the specified
|
||||||
|
permissions on the resource to the role defined by the `ImplicitRoleField`.
|
||||||
|
|
||||||
|
This field should be specified as a dictionary of permissions you wish to
|
||||||
|
automatically grant. Below is a list of available permissions. The special
|
||||||
|
permission `all` is a shortcut for generating a dict with all of the explicit
|
||||||
|
permissions listed below set to `True`. Note that permissions default to
|
||||||
|
`False` if not explicitly provided.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Available Permissions
|
# Available Permissions
|
||||||
|
|||||||
Reference in New Issue
Block a user