251 lines
9.4 KiB
Python
251 lines
9.4 KiB
Python
# Copyright (c) 2015 Ansible, Inc.
|
|
# All Rights Reserved.
|
|
|
|
# Django
|
|
from django.conf import settings # noqa
|
|
from django.db import connection
|
|
from django.db.models.signals import pre_delete # noqa
|
|
|
|
# AWX
|
|
from awx.main.models.base import ( # noqa
|
|
BaseModel, PrimordialModel, prevent_search, accepts_json,
|
|
CLOUD_INVENTORY_SOURCES, VERBOSITY_CHOICES
|
|
)
|
|
from awx.main.models.unified_jobs import ( # noqa
|
|
UnifiedJob, UnifiedJobTemplate, StdoutMaxBytesExceeded
|
|
)
|
|
from awx.main.models.organization import ( # noqa
|
|
Organization, Profile, Team, UserSessionMembership
|
|
)
|
|
from awx.main.models.credential import ( # noqa
|
|
Credential, CredentialType, CredentialInputSource, ManagedCredentialType, build_safe_env
|
|
)
|
|
from awx.main.models.projects import Project, ProjectUpdate # noqa
|
|
from awx.main.models.inventory import ( # noqa
|
|
CustomInventoryScript, Group, Host, Inventory, InventorySource,
|
|
InventoryUpdate, SmartInventoryMembership
|
|
)
|
|
from awx.main.models.jobs import ( # noqa
|
|
Job, JobHostSummary, JobLaunchConfig, JobTemplate, SystemJob,
|
|
SystemJobTemplate,
|
|
)
|
|
from awx.main.models.events import ( # noqa
|
|
AdHocCommandEvent, InventoryUpdateEvent, JobEvent, ProjectUpdateEvent,
|
|
SystemJobEvent,
|
|
)
|
|
from awx.main.models.ad_hoc_commands import AdHocCommand # noqa
|
|
from awx.main.models.schedules import Schedule # noqa
|
|
from awx.main.models.activity_stream import ActivityStream # noqa
|
|
from awx.main.models.ha import ( # noqa
|
|
Instance, InstanceGroup, TowerScheduleState,
|
|
)
|
|
from awx.main.models.rbac import ( # noqa
|
|
Role, batch_role_ancestor_rebuilding, get_roles_on_resource,
|
|
role_summary_fields_generator, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
|
ROLE_SINGLETON_SYSTEM_AUDITOR,
|
|
)
|
|
from awx.main.models.mixins import ( # noqa
|
|
CustomVirtualEnvMixin, ResourceMixin, SurveyJobMixin,
|
|
SurveyJobTemplateMixin, TaskManagerInventoryUpdateMixin,
|
|
TaskManagerJobMixin, TaskManagerProjectUpdateMixin,
|
|
TaskManagerUnifiedJobMixin,
|
|
)
|
|
from awx.main.models.notifications import ( # noqa
|
|
Notification, NotificationTemplate,
|
|
JobNotificationMixin
|
|
)
|
|
from awx.main.models.label import Label # noqa
|
|
from awx.main.models.workflow import ( # noqa
|
|
WorkflowJob, WorkflowJobNode, WorkflowJobOptions, WorkflowJobTemplate,
|
|
WorkflowJobTemplateNode, WorkflowApproval, WorkflowApprovalTemplate,
|
|
)
|
|
from awx.api.versioning import reverse
|
|
from awx.main.models.oauth import ( # noqa
|
|
OAuth2AccessToken, OAuth2Application
|
|
)
|
|
from oauth2_provider.models import Grant, RefreshToken # noqa -- needed django-oauth-toolkit model migrations
|
|
|
|
|
|
# Add custom methods to User model for permissions checks.
|
|
from django.contrib.auth.models import User # noqa
|
|
from awx.main.access import ( # noqa
|
|
get_user_queryset, check_user_access, check_user_access_with_errors,
|
|
user_accessible_objects
|
|
)
|
|
|
|
|
|
User.add_to_class('get_queryset', get_user_queryset)
|
|
User.add_to_class('can_access', check_user_access)
|
|
User.add_to_class('can_access_with_errors', check_user_access_with_errors)
|
|
User.add_to_class('accessible_objects', user_accessible_objects)
|
|
|
|
|
|
def enforce_bigint_pk_migration():
|
|
#
|
|
# NOTE: this function is not actually in use anymore,
|
|
# but has been intentionally kept for historical purposes,
|
|
# and to serve as an illustration if we ever need to perform
|
|
# bulk modification/migration of event data in the future.
|
|
#
|
|
# see: https://github.com/ansible/awx/issues/6010
|
|
# look at all the event tables and verify that they have been fully migrated
|
|
# from the *old* int primary key table to the replacement bigint table
|
|
# if not, attempt to migrate them in the background
|
|
#
|
|
for tblname in (
|
|
'main_jobevent', 'main_inventoryupdateevent',
|
|
'main_projectupdateevent', 'main_adhoccommandevent',
|
|
'main_systemjobevent'
|
|
):
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(
|
|
'SELECT 1 FROM information_schema.tables WHERE table_name=%s',
|
|
(f'_old_{tblname}',)
|
|
)
|
|
if bool(cursor.rowcount):
|
|
from awx.main.tasks import migrate_legacy_event_data
|
|
migrate_legacy_event_data.apply_async([tblname])
|
|
|
|
|
|
def cleanup_created_modified_by(sender, **kwargs):
|
|
# work around a bug in django-polymorphic that doesn't properly
|
|
# handle cascades for reverse foreign keys on the polymorphic base model
|
|
# https://github.com/django-polymorphic/django-polymorphic/issues/229
|
|
for cls in (UnifiedJobTemplate, UnifiedJob):
|
|
cls.objects.filter(created_by=kwargs['instance']).update(created_by=None)
|
|
cls.objects.filter(modified_by=kwargs['instance']).update(modified_by=None)
|
|
|
|
|
|
pre_delete.connect(cleanup_created_modified_by, sender=User)
|
|
|
|
|
|
@property
|
|
def user_get_organizations(user):
|
|
return Organization.objects.filter(member_role__members=user)
|
|
|
|
|
|
@property
|
|
def user_get_admin_of_organizations(user):
|
|
return Organization.objects.filter(admin_role__members=user)
|
|
|
|
|
|
@property
|
|
def user_get_auditor_of_organizations(user):
|
|
return Organization.objects.filter(auditor_role__members=user)
|
|
|
|
|
|
@property
|
|
def created(user):
|
|
return user.date_joined
|
|
|
|
|
|
User.add_to_class('organizations', user_get_organizations)
|
|
User.add_to_class('admin_of_organizations', user_get_admin_of_organizations)
|
|
User.add_to_class('auditor_of_organizations', user_get_auditor_of_organizations)
|
|
User.add_to_class('created', created)
|
|
|
|
|
|
@property
|
|
def user_is_system_auditor(user):
|
|
if not hasattr(user, '_is_system_auditor'):
|
|
if user.pk:
|
|
user._is_system_auditor = user.roles.filter(
|
|
singleton_name='system_auditor', role_field='system_auditor').exists()
|
|
else:
|
|
# Odd case where user is unsaved, this should never be relied on
|
|
return False
|
|
return user._is_system_auditor
|
|
|
|
|
|
@user_is_system_auditor.setter
|
|
def user_is_system_auditor(user, tf):
|
|
if not user.id:
|
|
# If the user doesn't have a primary key yet (i.e., this is the *first*
|
|
# time they've logged in, and we've just created the new User in this
|
|
# request), we need one to set up the system auditor role
|
|
user.save()
|
|
if tf:
|
|
role = Role.singleton('system_auditor')
|
|
# must check if member to not duplicate activity stream
|
|
if user not in role.members.all():
|
|
role.members.add(user)
|
|
user._is_system_auditor = True
|
|
else:
|
|
role = Role.singleton('system_auditor')
|
|
if user in role.members.all():
|
|
role.members.remove(user)
|
|
user._is_system_auditor = False
|
|
|
|
|
|
User.add_to_class('is_system_auditor', user_is_system_auditor)
|
|
|
|
|
|
def user_is_in_enterprise_category(user, category):
|
|
ret = (category,) in user.enterprise_auth.values_list('provider') and not user.has_usable_password()
|
|
# NOTE: this if-else block ensures existing enterprise users are still able to
|
|
# log in. Remove it in a future release
|
|
if category == 'radius':
|
|
ret = ret or not user.has_usable_password()
|
|
elif category == 'saml':
|
|
ret = ret or user.social_auth.all()
|
|
return ret
|
|
|
|
|
|
User.add_to_class('is_in_enterprise_category', user_is_in_enterprise_category)
|
|
|
|
|
|
|
|
|
|
def o_auth2_application_get_absolute_url(self, request=None):
|
|
return reverse('api:o_auth2_application_detail', kwargs={'pk': self.pk}, request=request)
|
|
|
|
|
|
OAuth2Application.add_to_class('get_absolute_url', o_auth2_application_get_absolute_url)
|
|
|
|
|
|
def o_auth2_token_get_absolute_url(self, request=None):
|
|
return reverse('api:o_auth2_token_detail', kwargs={'pk': self.pk}, request=request)
|
|
|
|
|
|
OAuth2AccessToken.add_to_class('get_absolute_url', o_auth2_token_get_absolute_url)
|
|
|
|
from awx.main.registrar import activity_stream_registrar # noqa
|
|
activity_stream_registrar.connect(Organization)
|
|
activity_stream_registrar.connect(Inventory)
|
|
activity_stream_registrar.connect(Host)
|
|
activity_stream_registrar.connect(Group)
|
|
activity_stream_registrar.connect(InventorySource)
|
|
#activity_stream_registrar.connect(InventoryUpdate)
|
|
activity_stream_registrar.connect(Credential)
|
|
activity_stream_registrar.connect(CredentialType)
|
|
activity_stream_registrar.connect(Team)
|
|
activity_stream_registrar.connect(Project)
|
|
#activity_stream_registrar.connect(ProjectUpdate)
|
|
activity_stream_registrar.connect(JobTemplate)
|
|
activity_stream_registrar.connect(Job)
|
|
activity_stream_registrar.connect(AdHocCommand)
|
|
# activity_stream_registrar.connect(JobHostSummary)
|
|
# activity_stream_registrar.connect(JobEvent)
|
|
# activity_stream_registrar.connect(Profile)
|
|
activity_stream_registrar.connect(Schedule)
|
|
activity_stream_registrar.connect(CustomInventoryScript)
|
|
activity_stream_registrar.connect(NotificationTemplate)
|
|
activity_stream_registrar.connect(Notification)
|
|
activity_stream_registrar.connect(Label)
|
|
activity_stream_registrar.connect(User)
|
|
activity_stream_registrar.connect(WorkflowJobTemplate)
|
|
activity_stream_registrar.connect(WorkflowJobTemplateNode)
|
|
activity_stream_registrar.connect(WorkflowJob)
|
|
activity_stream_registrar.connect(WorkflowApproval)
|
|
activity_stream_registrar.connect(WorkflowApprovalTemplate)
|
|
activity_stream_registrar.connect(OAuth2Application)
|
|
activity_stream_registrar.connect(OAuth2AccessToken)
|
|
|
|
# prevent API filtering on certain Django-supplied sensitive fields
|
|
prevent_search(User._meta.get_field('password'))
|
|
prevent_search(OAuth2AccessToken._meta.get_field('token'))
|
|
prevent_search(RefreshToken._meta.get_field('token'))
|
|
prevent_search(OAuth2Application._meta.get_field('client_secret'))
|
|
prevent_search(OAuth2Application._meta.get_field('client_id'))
|
|
prevent_search(Grant._meta.get_field('code'))
|