mirror of
https://github.com/ansible/awx.git
synced 2026-02-12 15:14:45 -03:30
373 lines
16 KiB
Python
373 lines
16 KiB
Python
import logging
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.db.models import Q
|
|
|
|
from collections import defaultdict
|
|
from awx.main.utils import getattrd
|
|
import _old_access as old_access
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def log_migration(wrapped):
|
|
'''setup the logging mechanism for each migration method
|
|
as it runs, Django resets this, so we use a decorator
|
|
to re-add the handler for each method.
|
|
'''
|
|
handler = logging.FileHandler("tower_rbac_migrations.log", mode="a")
|
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
handler.setLevel(logging.DEBUG)
|
|
handler.setFormatter(formatter)
|
|
|
|
def wrapper(*args, **kwargs):
|
|
logger.handlers = []
|
|
logger.addHandler(handler)
|
|
return wrapped(*args, **kwargs)
|
|
return wrapper
|
|
|
|
@log_migration
|
|
def migrate_users(apps, schema_editor):
|
|
User = apps.get_model('auth', "User")
|
|
Role = apps.get_model('main', "Role")
|
|
RolePermission = apps.get_model('main', "RolePermission")
|
|
|
|
for user in User.objects.iterator():
|
|
try:
|
|
Role.objects.get(content_type=ContentType.objects.get_for_model(User), object_id=user.id)
|
|
logger.info("found existing role for user: %s", user.username)
|
|
except Role.DoesNotExist:
|
|
role = Role.objects.create(
|
|
singleton_name = '%s-admin_role' % user.username,
|
|
content_object = user,
|
|
)
|
|
role.members.add(user)
|
|
RolePermission.objects.create(
|
|
role = role,
|
|
resource = user,
|
|
create=1, read=1, write=1, delete=1, update=1,
|
|
execute=1, scm_update=1, use=1,
|
|
)
|
|
logger.info("migrating to new role for user: %s", user.username)
|
|
|
|
if user.is_superuser:
|
|
Role.singleton('System Administrator').members.add(user)
|
|
logger.warning("added superuser: %s", user.username)
|
|
|
|
@log_migration
|
|
def migrate_organization(apps, schema_editor):
|
|
Organization = apps.get_model('main', "Organization")
|
|
for org in Organization.objects.iterator():
|
|
for admin in org.deprecated_admins.all():
|
|
org.admin_role.members.add(admin)
|
|
logger.info("added admin: %s, %s" % (org.name, admin.username))
|
|
for user in org.deprecated_users.all():
|
|
org.auditor_role.members.add(user)
|
|
logger.info("added auditor: %s, %s" % (org.name, user.username))
|
|
|
|
@log_migration
|
|
def migrate_team(apps, schema_editor):
|
|
Team = apps.get_model('main', 'Team')
|
|
for t in Team.objects.iterator():
|
|
for user in t.deprecated_users.all():
|
|
t.member_role.members.add(user)
|
|
logger.info("team: %s, added user: %s" % (t.name, user.username))
|
|
|
|
def attrfunc(attr_path):
|
|
'''attrfunc returns a function that will
|
|
attempt to use the attr_path to access the attribute
|
|
of an instance that is passed in to the returned function.
|
|
|
|
Example:
|
|
get_org = attrfunc('inventory.organization')
|
|
org = get_org(JobTemplateInstance)
|
|
'''
|
|
def attr(inst):
|
|
return getattrd(inst, attr_path)
|
|
return attr
|
|
|
|
def _update_credential_parents(org, cred):
|
|
org.admin_role.children.add(cred.owner_role)
|
|
org.member_role.children.add(cred.usage_role)
|
|
cred.deprecated_user, cred.deprecated_team = None, None
|
|
cred.save()
|
|
|
|
def _discover_credentials(instances, cred, orgfunc):
|
|
'''_discover_credentials will find shared credentials across
|
|
organizations. If a shared credential is found, it will duplicate
|
|
the credential, ensure the proper role permissions are added to the new
|
|
credential, and update any references from the old to the newly created
|
|
credential.
|
|
|
|
instances is a list of all objects that were matched when filtered
|
|
with cred.
|
|
|
|
orgfunc is a function that when called with an instance from instances
|
|
will produce an Organization object.
|
|
'''
|
|
orgs = defaultdict(list)
|
|
for inst in instances:
|
|
orgs[orgfunc(inst)].append(inst)
|
|
|
|
if len(orgs) == 1:
|
|
_update_credential_parents(instances[0].inventory.organization, cred)
|
|
else:
|
|
for pos, org in enumerate(orgs):
|
|
if pos == 0:
|
|
_update_credential_parents(org, cred)
|
|
else:
|
|
# Create a new credential
|
|
cred.pk = None
|
|
cred.save()
|
|
|
|
# Unlink the old information from the new credential
|
|
cred.deprecated_user, cred.deprecated_team = None, None
|
|
cred.owner_role, cred.usage_role = None, None
|
|
cred.save()
|
|
|
|
for i in orgs[org]:
|
|
i.credential = cred
|
|
i.save()
|
|
_update_credential_parents(org, cred)
|
|
|
|
@log_migration
|
|
def migrate_credential(apps, schema_editor):
|
|
Credential = apps.get_model('main', "Credential")
|
|
JobTemplate = apps.get_model('main', 'JobTemplate')
|
|
Project = apps.get_model('main', 'Project')
|
|
InventorySource = apps.get_model('main', 'InventorySource')
|
|
|
|
for cred in Credential.objects.iterator():
|
|
results = (JobTemplate.objects.filter(Q(credential=cred) | Q(cloud_credential=cred)).all() or
|
|
InventorySource.objects.filter(credential=cred).all())
|
|
if results:
|
|
if len(results) == 1:
|
|
_update_credential_parents(results[0].inventory.organization, cred)
|
|
else:
|
|
_discover_credentials(results, cred, attrfunc('inventory.organization'))
|
|
logger.info("added Credential(name=%s, kind=%s, host=%s) at organization level" % (cred.name, cred.kind, cred.host))
|
|
continue
|
|
|
|
projs = Project.objects.filter(credential=cred).all()
|
|
if projs:
|
|
if len(projs) == 1:
|
|
_update_credential_parents(projs[0].organization, cred)
|
|
else:
|
|
_discover_credentials(projs, cred, attrfunc('organization'))
|
|
logger.info("added Credential(name=%s, kind=%s, host=%s) at organization level" % (cred.name, cred.kind, cred.host))
|
|
continue
|
|
|
|
if cred.deprecated_team is not None:
|
|
cred.deprecated_team.admin_role.children.add(cred.owner_role)
|
|
cred.deprecated_team.member_role.children.add(cred.usage_role)
|
|
cred.deprecated_user, cred.deprecated_team = None, None
|
|
cred.save()
|
|
logger.info("added Credential(name=%s, kind=%s, host=%s) at user level" % (cred.name, cred.kind, cred.host))
|
|
elif cred.deprecated_user is not None:
|
|
cred.deprecated_user.admin_role.children.add(cred.owner_role)
|
|
cred.deprecated_user, cred.deprecated_team = None, None
|
|
cred.save()
|
|
logger.info("added Credential(name=%s, kind=%s, host=%s) at user level" % (cred.name, cred.kind, cred.host, ))
|
|
else:
|
|
logger.warning("orphaned credential found Credential(name=%s, kind=%s, host=%s), superuser only" % (cred.name, cred.kind, cred.host, ))
|
|
|
|
|
|
@log_migration
|
|
def migrate_inventory(apps, schema_editor):
|
|
Inventory = apps.get_model('main', 'Inventory')
|
|
Permission = apps.get_model('main', 'Permission')
|
|
|
|
for inventory in Inventory.objects.iterator():
|
|
for perm in Permission.objects.filter(inventory=inventory):
|
|
role = None
|
|
execrole = None
|
|
if perm.permission_type == 'admin':
|
|
role = inventory.admin_role
|
|
pass
|
|
elif perm.permission_type == 'read':
|
|
role = inventory.auditor_role
|
|
pass
|
|
elif perm.permission_type == 'write':
|
|
role = inventory.updater_role
|
|
pass
|
|
elif perm.permission_type == 'check':
|
|
pass
|
|
elif perm.permission_type == 'run':
|
|
pass
|
|
else:
|
|
raise Exception('Unhandled permission type for inventory: %s' % perm.permission_type)
|
|
if perm.run_ad_hoc_commands:
|
|
execrole = inventory.executor_role
|
|
|
|
if perm.team:
|
|
if role:
|
|
perm.team.member_role.children.add(role)
|
|
if execrole:
|
|
perm.team.member_role.children.add(execrole)
|
|
logger.info('added Team(%s) access to Inventory(%s)' % (perm.team.name, inventory.name))
|
|
|
|
if perm.user:
|
|
if role:
|
|
role.members.add(perm.user)
|
|
if execrole:
|
|
execrole.members.add(perm.user)
|
|
logger.info('added User(%s) access to Inventory(%s)' % (perm.user.username, inventory.name))
|
|
|
|
@log_migration
|
|
def migrate_projects(apps, schema_editor):
|
|
'''
|
|
I can see projects when:
|
|
X I am a superuser.
|
|
X I am an admin in an organization associated with the project.
|
|
X I am a user in an organization associated with the project.
|
|
X I am on a team associated with the project.
|
|
X I have been explicitly granted permission to run/check jobs using the
|
|
project.
|
|
X I created the project but it isn't associated with an organization
|
|
I can change/delete when:
|
|
X I am a superuser.
|
|
X I am an admin in an organization associated with the project.
|
|
X I created the project but it isn't associated with an organization
|
|
'''
|
|
Project = apps.get_model('main', 'Project')
|
|
Permission = apps.get_model('main', 'Permission')
|
|
JobTemplate = apps.get_model('main', 'JobTemplate')
|
|
|
|
# Migrate projects to single organizations, duplicating as necessary
|
|
for project in Project.objects.iterator():
|
|
original_project_name = project.name
|
|
project_orgs = project.deprecated_organizations.distinct().all()
|
|
|
|
if project_orgs.count() > 1:
|
|
first_org = None
|
|
for org in project_orgs:
|
|
if first_org is None:
|
|
# For the first org, re-use our existing Project object, so don't do the below duplication effort
|
|
first_org = org
|
|
project.name = first_org.name + ' - ' + original_project_name
|
|
project.organization = first_org
|
|
project.save()
|
|
else:
|
|
new_prj = Project.objects.create(
|
|
created = project.created,
|
|
description = project.description,
|
|
name = org.name + ' - ' + original_project_name,
|
|
old_pk = project.old_pk,
|
|
created_by_id = project.created_by_id,
|
|
scm_type = project.scm_type,
|
|
scm_url = project.scm_url,
|
|
scm_branch = project.scm_branch,
|
|
scm_clean = project.scm_clean,
|
|
scm_delete_on_update = project.scm_delete_on_update,
|
|
scm_delete_on_next_update = project.scm_delete_on_next_update,
|
|
scm_update_on_launch = project.scm_update_on_launch,
|
|
scm_update_cache_timeout = project.scm_update_cache_timeout,
|
|
credential = project.credential,
|
|
organization = org
|
|
)
|
|
logger.warning('cloning Project(%s) onto %s as Project(%s)' % (original_project_name, org, new_prj))
|
|
job_templates = JobTemplate.objects.filter(inventory__organization=org).all()
|
|
for jt in job_templates:
|
|
jt.project = new_prj
|
|
jt.save()
|
|
|
|
# Migrate permissions
|
|
for project in Project.objects.iterator():
|
|
if project.organization is None and project.created_by is not None:
|
|
project.admin_role.members.add(project.created_by)
|
|
logger.warn('adding Project(%s) admin: %s' % (project.name, project.created_by.username))
|
|
|
|
for team in project.deprecated_teams.all():
|
|
team.member_role.children.add(project.member_role)
|
|
logger.info('adding Team(%s) access for Project(%s)' % (team.name, project.name))
|
|
|
|
if project.organization is not None:
|
|
for user in project.organization.deprecated_users.all():
|
|
project.member_role.members.add(user)
|
|
logger.info('adding Organization(%s) member access to Project(%s)' % (project.organization.name, project.name))
|
|
|
|
for perm in Permission.objects.filter(project=project):
|
|
# All perms at this level just imply a user or team can read
|
|
if perm.team:
|
|
perm.team.member_role.children.add(project.member_role)
|
|
logger.info('adding Team(%s) access for Project(%s)' % (perm.team.name, project.name))
|
|
|
|
if perm.user:
|
|
project.member_role.members.add(perm.user)
|
|
logger.info('adding User(%s) access for Project(%s)' % (perm.user.username, project.name))
|
|
|
|
|
|
@log_migration
|
|
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.
|
|
|
|
'''
|
|
|
|
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.iterator():
|
|
permission = Permission.objects.filter(
|
|
inventory=jt.inventory,
|
|
project=jt.project,
|
|
permission_type__in=['create', 'check', 'run'] if jt.job_type == 'check' else ['create', 'run'],
|
|
)
|
|
|
|
for team in Team.objects.iterator():
|
|
if permission.filter(team=team).exists():
|
|
team.member_role.children.add(jt.executor_role)
|
|
logger.info('adding Team(%s) access to JobTemplate(%s)' % (team.name, jt.name))
|
|
|
|
for user in User.objects.iterator():
|
|
if permission.filter(user=user).exists():
|
|
jt.executor_role.members.add(user)
|
|
logger.info('adding User(%s) access to JobTemplate(%s)' % (user.username, jt.name))
|
|
|
|
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)
|
|
logger.info('adding User(%s) access to JobTemplate(%s)' % (user.username, jt.name))
|