From de34a6411528a02d116776b75bad80f9c796a197 Mon Sep 17 00:00:00 2001 From: beeankha Date: Mon, 1 Apr 2019 17:24:55 -0400 Subject: [PATCH] Basic License feature gating changes --- awx/api/serializers.py | 13 +- awx/api/views/__init__.py | 117 +++++++---------- awx/api/views/inventory.py | 3 +- awx/api/views/mixin.py | 37 ------ awx/api/views/organization.py | 27 +--- awx/api/views/root.py | 9 +- awx/conf/conf.py | 3 - awx/conf/license.py | 47 +------ awx/conf/registry.py | 15 +-- awx/conf/tests/unit/test_registry.py | 55 -------- awx/conf/views.py | 9 +- awx/main/access.py | 27 +--- awx/main/conf.py | 2 - awx/main/management/commands/cleanup_facts.py | 7 +- .../management/commands/inventory_import.py | 2 +- .../functional/api/test_activity_streams.py | 13 -- .../functional/api/test_fact_versions.py | 51 -------- .../tests/functional/api/test_fact_view.py | 46 ------- .../functional/api/test_organizations.py | 11 +- .../functional/api/test_rbac_displays.py | 10 -- .../tests/functional/api/test_survey_spec.py | 122 +----------------- .../functional/commands/test_cleanup_facts.py | 19 --- awx/main/tests/functional/test_rbac_api.py | 6 - awx/main/tests/unit/test_access.py | 27 +--- awx/main/utils/common.py | 3 +- awx/sso/backends.py | 27 +--- awx/sso/conf.py | 40 ------ awx/sso/fields.py | 7 +- awx/sso/pipeline.py | 56 ++------ awx/sso/tests/conftest.py | 18 --- awx/sso/tests/unit/test_tacacsplus.py | 21 +-- .../activity-stream/activitystream.route.js | 21 --- awx/ui/client/src/app.js | 10 +- .../src/bread-crumb/bread-crumb.directive.js | 37 ++---- .../auth-form/sub-forms/auth-ldap.form.js | 2 +- .../auth-form/sub-forms/auth-ldap1.form.js | 2 +- .../auth-form/sub-forms/auth-ldap2.form.js | 2 +- .../auth-form/sub-forms/auth-radius.form.js | 2 +- .../auth-form/sub-forms/auth-saml.form.js | 2 +- .../auth-form/sub-forms/auth-tacacs.form.js | 2 +- .../forms/settings-form.controller.js | 17 +-- awx/ui/client/src/home/home.route.js | 10 +- .../client/src/license/license.controller.js | 6 +- .../login/loginModal/loginModal.controller.js | 5 +- .../linkout/organizations-linkout.route.js | 15 --- .../src/organizations/organizations.list.js | 1 - .../shared/features/features.controller.js | 16 --- .../src/shared/features/features.directive.js | 41 ------ .../src/shared/features/features.service.js | 35 ----- awx/ui/client/src/shared/features/main.js | 13 -- awx/ui/client/src/shared/form-generator.js | 10 -- awx/ui/client/src/shared/generator-helpers.js | 1 - .../list-generator/list-actions.partial.html | 2 - awx/ui/client/src/shared/main.js | 2 - awx/ui/client/src/shared/socket/main.js | 2 - .../job_templates/job-template.form.js | 3 - awx/ui/client/src/templates/workflows.form.js | 3 - awx/ui/conf.py | 2 - .../spec/features/features.directive-test.js | 23 ---- docs/tower_configuration.md | 2 - pytest.ini | 1 - 61 files changed, 125 insertions(+), 1015 deletions(-) delete mode 100644 awx/ui/client/src/shared/features/features.controller.js delete mode 100644 awx/ui/client/src/shared/features/features.directive.js delete mode 100644 awx/ui/client/src/shared/features/features.service.js delete mode 100644 awx/ui/client/src/shared/features/main.js delete mode 100644 awx/ui/test/spec/features/features.directive-test.js diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 8f94fb759a6c..84247c210bde 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -73,7 +73,6 @@ from awx.main.validators import vars_validate_or_raise -from awx.conf.license import feature_enabled, LicenseForbids from awx.api.versioning import reverse, get_request_version from awx.api.fields import (BooleanNullField, CharNullField, ChoiceNullField, VerbatimField, DeprecatedCredentialField) @@ -918,7 +917,7 @@ def validate_password(self, value): def _update_password(self, obj, new_password): # For now we're not raising an error, just not saving password for # users managed by LDAP who already have an unusable password set. - if getattr(settings, 'AUTH_LDAP_SERVER_URI', None) and feature_enabled('ldap'): + if getattr(settings, 'AUTH_LDAP_SERVER_URI', None): try: if obj.pk and obj.profile.ldap_dn and not obj.has_usable_password(): new_password = None @@ -979,7 +978,7 @@ def get_related(self, obj): return res def _validate_ldap_managed_field(self, value, field_name): - if not getattr(settings, 'AUTH_LDAP_SERVER_URI', None) or not feature_enabled('ldap'): + if not getattr(settings, 'AUTH_LDAP_SERVER_URI', None): return value try: is_ldap_user = bool(self.instance and self.instance.profile.ldap_dn) @@ -1073,7 +1072,7 @@ def _is_valid_scope(self, value): if word not in self.ALLOWED_SCOPES: return False return True - + def validate_scope(self, value): if not self._is_valid_scope(value): raise serializers.ValidationError(_( @@ -3170,12 +3169,6 @@ def get_field_from_model_or_attrs(fd): def validate_extra_vars(self, value): return vars_validate_or_raise(value) - def validate_job_slice_count(self, value): - if value > 1 and not feature_enabled('workflows'): - raise LicenseForbids({'job_slice_count': [_( - "Job slicing is a workflows-based feature and your license does not allow use of workflows." - )]}) - return value def get_summary_fields(self, obj): summary_fields = super(JobTemplateSerializer, self).get_summary_fields(obj) diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index a2e70b8459c8..39ffd10f1c73 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -73,7 +73,7 @@ SubListDestroyAPIView, get_view_name ) from awx.api.versioning import reverse, get_request_version -from awx.conf.license import feature_enabled, feature_exists, LicenseForbids, get_license +from awx.conf.license import get_license from awx.main import models from awx.main.utils import ( camelcase_to_underscore, @@ -100,10 +100,9 @@ from awx.main.constants import ACTIVE_STATES from awx.main.scheduler.dag_workflow import WorkflowDAG from awx.api.views.mixin import ( - ActivityStreamEnforcementMixin, ControlledByScmMixin, - InstanceGroupMembershipMixin, OrganizationCountsMixin, - RelatedJobsPreventDeleteMixin, SystemTrackingEnforcementMixin, - UnifiedJobDeletionMixin, WorkflowsEnforcementMixin, + ControlledByScmMixin, InstanceGroupMembershipMixin, + OrganizationCountsMixin, RelatedJobsPreventDeleteMixin, + UnifiedJobDeletionMixin, ) from awx.api.views.organization import ( # noqa OrganizationList, @@ -530,12 +529,6 @@ def get(self, request): # Return auth backends in consistent order: Google, GitHub, SAML. auth_backends.sort(key=lambda x: 'g' if x[0] == 'google-oauth2' else x[0]) for name, backend in auth_backends: - if (not feature_exists('enterprise_auth') and - not feature_enabled('ldap')) or \ - (not feature_enabled('enterprise_auth') and - name in ['saml', 'radius']): - continue - login_url = reverse('social:begin', args=(name,)) complete_url = request.build_absolute_uri(reverse('social:complete', args=(name,))) backend_data = { @@ -649,7 +642,7 @@ def get_queryset(self): return self.model.accessible_objects(self.request.user, 'read_role').filter(pk__in=[t.content_object.pk for t in proj_roles]) -class TeamActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class TeamActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -745,7 +738,7 @@ class ProjectScmInventorySources(SubListAPIView): parent_key = 'source_project' -class ProjectActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class ProjectActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -989,7 +982,7 @@ class ApplicationOAuth2TokenList(SubListCreateAPIView): swagger_topic = 'Authentication' -class OAuth2ApplicationActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class OAuth2ApplicationActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -1071,7 +1064,7 @@ class OAuth2TokenDetail(RetrieveUpdateDestroyAPIView): swagger_topic = 'Authentication' -class OAuth2TokenActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class OAuth2TokenActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -1185,7 +1178,7 @@ def get_queryset(self): return my_qs & user_qs -class UserActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class UserActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -1272,7 +1265,7 @@ class CredentialTypeCredentialList(SubListCreateAPIView): serializer_class = serializers.CredentialSerializer -class CredentialTypeActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class CredentialTypeActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -1386,7 +1379,7 @@ class CredentialDetail(RetrieveUpdateDestroyAPIView): filter_backends = RetrieveUpdateDestroyAPIView.filter_backends + [V1CredentialFilterBackend] -class CredentialActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class CredentialActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -1618,7 +1611,7 @@ class HostSmartInventoriesList(SubListAPIView): relationship = 'smart_inventories' -class HostActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class HostActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -1633,7 +1626,7 @@ def get_queryset(self): return qs.filter(Q(host=parent) | Q(inventory=parent.inventory)) -class HostFactVersionsList(SystemTrackingEnforcementMixin, ParentMixin, ListAPIView): +class HostFactVersionsList(ParentMixin, ListAPIView): model = models.Fact serializer_class = serializers.FactVersionSerializer @@ -1660,7 +1653,7 @@ def list(self, *args, **kwargs): return Response(dict(results=self.serializer_class(queryset, many=True).data)) -class HostFactCompareView(SystemTrackingEnforcementMixin, SubDetailAPIView): +class HostFactCompareView(SubDetailAPIView): model = models.Fact parent_model = models.Host @@ -1876,7 +1869,7 @@ class GroupInventorySourcesList(SubListAPIView): relationship = 'inventory_sources' -class GroupActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class GroupActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -2119,7 +2112,7 @@ class InventorySourceSchedulesList(SubListCreateAPIView): parent_key = 'unified_job_template' -class InventorySourceActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class InventorySourceActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -2530,21 +2523,11 @@ class JobTemplateSurveySpec(GenericAPIView): def get(self, request, *args, **kwargs): obj = self.get_object() - if not feature_enabled('surveys'): - raise LicenseForbids(_('Your license does not allow ' - 'adding surveys.')) - return Response(obj.display_survey_spec()) def post(self, request, *args, **kwargs): obj = self.get_object() - # Sanity check: Are surveys available on this license? - # If not, do not allow them to be used. - if not feature_enabled('surveys'): - raise LicenseForbids(_('Your license does not allow ' - 'adding surveys.')) - if not request.user.can_access(self.model, 'change', obj, None): raise PermissionDenied() response = self._validate_spec_data(request.data, obj.survey_spec) @@ -2672,12 +2655,12 @@ def delete(self, request, *args, **kwargs): return Response() -class WorkflowJobTemplateSurveySpec(WorkflowsEnforcementMixin, JobTemplateSurveySpec): +class WorkflowJobTemplateSurveySpec(JobTemplateSurveySpec): model = models.WorkflowJobTemplate -class JobTemplateActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class JobTemplateActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -2995,14 +2978,14 @@ class JobTemplateCopy(CopyAPIView): copy_return_serializer_class = serializers.JobTemplateSerializer -class WorkflowJobNodeList(WorkflowsEnforcementMixin, ListAPIView): +class WorkflowJobNodeList(ListAPIView): model = models.WorkflowJobNode serializer_class = serializers.WorkflowJobNodeListSerializer search_fields = ('unified_job_template__name', 'unified_job_template__description',) -class WorkflowJobNodeDetail(WorkflowsEnforcementMixin, RetrieveAPIView): +class WorkflowJobNodeDetail(RetrieveAPIView): model = models.WorkflowJobNode serializer_class = serializers.WorkflowJobNodeDetailSerializer @@ -3016,14 +2999,14 @@ class WorkflowJobNodeCredentialsList(SubListAPIView): relationship = 'credentials' -class WorkflowJobTemplateNodeList(WorkflowsEnforcementMixin, ListCreateAPIView): +class WorkflowJobTemplateNodeList(ListCreateAPIView): model = models.WorkflowJobTemplateNode serializer_class = serializers.WorkflowJobTemplateNodeSerializer search_fields = ('unified_job_template__name', 'unified_job_template__description',) -class WorkflowJobTemplateNodeDetail(WorkflowsEnforcementMixin, RetrieveUpdateDestroyAPIView): +class WorkflowJobTemplateNodeDetail(RetrieveUpdateDestroyAPIView): model = models.WorkflowJobTemplateNode serializer_class = serializers.WorkflowJobTemplateNodeDetailSerializer @@ -3034,7 +3017,7 @@ class WorkflowJobTemplateNodeCredentialsList(LaunchConfigCredentialsBase): parent_model = models.WorkflowJobTemplateNode -class WorkflowJobTemplateNodeChildrenBaseList(WorkflowsEnforcementMixin, EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView): +class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView): model = models.WorkflowJobTemplateNode serializer_class = serializers.WorkflowJobTemplateNodeSerializer @@ -3096,7 +3079,7 @@ class WorkflowJobTemplateNodeAlwaysNodesList(WorkflowJobTemplateNodeChildrenBase relationship = 'always_nodes' -class WorkflowJobNodeChildrenBaseList(WorkflowsEnforcementMixin, SubListAPIView): +class WorkflowJobNodeChildrenBaseList(SubListAPIView): model = models.WorkflowJobNode serializer_class = serializers.WorkflowJobNodeListSerializer @@ -3126,21 +3109,21 @@ class WorkflowJobNodeAlwaysNodesList(WorkflowJobNodeChildrenBaseList): relationship = 'always_nodes' -class WorkflowJobTemplateList(WorkflowsEnforcementMixin, ListCreateAPIView): +class WorkflowJobTemplateList(ListCreateAPIView): model = models.WorkflowJobTemplate serializer_class = serializers.WorkflowJobTemplateSerializer always_allow_superuser = False -class WorkflowJobTemplateDetail(RelatedJobsPreventDeleteMixin, WorkflowsEnforcementMixin, RetrieveUpdateDestroyAPIView): +class WorkflowJobTemplateDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView): model = models.WorkflowJobTemplate serializer_class = serializers.WorkflowJobTemplateSerializer always_allow_superuser = False -class WorkflowJobTemplateCopy(WorkflowsEnforcementMixin, CopyAPIView): +class WorkflowJobTemplateCopy(CopyAPIView): model = models.WorkflowJobTemplate copy_return_serializer_class = serializers.WorkflowJobTemplateSerializer @@ -3185,11 +3168,11 @@ def deep_copy_permission_check_func(user, new_objs): obj.save() -class WorkflowJobTemplateLabelList(WorkflowsEnforcementMixin, JobTemplateLabelList): +class WorkflowJobTemplateLabelList(JobTemplateLabelList): parent_model = models.WorkflowJobTemplate -class WorkflowJobTemplateLaunch(WorkflowsEnforcementMixin, RetrieveAPIView): +class WorkflowJobTemplateLaunch(RetrieveAPIView): model = models.WorkflowJobTemplate @@ -3238,7 +3221,7 @@ def post(self, request, *args, **kwargs): return Response(data, status=status.HTTP_201_CREATED, headers=headers) -class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView): +class WorkflowJobRelaunch(GenericAPIView): model = models.WorkflowJob obj_permission_type = 'start' @@ -3270,7 +3253,7 @@ def post(self, request, *args, **kwargs): return Response(data, status=status.HTTP_201_CREATED, headers=headers) -class WorkflowJobTemplateWorkflowNodesList(WorkflowsEnforcementMixin, SubListCreateAPIView): +class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView): model = models.WorkflowJobTemplateNode serializer_class = serializers.WorkflowJobTemplateNodeSerializer @@ -3283,7 +3266,7 @@ def get_queryset(self): return super(WorkflowJobTemplateWorkflowNodesList, self).get_queryset().order_by('id') -class WorkflowJobTemplateJobsList(WorkflowsEnforcementMixin, SubListAPIView): +class WorkflowJobTemplateJobsList(SubListAPIView): model = models.WorkflowJob serializer_class = serializers.WorkflowJobListSerializer @@ -3292,7 +3275,7 @@ class WorkflowJobTemplateJobsList(WorkflowsEnforcementMixin, SubListAPIView): parent_key = 'workflow_job_template' -class WorkflowJobTemplateSchedulesList(WorkflowsEnforcementMixin, SubListCreateAPIView): +class WorkflowJobTemplateSchedulesList(SubListCreateAPIView): view_name = _("Workflow Job Template Schedules") @@ -3303,7 +3286,7 @@ class WorkflowJobTemplateSchedulesList(WorkflowsEnforcementMixin, SubListCreateA parent_key = 'unified_job_template' -class WorkflowJobTemplateNotificationTemplatesAnyList(WorkflowsEnforcementMixin, SubListCreateAttachDetachAPIView): +class WorkflowJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView): model = models.NotificationTemplate serializer_class = serializers.NotificationTemplateSerializer @@ -3311,7 +3294,7 @@ class WorkflowJobTemplateNotificationTemplatesAnyList(WorkflowsEnforcementMixin, relationship = 'notification_templates_any' -class WorkflowJobTemplateNotificationTemplatesErrorList(WorkflowsEnforcementMixin, SubListCreateAttachDetachAPIView): +class WorkflowJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView): model = models.NotificationTemplate serializer_class = serializers.NotificationTemplateSerializer @@ -3319,7 +3302,7 @@ class WorkflowJobTemplateNotificationTemplatesErrorList(WorkflowsEnforcementMixi relationship = 'notification_templates_error' -class WorkflowJobTemplateNotificationTemplatesSuccessList(WorkflowsEnforcementMixin, SubListCreateAttachDetachAPIView): +class WorkflowJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView): model = models.NotificationTemplate serializer_class = serializers.NotificationTemplateSerializer @@ -3327,13 +3310,13 @@ class WorkflowJobTemplateNotificationTemplatesSuccessList(WorkflowsEnforcementMi relationship = 'notification_templates_success' -class WorkflowJobTemplateAccessList(WorkflowsEnforcementMixin, ResourceAccessList): +class WorkflowJobTemplateAccessList(ResourceAccessList): model = models.User # needs to be User for AccessLists's parent_model = models.WorkflowJobTemplate -class WorkflowJobTemplateObjectRolesList(WorkflowsEnforcementMixin, SubListAPIView): +class WorkflowJobTemplateObjectRolesList(SubListAPIView): model = models.Role serializer_class = serializers.RoleSerializer @@ -3346,7 +3329,7 @@ def get_queryset(self): return models.Role.objects.filter(content_type=content_type, object_id=po.pk) -class WorkflowJobTemplateActivityStreamList(WorkflowsEnforcementMixin, ActivityStreamEnforcementMixin, SubListAPIView): +class WorkflowJobTemplateActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -3362,19 +3345,19 @@ def get_queryset(self): Q(workflow_job_template_node__workflow_job_template=parent)).distinct() -class WorkflowJobList(WorkflowsEnforcementMixin, ListCreateAPIView): +class WorkflowJobList(ListCreateAPIView): model = models.WorkflowJob serializer_class = serializers.WorkflowJobListSerializer -class WorkflowJobDetail(WorkflowsEnforcementMixin, UnifiedJobDeletionMixin, RetrieveDestroyAPIView): +class WorkflowJobDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView): model = models.WorkflowJob serializer_class = serializers.WorkflowJobSerializer -class WorkflowJobWorkflowNodesList(WorkflowsEnforcementMixin, SubListAPIView): +class WorkflowJobWorkflowNodesList(SubListAPIView): model = models.WorkflowJobNode serializer_class = serializers.WorkflowJobNodeListSerializer @@ -3388,7 +3371,7 @@ def get_queryset(self): return super(WorkflowJobWorkflowNodesList, self).get_queryset().order_by('id') -class WorkflowJobCancel(WorkflowsEnforcementMixin, RetrieveAPIView): +class WorkflowJobCancel(RetrieveAPIView): model = models.WorkflowJob obj_permission_type = 'cancel' @@ -3404,7 +3387,7 @@ def post(self, request, *args, **kwargs): return self.http_method_not_allowed(request, *args, **kwargs) -class WorkflowJobNotificationsList(WorkflowsEnforcementMixin, SubListAPIView): +class WorkflowJobNotificationsList(SubListAPIView): model = models.Notification serializer_class = serializers.NotificationSerializer @@ -3413,7 +3396,7 @@ class WorkflowJobNotificationsList(WorkflowsEnforcementMixin, SubListAPIView): search_fields = ('subject', 'notification_type', 'body',) -class WorkflowJobActivityStreamList(WorkflowsEnforcementMixin, ActivityStreamEnforcementMixin, SubListAPIView): +class WorkflowJobActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -3589,11 +3572,11 @@ class JobLabelList(SubListAPIView): parent_key = 'job' -class WorkflowJobLabelList(WorkflowsEnforcementMixin, JobLabelList): +class WorkflowJobLabelList(JobLabelList): parent_model = models.WorkflowJob -class JobActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class JobActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -4106,7 +4089,7 @@ class AdHocCommandAdHocCommandEventsList(BaseAdHocCommandEventsList): parent_model = models.AdHocCommand -class AdHocCommandActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class AdHocCommandActivityStreamList(SubListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer @@ -4402,14 +4385,14 @@ class LabelDetail(RetrieveUpdateAPIView): serializer_class = serializers.LabelSerializer -class ActivityStreamList(ActivityStreamEnforcementMixin, SimpleListAPIView): +class ActivityStreamList(SimpleListAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer search_fields = ('changes',) -class ActivityStreamDetail(ActivityStreamEnforcementMixin, RetrieveAPIView): +class ActivityStreamDetail(RetrieveAPIView): model = models.ActivityStream serializer_class = serializers.ActivityStreamSerializer diff --git a/awx/api/views/inventory.py b/awx/api/views/inventory.py index 896fc42add51..c64521202a79 100644 --- a/awx/api/views/inventory.py +++ b/awx/api/views/inventory.py @@ -48,7 +48,6 @@ JobTemplateSerializer, ) from awx.api.views.mixin import ( - ActivityStreamEnforcementMixin, RelatedJobsPreventDeleteMixin, ControlledByScmMixin, ) @@ -149,7 +148,7 @@ def destroy(self, request, *args, **kwargs): return Response(dict(error=_("{0}".format(e))), status=status.HTTP_400_BAD_REQUEST) -class InventoryActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class InventoryActivityStreamList(SubListAPIView): model = ActivityStream serializer_class = ActivityStreamSerializer diff --git a/awx/api/views/mixin.py b/awx/api/views/mixin.py index ee174d50912b..546b7090f25a 100644 --- a/awx/api/views/mixin.py +++ b/awx/api/views/mixin.py @@ -31,48 +31,11 @@ from awx.main.models.projects import Project from awx.main.models.inventory import Inventory from awx.main.models.jobs import JobTemplate -from awx.conf.license import ( - feature_enabled, - LicenseForbids, -) from awx.api.exceptions import ActiveJobConflict logger = logging.getLogger('awx.api.views.mixin') -class ActivityStreamEnforcementMixin(object): - ''' - Mixin to check that license supports activity streams. - ''' - def check_permissions(self, request): - ret = super(ActivityStreamEnforcementMixin, self).check_permissions(request) - if not feature_enabled('activity_streams'): - raise LicenseForbids(_('Your license does not allow use of the activity stream.')) - return ret - - -class SystemTrackingEnforcementMixin(object): - ''' - Mixin to check that license supports system tracking. - ''' - def check_permissions(self, request): - ret = super(SystemTrackingEnforcementMixin, self).check_permissions(request) - if not feature_enabled('system_tracking'): - raise LicenseForbids(_('Your license does not permit use of system tracking.')) - return ret - - -class WorkflowsEnforcementMixin(object): - ''' - Mixin to check that license supports workflows. - ''' - def check_permissions(self, request): - ret = super(WorkflowsEnforcementMixin, self).check_permissions(request) - if not feature_enabled('workflows') and request.method not in ('GET', 'OPTIONS', 'DELETE'): - raise LicenseForbids(_('Your license does not allow use of workflows.')) - return ret - - class UnifiedJobDeletionMixin(object): ''' Special handling when deleting a running unified job object. diff --git a/awx/api/views/organization.py b/awx/api/views/organization.py index 3c46ad3cca3f..93d03b55a313 100644 --- a/awx/api/views/organization.py +++ b/awx/api/views/organization.py @@ -7,13 +7,8 @@ # Django from django.db.models import Count from django.contrib.contenttypes.models import ContentType -from django.utils.translation import ugettext_lazy as _ # AWX -from awx.conf.license import ( - feature_enabled, - LicenseForbids, -) from awx.main.models import ( ActivityStream, Inventory, @@ -50,7 +45,6 @@ InstanceGroupSerializer, ) from awx.api.views.mixin import ( - ActivityStreamEnforcementMixin, RelatedJobsPreventDeleteMixin, OrganizationCountsMixin, ) @@ -69,24 +63,6 @@ def get_queryset(self): qs = qs.prefetch_related('created_by', 'modified_by') return qs - def create(self, request, *args, **kwargs): - """Create a new organzation. - - If there is already an organization and the license of this - instance does not permit multiple organizations, then raise - LicenseForbids. - """ - # Sanity check: If the multiple organizations feature is disallowed - # by the license, then we are only willing to create this organization - # if no organizations exist in the system. - if (not feature_enabled('multiple_organizations') and - self.model.objects.exists()): - raise LicenseForbids(_('Your license only permits a single ' - 'organization to exist.')) - - # Okay, create the organization as usual. - return super(OrganizationList, self).create(request, *args, **kwargs) - class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView): @@ -177,7 +153,7 @@ class OrganizationTeamsList(SubListCreateAttachDetachAPIView): parent_key = 'organization' -class OrganizationActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView): +class OrganizationActivityStreamList(SubListAPIView): model = ActivityStream serializer_class = ActivityStreamSerializer @@ -244,4 +220,3 @@ def get_queryset(self): po = self.get_parent_object() content_type = ContentType.objects.get_for_model(self.parent_model) return Role.objects.filter(content_type=content_type, object_id=po.pk) - diff --git a/awx/api/views/root.py b/awx/api/views/root.py index 3ee22c6673ba..96c88601ebeb 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -26,7 +26,7 @@ to_python_boolean, ) from awx.api.versioning import reverse, get_request_version, drf_reverse -from awx.conf.license import get_license, feature_enabled +from awx.conf.license import get_license from awx.main.constants import PRIVILEGE_ESCALATION_METHODS from awx.main.models import ( Project, @@ -57,9 +57,8 @@ def get(self, request, format=None): data['current_version'] = v2 data['available_versions'] = dict(v1 = v1, v2 = v2) data['oauth2'] = drf_reverse('api:oauth_authorization_root_view') - if feature_enabled('rebranding'): - data['custom_logo'] = settings.CUSTOM_LOGO - data['custom_login_info'] = settings.CUSTOM_LOGIN_INFO + data['custom_logo'] = settings.CUSTOM_LOGO + data['custom_login_info'] = settings.CUSTOM_LOGIN_INFO return Response(data) @@ -213,7 +212,7 @@ def get(self, request, format=None): # If LDAP is enabled, user_ldap_fields will return a list of field # names that are managed by LDAP and should be read-only for users with # a non-empty ldap_dn attribute. - if getattr(settings, 'AUTH_LDAP_SERVER_URI', None) and feature_enabled('ldap'): + if getattr(settings, 'AUTH_LDAP_SERVER_URI', None): user_ldap_fields = ['username', 'password'] user_ldap_fields.extend(getattr(settings, 'AUTH_LDAP_USER_ATTR_MAP', {}).keys()) user_ldap_fields.extend(getattr(settings, 'AUTH_LDAP_USER_FLAGS_BY_GROUP', {}).keys()) diff --git a/awx/conf/conf.py b/awx/conf/conf.py index 8ba0d072b0b5..6de90427e06e 100644 --- a/awx/conf/conf.py +++ b/awx/conf/conf.py @@ -78,9 +78,6 @@ def _get_read_only_ansible_cow_selection_default(): # the other settings change, the cached value for this setting will be # cleared to require it to be recomputed. depends_on=['ANSIBLE_COW_SELECTION'], - # Optional; licensed feature required to be able to view or modify this - # setting. - feature_required='rebranding', # Optional; field is stored encrypted in the database and only $encrypted$ # is returned via the API. encrypted=True, diff --git a/awx/conf/license.py b/awx/conf/license.py index a2e358847099..805dd1fba03a 100644 --- a/awx/conf/license.py +++ b/awx/conf/license.py @@ -1,64 +1,19 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved. -# Django -from django.core.signals import setting_changed -from django.dispatch import receiver -from django.utils.translation import ugettext_lazy as _ - -# Django REST Framework -from rest_framework.exceptions import APIException - # Tower from awx.main.utils.common import get_licenser -from awx.main.utils import memoize, memoize_delete - -__all__ = ['LicenseForbids', 'get_license', 'get_licensed_features', - 'feature_enabled', 'feature_exists'] - -class LicenseForbids(APIException): - status_code = 402 - default_detail = _('Your Tower license does not allow that.') +__all__ = ['get_license'] def _get_validated_license_data(): return get_licenser().validate() -@receiver(setting_changed) -def _on_setting_changed(sender, **kwargs): - # Clear cached result above when license changes. - if kwargs.get('setting', None) == 'LICENSE': - memoize_delete('feature_enabled') - - def get_license(show_key=False): """Return a dictionary representing the active license on this Tower instance.""" license_data = _get_validated_license_data() if not show_key: license_data.pop('license_key', None) return license_data - - -def get_licensed_features(): - """Return a set of all features enabled by the active license.""" - features = set() - for feature, enabled in _get_validated_license_data().get('features', {}).items(): - if enabled: - features.add(feature) - return features - - -@memoize(track_function=True) -def feature_enabled(name): - """Return True if the requested feature is enabled, False otherwise.""" - validated_license_data = _get_validated_license_data() - if validated_license_data.get('license_type', 'UNLICENSED') == 'open': - return True - return validated_license_data.get('features', {}).get(name, False) - - -def feature_exists(name): - """Return True if the requested feature name exists, False otherwise.""" - return bool(name in _get_validated_license_data().get('features', {})) diff --git a/awx/conf/registry.py b/awx/conf/registry.py index 6ce5cff8282d..63336fc55eac 100644 --- a/awx/conf/registry.py +++ b/awx/conf/registry.py @@ -68,7 +68,7 @@ def unregister_validate(self, category_slug): def get_dependent_settings(self, setting): return self._dependent_settings.get(setting, set()) - def get_registered_categories(self, features_enabled=None): + def get_registered_categories(self): categories = { 'all': _('All'), 'changed': _('Changed'), @@ -77,10 +77,6 @@ def get_registered_categories(self, features_enabled=None): category_slug = kwargs.get('category_slug', None) if category_slug is None or category_slug in categories: continue - if features_enabled is not None: - feature_required = kwargs.get('feature_required', None) - if feature_required and feature_required not in features_enabled: - continue if category_slug == 'user': categories['user'] = _('User') categories['user-defaults'] = _('User-Defaults') @@ -88,7 +84,7 @@ def get_registered_categories(self, features_enabled=None): categories[category_slug] = kwargs.get('category', None) or category_slug return categories - def get_registered_settings(self, category_slug=None, read_only=None, features_enabled=None, slugs_to_ignore=set()): + def get_registered_settings(self, category_slug=None, read_only=None, slugs_to_ignore=set()): setting_names = [] if category_slug == 'user-defaults': category_slug = 'user' @@ -104,10 +100,6 @@ def get_registered_settings(self, category_slug=None, read_only=None, features_e # Note: Doesn't catch fields that set read_only via __init__; # read-only field kwargs should always include read_only=True. continue - if features_enabled is not None: - feature_required = kwargs.get('feature_required', None) - if feature_required and feature_required not in features_enabled: - continue setting_names.append(setting) return setting_names @@ -135,7 +127,6 @@ def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs) category = field_kwargs.pop('category', None) depends_on = frozenset(field_kwargs.pop('depends_on', None) or []) placeholder = field_kwargs.pop('placeholder', empty) - feature_required = field_kwargs.pop('feature_required', empty) encrypted = bool(field_kwargs.pop('encrypted', False)) defined_in_file = bool(field_kwargs.pop('defined_in_file', False)) if getattr(field_kwargs.get('child', None), 'source', None) is not None: @@ -146,8 +137,6 @@ def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs) field_instance.depends_on = depends_on if placeholder is not empty: field_instance.placeholder = placeholder - if feature_required is not empty: - field_instance.feature_required = feature_required field_instance.defined_in_file = defined_in_file if field_instance.defined_in_file: field_instance.help_text = ( diff --git a/awx/conf/tests/unit/test_registry.py b/awx/conf/tests/unit/test_registry.py index b996751f0f46..c25ea0072485 100644 --- a/awx/conf/tests/unit/test_registry.py +++ b/awx/conf/tests/unit/test_registry.py @@ -119,20 +119,6 @@ def test_get_registered_read_only_settings(reg): ] -def test_get_registered_settings_with_required_features(reg): - reg.register( - 'AWX_SOME_SETTING_ENABLED', - field_class=fields.BooleanField, - category=_('System'), - category_slug='system', - feature_required='superpowers', - ) - assert reg.get_registered_settings(features_enabled=[]) == [] - assert reg.get_registered_settings(features_enabled=['superpowers']) == [ - 'AWX_SOME_SETTING_ENABLED' - ] - - def test_get_dependent_settings(reg): reg.register( 'AWX_SOME_SETTING_ENABLED', @@ -173,45 +159,6 @@ def test_get_registered_categories(reg): } -def test_get_registered_categories_with_required_features(reg): - reg.register( - 'AWX_SOME_SETTING_ENABLED', - field_class=fields.BooleanField, - category=_('System'), - category_slug='system', - feature_required='superpowers' - ) - reg.register( - 'AWX_SOME_OTHER_SETTING_ENABLED', - field_class=fields.BooleanField, - category=_('OtherSystem'), - category_slug='other-system', - feature_required='sortapowers' - ) - assert reg.get_registered_categories(features_enabled=[]) == { - 'all': _('All'), - 'changed': _('Changed'), - } - assert reg.get_registered_categories(features_enabled=['superpowers']) == { - 'all': _('All'), - 'changed': _('Changed'), - 'system': _('System'), - } - assert reg.get_registered_categories(features_enabled=['sortapowers']) == { - 'all': _('All'), - 'changed': _('Changed'), - 'other-system': _('OtherSystem'), - } - assert reg.get_registered_categories( - features_enabled=['superpowers', 'sortapowers'] - ) == { - 'all': _('All'), - 'changed': _('Changed'), - 'system': _('System'), - 'other-system': _('OtherSystem'), - } - - def test_is_setting_encrypted(reg): reg.register( 'AWX_SOME_SETTING_ENABLED', @@ -237,7 +184,6 @@ def test_simple_field(reg): category=_('System'), category_slug='system', placeholder='Example Value', - feature_required='superpowers' ) field = reg.get_setting_field('AWX_SOME_SETTING') @@ -246,7 +192,6 @@ def test_simple_field(reg): assert field.category_slug == 'system' assert field.default is empty assert field.placeholder == 'Example Value' - assert field.feature_required == 'superpowers' def test_field_with_custom_attribute(reg): diff --git a/awx/conf/views.py b/awx/conf/views.py index 448b300d3c8c..ac704e9f3718 100644 --- a/awx/conf/views.py +++ b/awx/conf/views.py @@ -28,7 +28,6 @@ from awx.main.utils import camelcase_to_underscore from awx.main.utils.handlers import AWXProxyHandler, LoggingConnectivityException from awx.main.tasks import handle_setting_changes -from awx.conf.license import get_licensed_features from awx.conf.models import Setting from awx.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer from awx.conf import settings_registry @@ -53,7 +52,7 @@ class SettingCategoryList(ListAPIView): def get_queryset(self): setting_categories = [] - categories = settings_registry.get_registered_categories(features_enabled=get_licensed_features()) + categories = settings_registry.get_registered_categories() if self.request.user.is_superuser or self.request.user.is_system_auditor: pass # categories = categories elif 'user' in categories: @@ -77,7 +76,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView): def get_queryset(self): self.category_slug = self.kwargs.get('category_slug', 'all') - all_category_slugs = list(settings_registry.get_registered_categories(features_enabled=get_licensed_features()).keys()) + all_category_slugs = list(settings_registry.get_registered_categories().keys()) for slug_to_delete in VERSION_SPECIFIC_CATEGORIES_TO_EXCLUDE[get_request_version(self.request)]: all_category_slugs.remove(slug_to_delete) if self.request.user.is_superuser or getattr(self.request.user, 'is_system_auditor', False): @@ -90,7 +89,7 @@ def get_queryset(self): raise PermissionDenied() registered_settings = settings_registry.get_registered_settings( - category_slug=self.category_slug, read_only=False, features_enabled=get_licensed_features(), + category_slug=self.category_slug, read_only=False, slugs_to_ignore=VERSION_SPECIFIC_CATEGORIES_TO_EXCLUDE[get_request_version(self.request)] ) if self.category_slug == 'user': @@ -101,7 +100,7 @@ def get_queryset(self): def get_object(self): settings_qs = self.get_queryset() registered_settings = settings_registry.get_registered_settings( - category_slug=self.category_slug, features_enabled=get_licensed_features(), + category_slug=self.category_slug, slugs_to_ignore=VERSION_SPECIFIC_CATEGORIES_TO_EXCLUDE[get_request_version(self.request)] ) all_settings = {} diff --git a/awx/main/access.py b/awx/main/access.py index 01bd3e309663..b09783a912df 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -41,8 +41,6 @@ ) from awx.main.models.mixins import ResourceMixin -from awx.conf.license import LicenseForbids, feature_enabled - __all__ = ['get_user_queryset', 'check_user_access', 'check_user_access_with_errors', 'user_accessible_objects', 'consumer_access',] @@ -324,12 +322,6 @@ def check_license(self, add_host_name=None, feature=None, check_expiration=True) elif not add_host_name and free_instances < 0: raise PermissionDenied(_("Host count exceeds available instances.")) - if feature is not None: - if "features" in validation_info and not validation_info["features"].get(feature, False): - raise LicenseForbids(_("Feature %s is not enabled in the active license.") % feature) - elif "features" not in validation_info: - raise LicenseForbids(_("Features not found in active license.")) - def check_org_host_limit(self, data, add_host_name=None): validation_info = get_licenser().validate() if validation_info.get('license_type', 'UNLICENSED') == 'open': @@ -383,9 +375,6 @@ def get_user_capabilities(self, obj, method_list=[], parent_obj=None, capabiliti if obj.validation_errors: user_capabilities[display_method] = False continue - elif isinstance(obj, (WorkflowJobTemplate, WorkflowJob)) and (not feature_enabled('workflows')): - user_capabilities[display_method] = (display_method == 'delete') - continue elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None: user_capabilities[display_method] = self.user.is_superuser continue @@ -776,7 +765,7 @@ def can_change(self, obj, data): return self.user in obj.admin_role def can_delete(self, obj): - self.check_license(feature='multiple_organizations', check_expiration=False) + self.check_license(check_expiration=False) is_change_possible = self.can_change(obj, None) if not is_change_possible: return False @@ -1492,11 +1481,6 @@ def can_start(self, obj, validate_license=True): # Check the per-org limit self.check_org_host_limit({'inventory': obj.inventory}) - if obj.survey_enabled: - self.check_license(feature='surveys') - if Instance.objects.active_count() > 1: - self.check_license(feature='ha') - # Super users can start any job if self.user.is_superuser: return True @@ -2021,10 +2005,6 @@ def can_start(self, obj, validate_license=True): # Check the per-org limit self.check_org_host_limit({'inventory': obj.inventory}) - # if surveys are added to WFJTs, check license here - if obj.survey_enabled: - self.check_license(feature='surveys') - # Super users can start any job if self.user.is_superuser: return True @@ -2032,11 +2012,6 @@ def can_start(self, obj, validate_license=True): return self.user in obj.execute_role def can_change(self, obj, data): - # Check survey license if surveys are added to WFJTs - if (data and 'survey_enabled' in data and - obj.survey_enabled != data['survey_enabled'] and data['survey_enabled']): - self.check_license(feature='surveys') - if self.user.is_superuser: return True diff --git a/awx/main/conf.py b/awx/main/conf.py index 941926f75afe..949c52726c61 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -21,7 +21,6 @@ help_text=_('Enable capturing activity for the activity stream.'), category=_('System'), category_slug='system', - feature_required='activity_streams', ) register( @@ -31,7 +30,6 @@ help_text=_('Enable capturing activity for the activity stream when running inventory sync.'), category=_('System'), category_slug='system', - feature_required='activity_streams', ) register( diff --git a/awx/main/management/commands/cleanup_facts.py b/awx/main/management/commands/cleanup_facts.py index 76bc4190fb07..f4a7fe39840f 100644 --- a/awx/main/management/commands/cleanup_facts.py +++ b/awx/main/management/commands/cleanup_facts.py @@ -13,7 +13,6 @@ # AWX from awx.main.models.fact import Fact -from awx.conf.license import feature_enabled OLDER_THAN = 'older_than' GRANULARITY = 'granularity' @@ -30,7 +29,7 @@ def __init__(self): # Delete all except LAST entry (or Delete all except the FIRST entry, it's an arbitrary decision) # # pivot -= granularity - # group by host + # group by host def cleanup(self, older_than_abs, granularity, module=None): fact_oldest = Fact.objects.all().order_by('timestamp').first() if not fact_oldest: @@ -114,7 +113,7 @@ def __init__(self): def string_time_to_timestamp(self, time_string): units = { 'y': 'years', - 'd': 'days', + 'd': 'days', 'w': 'weeks', 'm': 'months' } @@ -131,8 +130,6 @@ def string_time_to_timestamp(self, time_string): @transaction.atomic def handle(self, *args, **options): sys.stderr.write("This command has been deprecated and will be removed in a future release.\n") - if not feature_enabled('system_tracking'): - raise CommandError("The System Tracking feature is not enabled for your instance") cleanup_facts = CleanupFacts() if not all([options[GRANULARITY], options[OLDER_THAN]]): raise CommandError('Both --granularity and --older_than are required.') diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 6221e54f7cd1..83a27ed3ea55 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -1088,7 +1088,7 @@ def handle(self, *args, **options): logger.warning('update computed fields took %d queries', len(connection.queries) - queries_before2) # Check if the license is valid. - # If the license is not valid, a CommandError will be thrown, + # If the license is not valid, a CommandError will be thrown, # and inventory update will be marked as invalid. # with transaction.atomic() will roll back the changes. license_fail = True diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py index 72ae48d9e41e..8e49da6e0c9e 100644 --- a/awx/main/tests/functional/api/test_activity_streams.py +++ b/awx/main/tests/functional/api/test_activity_streams.py @@ -1,4 +1,3 @@ -from unittest import mock import pytest from awx.api.versioning import reverse @@ -8,16 +7,12 @@ from awx.conf.models import Setting -def mock_feature_enabled(feature): - return True - @pytest.fixture def activity_stream_entry(organization, org_admin): return ActivityStream.objects.filter(organization__pk=organization.pk, user=org_admin, operation='associate').first() -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_get_activity_stream_list(monkeypatch, organization, get, user, settings): settings.ACTIVITY_STREAM_ENABLED = True @@ -27,7 +22,6 @@ def test_get_activity_stream_list(monkeypatch, organization, get, user, settings assert response.status_code == 200 -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_basic_fields(monkeypatch, organization, get, user, settings): settings.ACTIVITY_STREAM_ENABLED = True @@ -48,7 +42,6 @@ def test_basic_fields(monkeypatch, organization, get, user, settings): assert response.data['summary_fields']['organization'][0]['name'] == 'test-org' -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_ctint_activity_stream(monkeypatch, get, user, settings): Setting.objects.create(key="FOO", value="bar") @@ -68,7 +61,6 @@ def test_ctint_activity_stream(monkeypatch, get, user, settings): assert response.data['summary_fields']['setting'][0]['name'] == 'FOO' -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_middleware_actor_added(monkeypatch, post, get, user, settings): settings.ACTIVITY_STREAM_ENABLED = True @@ -91,7 +83,6 @@ def test_middleware_actor_added(monkeypatch, post, get, user, settings): assert response.data['summary_fields']['actor']['username'] == 'admin-poster' -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_rbac_stream_resource_roles(activity_stream_entry, organization, org_admin, settings): settings.ACTIVITY_STREAM_ENABLED = True @@ -101,7 +92,6 @@ def test_rbac_stream_resource_roles(activity_stream_entry, organization, org_adm assert activity_stream_entry.object_relationship_type == 'awx.main.models.organization.Organization.admin_role' -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_rbac_stream_user_roles(activity_stream_entry, organization, org_admin, settings): settings.ACTIVITY_STREAM_ENABLED = True @@ -113,7 +103,6 @@ def test_rbac_stream_user_roles(activity_stream_entry, organization, org_admin, @pytest.mark.django_db @pytest.mark.activity_stream_access -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) def test_stream_access_cant_change(activity_stream_entry, organization, org_admin, settings): settings.ACTIVITY_STREAM_ENABLED = True access = ActivityStreamAccess(org_admin) @@ -125,7 +114,6 @@ def test_stream_access_cant_change(activity_stream_entry, organization, org_admi @pytest.mark.django_db @pytest.mark.activity_stream_access -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) def test_stream_queryset_hides_shows_items( activity_stream_entry, organization, user, org_admin, project, org_credential, inventory, label, deploy_jobtemplate, @@ -160,7 +148,6 @@ def test_stream_queryset_hides_shows_items( @pytest.mark.django_db -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) def test_stream_user_direct_role_updates(get, post, organization_factory): objects = organization_factory('test_org', superusers=['admin'], diff --git a/awx/main/tests/functional/api/test_fact_versions.py b/awx/main/tests/functional/api/test_fact_versions.py index 0c7c43b46ce5..a8d1db6c4a74 100644 --- a/awx/main/tests/functional/api/test_fact_versions.py +++ b/awx/main/tests/functional/api/test_fact_versions.py @@ -1,5 +1,4 @@ # Python -from unittest import mock import pytest from datetime import timedelta import urllib.parse @@ -13,14 +12,6 @@ from django.utils import timezone -def mock_feature_enabled(feature): - return True - - -def mock_feature_disabled(feature): - return False - - def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), get_params={}, host_count=1): hosts = hosts(host_count=host_count) fact_scans(fact_scans=3, timestamp_epoch=epoch) @@ -53,36 +44,7 @@ def check_response_facts(facts_known, response): check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module) -def check_system_tracking_feature_forbidden(response): - assert 402 == response.status_code - assert 'Your license does not permit use of system tracking.' == response.data['detail'] - - -@mock.patch('awx.api.views.mixin.feature_enabled', new=mock_feature_disabled) -@pytest.mark.django_db -@pytest.mark.license_feature -def test_system_tracking_license_get(hosts, get, user): - hosts = hosts(host_count=1) - url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk}) - response = get(url, user('admin', True)) - - check_system_tracking_feature_forbidden(response) - - -@mock.patch('awx.api.views.mixin.feature_enabled', new=mock_feature_disabled) -@pytest.mark.django_db -@pytest.mark.license_feature -def test_system_tracking_license_options(hosts, options, user): - hosts = hosts(host_count=1) - url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk}) - response = options(url, None, user('admin', True)) - - check_system_tracking_feature_forbidden(response) - - -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db -@pytest.mark.license_feature def test_no_facts_db(hosts, get, user): hosts = hosts(host_count=1) url = reverse('api:host_fact_versions_list', kwargs={'pk': hosts[0].pk}) @@ -94,7 +56,6 @@ def test_no_facts_db(hosts, get, user): assert response_expected == response.data -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_basic_fields(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -111,9 +72,7 @@ def test_basic_fields(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_d assert 'module' in results[0] -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db -@pytest.mark.license_feature def test_basic_options_fields(hosts, fact_scans, options, user, monkeypatch_jsonbfield_get_db_prep_save): hosts = hosts(host_count=1) fact_scans(fact_scans=1) @@ -128,7 +87,6 @@ def test_basic_options_fields(hosts, fact_scans, options, user, monkeypatch_json assert ("packages", "Packages") in response.data['actions']['GET']['module']['choices'] -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_related_fact_view(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -142,7 +100,6 @@ def test_related_fact_view(hosts, fact_scans, get, user, monkeypatch_jsonbfield_ check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module) -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_multiple_hosts(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -156,7 +113,6 @@ def test_multiple_hosts(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module) -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_param_to_from(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -173,7 +129,6 @@ def test_param_to_from(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_ check_response_facts(facts_known, response) -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_param_module(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -189,7 +144,6 @@ def test_param_module(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_d check_response_facts(facts_known, response) -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_param_from(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -205,7 +159,6 @@ def test_param_from(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_ check_response_facts(facts_known, response) -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_param_to(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -232,7 +185,6 @@ def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj): return response -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_normal_user_403(hosts, fact_scans, get, user, team, monkeypatch_jsonbfield_get_db_prep_save): @@ -243,7 +195,6 @@ def test_normal_user_403(hosts, fact_scans, get, user, team, monkeypatch_jsonbfi assert "You do not have permission to perform this action." == response.data['detail'] -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_super_user_ok(hosts, fact_scans, get, user, team, monkeypatch_jsonbfield_get_db_prep_save): @@ -253,7 +204,6 @@ def test_super_user_ok(hosts, fact_scans, get, user, team, monkeypatch_jsonbfiel assert 200 == response.status_code -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_user_admin_ok(organization, hosts, fact_scans, get, user, team): @@ -265,7 +215,6 @@ def test_user_admin_ok(organization, hosts, fact_scans, get, user, team): assert 200 == response.status_code -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_user_admin_403(organization, organizations, hosts, fact_scans, get, user, team, monkeypatch_jsonbfield_get_db_prep_save): diff --git a/awx/main/tests/functional/api/test_fact_view.py b/awx/main/tests/functional/api/test_fact_view.py index 416259923e61..950e9a67af84 100644 --- a/awx/main/tests/functional/api/test_fact_view.py +++ b/awx/main/tests/functional/api/test_fact_view.py @@ -1,4 +1,3 @@ -from unittest import mock import pytest import json @@ -8,14 +7,6 @@ from django.utils import timezone -def mock_feature_enabled(feature): - return True - - -def mock_feature_disabled(feature): - return False - - # TODO: Consider making the fact_scan() fixture a Class, instead of a function, and move this method into it def find_fact(facts, host_id, module_name, timestamp): for f in facts: @@ -35,34 +26,6 @@ def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), module_name return (fact_known, response) -def check_system_tracking_feature_forbidden(response): - assert 402 == response.status_code - assert 'Your license does not permit use of system tracking.' == response.data['detail'] - - -@mock.patch('awx.api.views.mixin.feature_enabled', new=mock_feature_disabled) -@pytest.mark.django_db -@pytest.mark.license_feature -def test_system_tracking_license_get(hosts, get, user): - hosts = hosts(host_count=1) - url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk}) - response = get(url, user('admin', True)) - - check_system_tracking_feature_forbidden(response) - - -@mock.patch('awx.api.views.mixin.feature_enabled', new=mock_feature_disabled) -@pytest.mark.django_db -@pytest.mark.license_feature -def test_system_tracking_license_options(hosts, options, user): - hosts = hosts(host_count=1) - url = reverse('api:host_fact_compare_view', kwargs={'pk': hosts[0].pk}) - response = options(url, None, user('admin', True)) - - check_system_tracking_feature_forbidden(response) - - -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_no_fact_found(hosts, get, user): hosts = hosts(host_count=1) @@ -76,7 +39,6 @@ def test_no_fact_found(hosts, get, user): assert expected_response == response.data -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_basic_fields(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_db_prep_save): hosts = hosts(host_count=1) @@ -99,7 +61,6 @@ def test_basic_fields(hosts, fact_scans, get, user, monkeypatch_jsonbfield_get_d assert reverse('api:host_detail', kwargs={'pk': hosts[0].pk}) == response.data['related']['host'] -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_content(hosts, fact_scans, get, user, fact_ansible_json, monkeypatch_jsonbfield_get_db_prep_save): (fact_known, response) = setup_common(hosts, fact_scans, get, user) @@ -123,19 +84,16 @@ def _test_search_by_module(hosts, fact_scans, get, user, fact_json, module_name) assert module_name == response.data['module'] -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_search_by_module_packages(hosts, fact_scans, get, user, fact_packages_json, monkeypatch_jsonbfield_get_db_prep_save): _test_search_by_module(hosts, fact_scans, get, user, fact_packages_json, 'packages') -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_search_by_module_services(hosts, fact_scans, get, user, fact_services_json, monkeypatch_jsonbfield_get_db_prep_save): _test_search_by_module(hosts, fact_scans, get, user, fact_services_json, 'services') -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_search_by_timestamp_and_module(hosts, fact_scans, get, user, fact_packages_json, monkeypatch_jsonbfield_get_db_prep_save): epoch = timezone.now() @@ -160,7 +118,6 @@ def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj): return response -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_normal_user_403(hosts, fact_scans, get, user, team, monkeypatch_jsonbfield_get_db_prep_save): @@ -171,7 +128,6 @@ def test_normal_user_403(hosts, fact_scans, get, user, team, monkeypatch_jsonbfi assert "You do not have permission to perform this action." == response.data['detail'] -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_super_user_ok(hosts, fact_scans, get, user, team, monkeypatch_jsonbfield_get_db_prep_save): @@ -181,7 +137,6 @@ def test_super_user_ok(hosts, fact_scans, get, user, team, monkeypatch_jsonbfiel assert 200 == response.status_code -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_user_admin_ok(organization, hosts, fact_scans, get, user, team, monkeypatch_jsonbfield_get_db_prep_save): @@ -193,7 +148,6 @@ def test_user_admin_ok(organization, hosts, fact_scans, get, user, team, monkeyp assert 200 == response.status_code -@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) @pytest.mark.ac @pytest.mark.django_db def test_user_admin_403(organization, organizations, hosts, fact_scans, get, user, team, monkeypatch_jsonbfield_get_db_prep_save): diff --git a/awx/main/tests/functional/api/test_organizations.py b/awx/main/tests/functional/api/test_organizations.py index 18aeb269aceb..082719967631 100644 --- a/awx/main/tests/functional/api/test_organizations.py +++ b/awx/main/tests/functional/api/test_organizations.py @@ -7,7 +7,6 @@ from backports.tempfile import TemporaryDirectory from django.conf import settings import pytest -from unittest import mock # AWX from awx.main.models import ProjectUpdate @@ -131,10 +130,9 @@ def test_organization_inventory_list(organization, inventory_factory, get, alice assert get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=alice).data['count'] == 2 assert get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=bob).data['count'] == 1 get(reverse('api:organization_inventories_list', kwargs={'pk': organization.id}), user=rando, expect=403) - + @pytest.mark.django_db -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) def test_create_organization(post, admin, alice): new_org = { 'name': 'new org', @@ -146,7 +144,6 @@ def test_create_organization(post, admin, alice): @pytest.mark.django_db -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) def test_create_organization_xfail(post, alice): new_org = { 'name': 'new org', @@ -224,27 +221,23 @@ def test_update_organization_max_hosts(get, put, organization, admin, alice, bob @pytest.mark.django_db -@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) def test_delete_organization(delete, organization, admin): delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=admin, expect=204) @pytest.mark.django_db -@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) def test_delete_organization2(delete, organization, alice): organization.admin_role.members.add(alice) delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=alice, expect=204) @pytest.mark.django_db -@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) def test_delete_organization_xfail1(delete, organization, alice): organization.member_role.members.add(alice) delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=alice, expect=403) @pytest.mark.django_db -@mock.patch('awx.main.access.BaseAccess.check_license', lambda *a, **kw: True) def test_delete_organization_xfail2(delete, organization): delete(reverse('api:organization_detail', kwargs={'pk': organization.id}), user=None, expect=401) @@ -295,5 +288,3 @@ def sort_keys(x): assert resp.data['error'] == u"Resource is being used by running jobs." assert resp_sorted == expect_sorted - - diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py index 822456b92b8c..362fcb924a18 100644 --- a/awx/main/tests/functional/api/test_rbac_displays.py +++ b/awx/main/tests/functional/api/test_rbac_displays.py @@ -332,13 +332,3 @@ def test_manual_projects_no_update(manual_project, get, admin_user): response = get(reverse('api:project_detail', kwargs={'pk': manual_project.pk}), admin_user, expect=200) assert not response.data['summary_fields']['user_capabilities']['start'] assert not response.data['summary_fields']['user_capabilities']['schedule'] - - -@pytest.mark.django_db -def test_license_check_not_called(mocker, job_template, project, org_admin, get): - job_template.project = project - job_template.save() # need this to make the JT visible - mock_license_check = mocker.MagicMock() - with mocker.patch('awx.main.access.BaseAccess.check_license', mock_license_check): - get(reverse('api:job_template_detail', kwargs={'pk': job_template.pk}), org_admin, expect=200) - assert not mock_license_check.called diff --git a/awx/main/tests/functional/api/test_survey_spec.py b/awx/main/tests/functional/api/test_survey_spec.py index ef05f220ca9c..7e8288685766 100644 --- a/awx/main/tests/functional/api/test_survey_spec.py +++ b/awx/main/tests/functional/api/test_survey_spec.py @@ -6,17 +6,10 @@ from awx.api.versioning import reverse from awx.main.models.jobs import JobTemplate, Job from awx.main.models.activity_stream import ActivityStream -from awx.conf.license import LicenseForbids from awx.main.access import JobTemplateAccess from awx.main.utils.common import get_type_for_model -def mock_no_surveys(self, add_host=False, feature=None, check_expiration=True): - if feature == 'surveys': - raise LicenseForbids("Feature %s is not enabled in the active license." % feature) - else: - pass - @pytest.fixture def job_template_with_survey(job_template_factory): @@ -24,18 +17,6 @@ def job_template_with_survey(job_template_factory): return objects.job_template -# Survey license-based denial tests -@mock.patch('awx.api.views.feature_enabled', lambda feature: False) -@pytest.mark.django_db -@pytest.mark.survey -def test_survey_spec_view_denied(job_template_with_survey, get, admin_user): - # TODO: Test non-enterprise license - response = get(reverse('api:job_template_survey_spec', - kwargs={'pk': job_template_with_survey.id}), admin_user, expect=402) - assert response.data['detail'] == 'Your license does not allow adding surveys.' - - -@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys) @pytest.mark.django_db @pytest.mark.survey @pytest.mark.parametrize("role_field,expected_status_code", [ @@ -54,45 +35,7 @@ def test_survey_edit_access(job_template, workflow_job_template, survey_spec_fac user=rando, data=survey_input_data, expect=expected_status_code) -@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys) -@pytest.mark.django_db -@pytest.mark.survey -def test_deny_enabling_survey(deploy_jobtemplate, patch, admin_user): - response = patch(url=deploy_jobtemplate.get_absolute_url(), - data=dict(survey_enabled=True), user=admin_user, expect=402) - assert response.data['detail'] == 'Feature surveys is not enabled in the active license.' - - -@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys) -@pytest.mark.django_db -@pytest.mark.survey -def test_job_start_blocked_without_survey_license(job_template_with_survey, admin_user): - """Check that user can't start a job with surveys without a survey license.""" - access = JobTemplateAccess(admin_user) - with pytest.raises(LicenseForbids): - access.can_start(job_template_with_survey) - - -@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys) -@pytest.mark.django_db -@pytest.mark.survey -def test_deny_creating_with_survey(project, post, admin_user): - response = post( - url=reverse('api:job_template_list'), - data=dict( - name = 'JT with survey', - job_type = 'run', - project = project.pk, - playbook = 'helloworld.yml', - ask_credential_on_launch = True, - ask_inventory_on_launch = True, - survey_enabled = True), - user=admin_user, expect=402) - assert response.data['detail'] == 'Feature surveys is not enabled in the active license.' - - # Test normal operations with survey license work -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db @pytest.mark.survey def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user): @@ -100,7 +43,6 @@ def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user): admin_user, expect=200) -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db @pytest.mark.survey def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post, admin_user): @@ -111,7 +53,6 @@ def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post, assert updated_jt.survey_spec == survey_input_data -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db @pytest.mark.parametrize('with_default', [True, False]) @pytest.mark.parametrize('value, status', [ @@ -154,7 +95,6 @@ def test_survey_spec_passwords_are_encrypted_on_launch(job_template_factory, pos assert "for 'secret_value' expected to be a string." in json.dumps(resp.data) -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db def test_survey_spec_passwords_with_empty_default(job_template_factory, post, admin_user): objects = job_template_factory('jt', organization='org1', project='prj', @@ -186,7 +126,6 @@ def test_survey_spec_passwords_with_empty_default(job_template_factory, post, ad } -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db @pytest.mark.parametrize('default, launch_value, expected_extra_vars, status', [ ['', '$encrypted$', {'secret_value': ''}, 201], @@ -238,7 +177,6 @@ def test_survey_spec_passwords_with_default_optional(job_template_factory, post, assert launch_value not in json.loads(job.extra_vars).values() -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db @pytest.mark.parametrize('default, launch_value, expected_extra_vars, status', [ ['', '$encrypted$', {'secret_value': ''}, 201], @@ -281,7 +219,6 @@ def test_survey_spec_passwords_with_default_required(job_template_factory, post, assert launch_value not in json.loads(job.extra_vars).values() -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db @pytest.mark.parametrize('default, status', [ ('SUPERSECRET', 200), @@ -318,7 +255,6 @@ def test_survey_spec_default_passwords_are_encrypted(job_template, post, admin_u assert "expected to be string." in str(resp.data) -@mock.patch('awx.api.views.feature_enabled', lambda feature: True) @pytest.mark.django_db def test_survey_spec_default_passwords_encrypted_on_update(job_template, post, put, admin_user): input_data = { @@ -344,41 +280,6 @@ def test_survey_spec_default_passwords_encrypted_on_update(job_template, post, p assert updated_jt.survey_spec == JobTemplate.objects.get(pk=job_template.pk).survey_spec -# Test actions that should be allowed with non-survey license -@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys) -@pytest.mark.django_db -@pytest.mark.survey -def test_disable_survey_access_without_license(job_template_with_survey, admin_user): - """Assure that user can disable a JT survey after downgrading license.""" - access = JobTemplateAccess(admin_user) - assert access.can_change(job_template_with_survey, dict(survey_enabled=False)) - - -@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys) -@pytest.mark.django_db -@pytest.mark.survey -def test_delete_survey_access_without_license(job_template_with_survey, admin_user): - """Assure that access.py allows deleting surveys after downgrading license.""" - access = JobTemplateAccess(admin_user) - assert access.can_change(job_template_with_survey, dict(survey_spec=None)) - assert access.can_change(job_template_with_survey, dict(survey_spec={})) - - -@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys) -@pytest.mark.django_db -@pytest.mark.survey -def test_job_start_allowed_with_survey_spec(job_template_factory, admin_user): - """After user downgrades survey license and disables survey on the JT, - check that jobs still launch even if the survey_spec data persists.""" - objects = job_template_factory('jt', project='prj', survey='submitter_email') - obj = objects.job_template - obj.survey_enabled = False - obj.save() - access = JobTemplateAccess(admin_user) - assert access.can_start(job_template_with_survey, {}) - - -@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys) @pytest.mark.django_db @pytest.mark.survey def test_job_template_delete_access_with_survey(job_template_with_survey, admin_user): @@ -388,11 +289,9 @@ def test_job_template_delete_access_with_survey(job_template_with_survey, admin_ assert access.can_delete(job_template_with_survey) -@mock.patch('awx.api.views.feature_enabled', lambda feature: False) -@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys) @pytest.mark.django_db @pytest.mark.survey -def test_delete_survey_spec_without_license(job_template_with_survey, delete, admin_user): +def test_delete_survey_spec(job_template_with_survey, delete, admin_user): """Functional delete test through the survey_spec view.""" delete(reverse('api:job_template_survey_spec', kwargs={'pk': job_template_with_survey.pk}), admin_user, expect=200) @@ -400,7 +299,6 @@ def test_delete_survey_spec_without_license(job_template_with_survey, delete, ad assert new_jt.survey_spec == {} -@mock.patch('awx.main.access.BaseAccess.check_license', lambda self, **kwargs: True) @mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job', lambda self, **kwargs: mock.MagicMock(spec=Job, id=968)) @mock.patch('awx.api.serializers.JobSerializer.to_representation', lambda self, obj: {}) @@ -418,24 +316,6 @@ def test_launch_survey_enabled_but_no_survey_spec(job_template_factory, post, ad assert 'survey_var' in response.data['ignored_fields']['extra_vars'] -@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys) -@mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job', - lambda self: mock.MagicMock(spec=Job, id=968)) -@mock.patch('awx.api.serializers.JobSerializer.to_representation', lambda self, obj: {}) -@pytest.mark.django_db -@pytest.mark.survey -def test_launch_with_non_empty_survey_spec_no_license(job_template_factory, post, admin_user): - """Assure jobs can still be launched from JTs with a survey_spec - when the survey is diabled.""" - objects = job_template_factory('jt', organization='org1', project='prj', - inventory='inv', credential='cred', - survey='survey_var') - obj = objects.job_template - obj.survey_enabled = False - obj.save() - post(reverse('api:job_template_launch', kwargs={'pk': obj.pk}), {}, admin_user, expect=201) - - @pytest.mark.django_db @pytest.mark.survey def test_redact_survey_passwords_in_activity_stream(job_template_with_survey_passwords): diff --git a/awx/main/tests/functional/commands/test_cleanup_facts.py b/awx/main/tests/functional/commands/test_cleanup_facts.py index dbfae7a0c556..0028fda8e641 100644 --- a/awx/main/tests/functional/commands/test_cleanup_facts.py +++ b/awx/main/tests/functional/commands/test_cleanup_facts.py @@ -3,7 +3,6 @@ # Python import pytest -from unittest import mock from dateutil.relativedelta import relativedelta from datetime import timedelta @@ -17,13 +16,6 @@ from awx.main.models.inventory import Host -def mock_feature_enabled(feature): - return True - - -def mock_feature_disabled(feature): - return False - @pytest.mark.django_db def test_cleanup_granularity(fact_scans, hosts, monkeypatch_jsonbfield_get_db_prep_save): @@ -101,17 +93,7 @@ def test_cleanup_logic(fact_scans, hosts, monkeypatch_jsonbfield_get_db_prep_sav assert fact.timestamp == timestamp_pivot -@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_disabled) -@pytest.mark.django_db -@pytest.mark.license_feature -def test_system_tracking_feature_disabled(mocker): - cmd = Command() - with pytest.raises(CommandError) as err: - cmd.handle(None) - assert 'The System Tracking feature is not enabled for your instance' in str(err.value) - -@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_parameters_ok(mocker): run = mocker.patch('awx.main.management.commands.cleanup_facts.CleanupFacts.run') @@ -185,7 +167,6 @@ def test_string_time_to_timestamp_invalid(): assert res is None -@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_enabled) @pytest.mark.django_db def test_parameters_fail(mocker): # Mock run() just in case, but it should never get called because an error should be thrown diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index 725227d5ae1e..2e699da361e8 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -6,12 +6,6 @@ from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR -def mock_feature_enabled(feature): - return True - - -#@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled) - @pytest.fixture def role(): diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py index fb2eb7d4034c..9496f355c8f6 100644 --- a/awx/main/tests/unit/test_access.py +++ b/awx/main/tests/unit/test_access.py @@ -13,7 +13,6 @@ SystemJobTemplateAccess, ) -from awx.conf.license import LicenseForbids from awx.main.models import ( Credential, CredentialType, @@ -21,7 +20,6 @@ Project, Role, Organization, - Instance, ) @@ -204,31 +202,20 @@ def mock_get_object(Class, **kwargs): else: raise Exception('Item requested has not been mocked') - with mock.patch.object(JobTemplateAccess, 'check_license', return_value=None): - with mock.patch('awx.main.models.rbac.Role.__contains__', return_value=True): - with mock.patch('awx.main.access.get_object_or_400', mock_get_object): - assert access.can_add({ - 'project': project.pk, - 'inventory': inventory.pk, - 'job_type': 'scan' - }) - -def mock_raise_license_forbids(self, add_host=False, feature=None, check_expiration=True): - raise LicenseForbids("Feature not enabled") + with mock.patch('awx.main.models.rbac.Role.__contains__', return_value=True): + with mock.patch('awx.main.access.get_object_or_400', mock_get_object): + assert access.can_add({ + 'project': project.pk, + 'inventory': inventory.pk, + 'job_type': 'scan' + }) def mock_raise_none(self, add_host=False, feature=None, check_expiration=True): return None -def test_jt_can_start_ha(job_template_with_ids): - with mock.patch.object(Instance.objects, 'active_count', return_value=2): - with mock.patch('awx.main.access.BaseAccess.check_license', new=mock_raise_license_forbids): - with pytest.raises(LicenseForbids): - JobTemplateAccess(user_unit).can_start(job_template_with_ids) - - def test_jt_can_add_bad_data(user_unit): "Assure that no server errors are returned if we call JT can_add with bad data" access = JobTemplateAccess(user_unit) diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 54d5abe2664b..60f2bb806564 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -990,9 +990,8 @@ def has_model_field_prefetched(model_obj, field_name): def get_external_account(user): from django.conf import settings - from awx.conf.license import feature_enabled account_type = None - if getattr(settings, 'AUTH_LDAP_SERVER_URI', None) and feature_enabled('ldap'): + if getattr(settings, 'AUTH_LDAP_SERVER_URI', None): try: if user.pk and user.profile.ldap_dn and not user.has_usable_password(): account_type = "ldap" diff --git a/awx/sso/backends.py b/awx/sso/backends.py index 7fe786fc0982..13e55cbd8377 100644 --- a/awx/sso/backends.py +++ b/awx/sso/backends.py @@ -31,7 +31,6 @@ from social_core.backends.saml import SAMLIdentityProvider as BaseSAMLIdentityProvider # Ansible Tower -from awx.conf.license import feature_enabled from awx.sso.models import UserEnterpriseAuth logger = logging.getLogger('awx.sso.backends') @@ -94,9 +93,6 @@ def authenticate(self, username, password): if not self.settings.SERVER_URI: return None - if not feature_enabled('ldap'): - logger.error("Unable to authenticate, license does not support LDAP authentication") - return None try: user = User.objects.get(username=username) if user and (not user.profile or not user.profile.ldap_dn): @@ -121,9 +117,6 @@ def authenticate(self, username, password): def get_user(self, user_id): if not self.settings.SERVER_URI: return None - if not feature_enabled('ldap'): - logger.error("Unable to get_user, license does not support LDAP authentication") - return None return super(LDAPBackend, self).get_user(user_id) # Disable any LDAP based authorization / permissions checking. @@ -188,20 +181,14 @@ class RADIUSBackend(BaseRADIUSBackend): Custom Radius backend to verify license status ''' - def authenticate(self, username, password): + def authenticate(self, username, password): if not django_settings.RADIUS_SERVER: return None - if not feature_enabled('enterprise_auth'): - logger.error("Unable to authenticate, license does not support RADIUS authentication") - return None return super(RADIUSBackend, self).authenticate(None, username, password) def get_user(self, user_id): if not django_settings.RADIUS_SERVER: return None - if not feature_enabled('enterprise_auth'): - logger.error("Unable to get_user, license does not support RADIUS authentication") - return None user = super(RADIUSBackend, self).get_user(user_id) if not user.has_usable_password(): return user @@ -218,9 +205,6 @@ class TACACSPlusBackend(object): def authenticate(self, username, password): if not django_settings.TACACSPLUS_HOST: return None - if not feature_enabled('enterprise_auth'): - logger.error("Unable to authenticate, license does not support TACACS+ authentication") - return None try: # Upstream TACACS+ client does not accept non-string, so convert if needed. auth = tacacs_plus.TACACSClient( @@ -241,9 +225,6 @@ def authenticate(self, username, password): def get_user(self, user_id): if not django_settings.TACACSPLUS_HOST: return None - if not feature_enabled('enterprise_auth'): - logger.error("Unable to get user, license does not support TACACS+ authentication") - return None try: return User.objects.get(pk=user_id) except User.DoesNotExist: @@ -294,9 +275,6 @@ def authenticate(self, *args, **kwargs): django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT, django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS]): return None - if not feature_enabled('enterprise_auth'): - logger.error("Unable to authenticate, license does not support SAML authentication") - return None user = super(SAMLAuth, self).authenticate(*args, **kwargs) # Comes from https://github.com/omab/python-social-auth/blob/v0.2.21/social/backends/base.py#L91 if getattr(user, 'is_new', False): @@ -311,9 +289,6 @@ def get_user(self, user_id): django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT, django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS]): return None - if not feature_enabled('enterprise_auth'): - logger.error("Unable to get_user, license does not support SAML authentication") - return None return super(SAMLAuth, self).get_user(user_id) diff --git a/awx/sso/conf.py b/awx/sso/conf.py index 14ab784db2ed..4f923a6c6317 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -157,7 +157,6 @@ def _register_ldap(append=None): category=_('LDAP'), category_slug='ldap', placeholder='ldaps://ldap.example.com:636', - feature_required='ldap', ) register( @@ -172,7 +171,6 @@ def _register_ldap(append=None): ' user information. Refer to the Ansible Tower documentation for example syntax.'), category=_('LDAP'), category_slug='ldap', - feature_required='ldap', ) register( @@ -184,7 +182,6 @@ def _register_ldap(append=None): help_text=_('Password used to bind LDAP user account.'), category=_('LDAP'), category_slug='ldap', - feature_required='ldap', encrypted=True, ) @@ -196,7 +193,6 @@ def _register_ldap(append=None): help_text=_('Whether to enable TLS when the LDAP connection is not using SSL.'), category=_('LDAP'), category_slug='ldap', - feature_required='ldap', ) register( @@ -216,7 +212,6 @@ def _register_ldap(append=None): ('OPT_REFERRALS', 0), ('OPT_NETWORK_TIMEOUT', 30) ]), - feature_required='ldap', ) register( @@ -237,7 +232,6 @@ def _register_ldap(append=None): 'SCOPE_SUBTREE', '(sAMAccountName=%(user)s)', ), - feature_required='ldap', ) register( @@ -255,7 +249,6 @@ def _register_ldap(append=None): category=_('LDAP'), category_slug='ldap', placeholder='uid=%(user)s,OU=Users,DC=example,DC=com', - feature_required='ldap', ) register( @@ -274,7 +267,6 @@ def _register_ldap(append=None): ('last_name', 'sn'), ('email', 'mail'), ]), - feature_required='ldap', ) register( @@ -292,7 +284,6 @@ def _register_ldap(append=None): 'SCOPE_SUBTREE', '(objectClass=group)', ), - feature_required='ldap', ) register( @@ -304,7 +295,6 @@ def _register_ldap(append=None): 'https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups'), category=_('LDAP'), category_slug='ldap', - feature_required='ldap', default='MemberDNGroupType', depends_on=['AUTH_LDAP{}_GROUP_TYPE_PARAMS'.format(append_str)], ) @@ -325,7 +315,6 @@ def _register_ldap(append=None): ('member_attr', 'member'), ('name_attr', 'cn'), ]), - feature_required='ldap', depends_on=['AUTH_LDAP{}_GROUP_TYPE'.format(append_str)], ) @@ -343,7 +332,6 @@ def _register_ldap(append=None): category=_('LDAP'), category_slug='ldap', placeholder='CN=Tower Users,OU=Users,DC=example,DC=com', - feature_required='ldap', ) register( @@ -359,7 +347,6 @@ def _register_ldap(append=None): category=_('LDAP'), category_slug='ldap', placeholder='CN=Disabled Users,OU=Users,DC=example,DC=com', - feature_required='ldap', ) register( @@ -376,7 +363,6 @@ def _register_ldap(append=None): ('is_superuser', 'CN=Domain Admins,CN=Users,DC=example,DC=com'), ('is_system_auditor', 'CN=Domain Auditors,CN=Users,DC=example,DC=com'), ]), - feature_required='ldap', ) register( @@ -404,7 +390,6 @@ def _register_ldap(append=None): ('remove_admins', True), ])), ]), - feature_required='ldap', ) register( @@ -428,7 +413,6 @@ def _register_ldap(append=None): ('remove', False), ])), ]), - feature_required='ldap', ) @@ -454,7 +438,6 @@ def _register_ldap(append=None): category=_('RADIUS'), category_slug='radius', placeholder='radius.example.com', - feature_required='enterprise_auth', ) register( @@ -467,7 +450,6 @@ def _register_ldap(append=None): help_text=_('Port of RADIUS server.'), category=_('RADIUS'), category_slug='radius', - feature_required='enterprise_auth', ) register( @@ -479,7 +461,6 @@ def _register_ldap(append=None): help_text=_('Shared secret for authenticating to RADIUS server.'), category=_('RADIUS'), category_slug='radius', - feature_required='enterprise_auth', encrypted=True, ) @@ -496,7 +477,6 @@ def _register_ldap(append=None): help_text=_('Hostname of TACACS+ server.'), category=_('TACACS+'), category_slug='tacacsplus', - feature_required='enterprise_auth', ) register( @@ -509,7 +489,6 @@ def _register_ldap(append=None): help_text=_('Port number of TACACS+ server.'), category=_('TACACS+'), category_slug='tacacsplus', - feature_required='enterprise_auth', ) register( @@ -522,7 +501,6 @@ def _register_ldap(append=None): help_text=_('Shared secret for authenticating to TACACS+ server.'), category=_('TACACS+'), category_slug='tacacsplus', - feature_required='enterprise_auth', encrypted=True, ) @@ -535,7 +513,6 @@ def _register_ldap(append=None): help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'), category=_('TACACS+'), category_slug='tacacsplus', - feature_required='enterprise_auth', ) register( @@ -547,7 +524,6 @@ def _register_ldap(append=None): help_text=_('Choose the authentication protocol used by TACACS+ client.'), category=_('TACACS+'), category_slug='tacacsplus', - feature_required='enterprise_auth', ) ############################################################################### @@ -953,7 +929,6 @@ def get_saml_entity_id(): category=_('SAML'), category_slug='saml', depends_on=['TOWER_URL_BASE'], - feature_required='enterprise_auth', ) register( @@ -966,7 +941,6 @@ def get_saml_entity_id(): 'metadata file, you can download one from this URL.'), category=_('SAML'), category_slug='saml', - feature_required='enterprise_auth', ) register( @@ -980,7 +954,6 @@ def get_saml_entity_id(): 'This is usually the URL for Tower.'), category=_('SAML'), category_slug='saml', - feature_required='enterprise_auth', depends_on=['TOWER_URL_BASE'], ) @@ -995,7 +968,6 @@ def get_saml_entity_id(): 'and include the certificate content here.'), category=_('SAML'), category_slug='saml', - feature_required='enterprise_auth', ) register( @@ -1009,7 +981,6 @@ def get_saml_entity_id(): 'and include the private key content here.'), category=_('SAML'), category_slug='saml', - feature_required='enterprise_auth', encrypted=True, ) @@ -1029,7 +1000,6 @@ def get_saml_entity_id(): ('url', 'http://www.example.com'), ])), ]), - feature_required='enterprise_auth', ) register( @@ -1047,7 +1017,6 @@ def get_saml_entity_id(): ('givenName', 'Technical Contact'), ('emailAddress', 'techsup@example.com'), ]), - feature_required='enterprise_auth', ) register( @@ -1065,7 +1034,6 @@ def get_saml_entity_id(): ('givenName', 'Support Contact'), ('emailAddress', 'support@example.com'), ]), - feature_required='enterprise_auth', ) register( @@ -1102,7 +1070,6 @@ def get_saml_entity_id(): ('attr_email', 'User.email'), ])), ]), - feature_required='enterprise_auth', ) register( @@ -1135,7 +1102,6 @@ def get_saml_entity_id(): ("signatureAlgorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"), ("digestAlgorithm", "http://www.w3.org/2000/09/xmldsig#sha1"), ]), - feature_required='enterprise_auth', ) register( @@ -1149,7 +1115,6 @@ def get_saml_entity_id(): category=_('SAML'), category_slug='saml', placeholder=collections.OrderedDict(), - feature_required='enterprise_auth', ) register( @@ -1167,7 +1132,6 @@ def get_saml_entity_id(): ('department', 'department'), ('manager_full_name', 'manager_full_name') ], - feature_required='enterprise_auth', ) register( @@ -1180,7 +1144,6 @@ def get_saml_entity_id(): category=_('SAML'), category_slug='saml', placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER, - feature_required='enterprise_auth', ) register( @@ -1193,7 +1156,6 @@ def get_saml_entity_id(): category=_('SAML'), category_slug='saml', placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, - feature_required='enterprise_auth', ) register( @@ -1211,7 +1173,6 @@ def get_saml_entity_id(): ('remove', True), ('remove_admins', True), ]), - feature_required='enterprise_auth', ) register( @@ -1253,7 +1214,6 @@ def get_saml_entity_id(): ]), ]), ]), - feature_required='enterprise_auth', ) diff --git a/awx/sso/fields.py b/awx/sso/fields.py index 188ca7ab3082..d59475f84b9a 100644 --- a/awx/sso/fields.py +++ b/awx/sso/fields.py @@ -25,7 +25,6 @@ # Tower from awx.conf import fields -from awx.conf.license import feature_enabled from awx.main.validators import validate_certificate from awx.sso.validators import ( # noqa validate_ldap_dn, @@ -164,13 +163,12 @@ def _default_from_required_settings(self): except AttributeError: backends = self.REQUIRED_BACKEND_SETTINGS.keys() # Filter which authentication backends are enabled based on their - # required settings being defined and non-empty. Also filter available - # backends based on license features. + # required settings being defined and non-empty. for backend, required_settings in self.REQUIRED_BACKEND_SETTINGS.items(): if backend not in backends: continue required_feature = self.REQUIRED_BACKEND_FEATURE.get(backend, '') - if not required_feature or feature_enabled(required_feature): + if not required_feature: if all([getattr(settings, rs, None) for rs in required_settings]): continue backends = [x for x in backends if x != backend] @@ -782,4 +780,3 @@ class SAMLTeamAttrField(BaseDictWithChildField): 'remove': fields.BooleanField(required=False), 'saml_attr': fields.CharField(required=False, allow_null=True), } - diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index d5b3602e80a4..1343f2e75ecb 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -13,9 +13,6 @@ from django.utils.translation import ugettext_lazy as _ from django.db.models import Q -# Tower -from awx.conf.license import feature_enabled - logger = logging.getLogger('awx.sso.pipeline') @@ -83,18 +80,11 @@ def _update_m2m_from_expression(user, rel, expr, remove=True): def _update_org_from_attr(user, rel, attr, remove, remove_admins): from awx.main.models import Organization - multiple_orgs = feature_enabled('multiple_organizations') org_ids = [] for org_name in attr: - if multiple_orgs: - org = Organization.objects.get_or_create(name=org_name)[0] - else: - try: - org = Organization.objects.order_by('pk')[0] - except IndexError: - continue + org = Organization.objects.get_or_create(name=org_name)[0] org_ids.append(org.id) getattr(org, rel).members.add(user) @@ -116,19 +106,10 @@ def update_user_orgs(backend, details, user=None, *args, **kwargs): if not user: return from awx.main.models import Organization - multiple_orgs = feature_enabled('multiple_organizations') org_map = backend.setting('ORGANIZATION_MAP') or {} for org_name, org_opts in org_map.items(): + org = Organization.objects.get_or_create(name=org_name)[0] - # Get or create the org to update. If the license only allows for one - # org, always use the first active org, unless no org exists. - if multiple_orgs: - org = Organization.objects.get_or_create(name=org_name)[0] - else: - try: - org = Organization.objects.order_by('pk')[0] - except IndexError: - continue # Update org admins from expression(s). remove = bool(org_opts.get('remove', True)) @@ -150,21 +131,13 @@ def update_user_teams(backend, details, user=None, *args, **kwargs): if not user: return from awx.main.models import Organization, Team - multiple_orgs = feature_enabled('multiple_organizations') team_map = backend.setting('TEAM_MAP') or {} for team_name, team_opts in team_map.items(): + # Get or create the org to update. + if 'organization' not in team_opts: + continue + org = Organization.objects.get_or_create(name=team_opts['organization'])[0] - # Get or create the org to update. If the license only allows for one - # org, always use the first active org, unless no org exists. - if multiple_orgs: - if 'organization' not in team_opts: - continue - org = Organization.objects.get_or_create(name=team_opts['organization'])[0] - else: - try: - org = Organization.objects.order_by('pk')[0] - except IndexError: - continue # Update team members from expression(s). team = Team.objects.get_or_create(name=team_name, organization=org)[0] @@ -196,7 +169,6 @@ def update_user_teams_by_saml_attr(backend, details, user=None, *args, **kwargs) return from awx.main.models import Organization, Team from django.conf import settings - multiple_orgs = feature_enabled('multiple_organizations') team_map = settings.SOCIAL_AUTH_SAML_TEAM_ATTR if team_map.get('saml_attr') is None: return @@ -210,17 +182,11 @@ def update_user_teams_by_saml_attr(backend, details, user=None, *args, **kwargs) for team_name_map in team_map.get('team_org_map', []): team_name = team_name_map.get('team', '') if team_name in saml_team_names: - if multiple_orgs: - if not team_name_map.get('organization', ''): - # Settings field validation should prevent this. - logger.error("organization name invalid for team {}".format(team_name)) - continue - org = Organization.objects.get_or_create(name=team_name_map['organization'])[0] - else: - try: - org = Organization.objects.order_by('pk')[0] - except IndexError: - continue + if not team_name_map.get('organization', ''): + # Settings field validation should prevent this. + logger.error("organization name invalid for team {}".format(team_name)) + continue + org = Organization.objects.get_or_create(name=team_name_map['organization'])[0] team = Team.objects.get_or_create(name=team_name, organization=org)[0] team_ids.append(team.id) diff --git a/awx/sso/tests/conftest.py b/awx/sso/tests/conftest.py index ffd41aec053f..aa31bb0453a2 100644 --- a/awx/sso/tests/conftest.py +++ b/awx/sso/tests/conftest.py @@ -31,21 +31,3 @@ def existing_tacacsplus_user(): enterprise_auth = UserEnterpriseAuth(user=user, provider='tacacs+') enterprise_auth.save() return user - - -@pytest.fixture -def feature_enabled(): - def func(feature): - def inner(name): - return name == feature - return inner - return func - - -@pytest.fixture -def feature_disabled(): - def func(feature): - def inner(name): - return False - return inner - return func diff --git a/awx/sso/tests/unit/test_tacacsplus.py b/awx/sso/tests/unit/test_tacacsplus.py index e048078571d7..c10cbd317e3c 100644 --- a/awx/sso/tests/unit/test_tacacsplus.py +++ b/awx/sso/tests/unit/test_tacacsplus.py @@ -8,24 +8,11 @@ def test_empty_host_fails_auth(tacacsplus_backend): assert ret_user is None -def test_disabled_enterprise_auth_fails_auth(tacacsplus_backend, feature_disabled): - with mock.patch('awx.sso.backends.django_settings') as settings,\ - mock.patch('awx.sso.backends.logger') as logger,\ - mock.patch('awx.sso.backends.feature_enabled', feature_disabled('enterprise_auth')): - settings.TACACSPLUS_HOST = 'localhost' - ret_user = tacacsplus_backend.authenticate(u"user", u"pass") - assert ret_user is None - logger.error.assert_called_once_with( - "Unable to authenticate, license does not support TACACS+ authentication" - ) - - -def test_client_raises_exception(tacacsplus_backend, feature_enabled): +def test_client_raises_exception(tacacsplus_backend): client = mock.MagicMock() client.authenticate.side_effect=Exception("foo") with mock.patch('awx.sso.backends.django_settings') as settings,\ mock.patch('awx.sso.backends.logger') as logger,\ - mock.patch('awx.sso.backends.feature_enabled', feature_enabled('enterprise_auth')),\ mock.patch('tacacs_plus.TACACSClient', return_value=client): settings.TACACSPLUS_HOST = 'localhost' settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii' @@ -36,13 +23,12 @@ def test_client_raises_exception(tacacsplus_backend, feature_enabled): ) -def test_client_return_invalid_fails_auth(tacacsplus_backend, feature_enabled): +def test_client_return_invalid_fails_auth(tacacsplus_backend): auth = mock.MagicMock() auth.valid = False client = mock.MagicMock() client.authenticate.return_value = auth with mock.patch('awx.sso.backends.django_settings') as settings,\ - mock.patch('awx.sso.backends.feature_enabled', feature_enabled('enterprise_auth')),\ mock.patch('tacacs_plus.TACACSClient', return_value=client): settings.TACACSPLUS_HOST = 'localhost' settings.TACACSPLUS_AUTH_PROTOCOL = 'ascii' @@ -50,7 +36,7 @@ def test_client_return_invalid_fails_auth(tacacsplus_backend, feature_enabled): assert ret_user is None -def test_client_return_valid_passes_auth(tacacsplus_backend, feature_enabled): +def test_client_return_valid_passes_auth(tacacsplus_backend): auth = mock.MagicMock() auth.valid = True client = mock.MagicMock() @@ -58,7 +44,6 @@ def test_client_return_valid_passes_auth(tacacsplus_backend, feature_enabled): user = mock.MagicMock() user.has_usable_password = mock.MagicMock(return_value=False) with mock.patch('awx.sso.backends.django_settings') as settings,\ - mock.patch('awx.sso.backends.feature_enabled', feature_enabled('enterprise_auth')),\ mock.patch('tacacs_plus.TACACSClient', return_value=client),\ mock.patch('awx.sso.backends._get_or_set_enterprise_user', return_value=user): settings.TACACSPLUS_HOST = 'localhost' diff --git a/awx/ui/client/src/activity-stream/activitystream.route.js b/awx/ui/client/src/activity-stream/activitystream.route.js index ffdb424626ec..feb3676c25ff 100644 --- a/awx/ui/client/src/activity-stream/activitystream.route.js +++ b/awx/ui/client/src/activity-stream/activitystream.route.js @@ -61,27 +61,6 @@ export default { return qs.search(path, stateParams); } ], - features: ['FeaturesService', '$state', '$rootScope', - function(FeaturesService, $state, $rootScope) { - var features = FeaturesService.get(); - if (features) { - if (FeaturesService.featureEnabled('activity_streams')) { - return features; - } else { - $state.go('dashboard'); - } - } - $rootScope.featuresConfigured.promise.then(function(features) { - if (features) { - if (FeaturesService.featureEnabled('activity_streams')) { - return features; - } else { - $state.go('dashboard'); - } - } - }); - } - ], subTitle: ['$stateParams', 'Rest', 'ModelToBasePathKey', 'GetBasePath', 'ProcessErrors', function($stateParams, rest, ModelToBasePathKey, getBasePath, diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 0064b358d00b..cfa5ccddd1bf 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -91,7 +91,6 @@ angular 'templates', 'PromptDialog', 'AWDirectives', - 'features', instanceGroups, atFeatures, @@ -166,11 +165,11 @@ angular 'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer', 'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest', 'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService', - 'FeaturesService', '$filter', 'SocketService', 'AppStrings', '$transitions', + '$filter', 'SocketService', 'AppStrings', '$transitions', function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams, CheckLicense, $location, Authorization, LoadBasePaths, Timer, LoadConfig, Store, pendoService, Prompt, Rest, Wait, - ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, + ProcessErrors, $state, GetBasePath, ConfigService, $filter, SocketService, AppStrings, $transitions) { $rootScope.$state = $state; @@ -381,7 +380,6 @@ angular SocketService.init(); pendoService.issuePendoIdentity(); CheckLicense.test(); - FeaturesService.get(); if ($location.$$path === "/home" && $state.current && $state.current.name === "") { $state.go('dashboard'); } else if ($location.$$path === "/portal" && $state.current && $state.current.name === "") { @@ -413,10 +411,6 @@ angular // create a promise that will resolve state $AnsibleConfig is loaded $rootScope.loginConfig = $q.defer(); } - if (!$rootScope.featuresConfigured) { - // create a promise that will resolve when features are loaded - $rootScope.featuresConfigured = $q.defer(); - } $rootScope.licenseMissing = true; //the authorization controller redirects to the home page automatcially if there is no last path defined. in order to override // this, set the last path to /portal for instances where portal is visited for the first time. diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js index 1bb9255f0254..c3f8466b81d8 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.directive.js +++ b/awx/ui/client/src/bread-crumb/bread-crumb.directive.js @@ -1,6 +1,6 @@ export default - ['templateUrl', '$state', 'FeaturesService','$rootScope', 'Store', 'Empty', '$window', 'BreadCrumbService', 'i18n', '$transitions', - function(templateUrl, $state, FeaturesService, $rootScope, Store, Empty, $window, BreadCrumbService, i18n, $transitions) { + ['templateUrl', '$state', '$rootScope', 'Store', 'Empty', '$window', 'BreadCrumbService', 'i18n', '$transitions', + function(templateUrl, $state, $rootScope, Store, Empty, $window, BreadCrumbService, i18n, $transitions) { return { restrict: 'E', templateUrl: templateUrl('bread-crumb/bread-crumb'), @@ -28,40 +28,23 @@ export default streamConfig = (trans.to() && trans.to().data) ? trans.to().data : {}; if(streamConfig && streamConfig.activityStream) { - - // Check to see if activity_streams is an enabled feature. $transition.onSuccess fires - // after the resolve on the state declaration so features should be available at this - // point. We use the get() function call here just in case the features aren't available. - // The get() function will only fire off the server call if the features aren't already - // attached to the $rootScope. - var features = FeaturesService.get(); - if(features){ - scope.loadingLicense = false; - scope.activityStreamActive = (trans.to().name === 'activityStream') ? true : false; - scope.activityStreamTooltip = (trans.to().name === 'activityStream') ? i18n._('Hide Activity Stream') : i18n._('View Activity Stream'); - scope.showActivityStreamButton = (FeaturesService.featureEnabled('activity_streams') || trans.to().name ==='activityStream') ? true : false; - } + scope.loadingLicense = false; + scope.activityStreamActive = (trans.to().name === 'activityStream') ? true : false; + scope.activityStreamTooltip = (trans.to().name === 'activityStream') ? i18n._('Hide Activity Stream') : i18n._('View Activity Stream'); + scope.showActivityStreamButton = true; } else { - scope.showActivityStreamButton = false; - } scope.showRefreshButton = (streamConfig && streamConfig.refreshButton) ? true : false; scope.alwaysShowRefreshButton = (streamConfig && streamConfig.alwaysShowRefreshButton) ? true: false; }); - // scope.$on('featuresLoaded', function(){ - $rootScope.featuresConfigured.promise.then(function(features){ - // var features = FeaturesService.get(); - if(features){ - scope.loadingLicense = false; - scope.activityStreamActive = ($state.current.name === 'activityStream') ? true : false; - scope.activityStreamTooltip = ($state.current.name === 'activityStream') ? 'Hide Activity Stream' : 'View Activity Stream'; - scope.showActivityStreamButton = ((FeaturesService.featureEnabled('activity_streams') && streamConfig && streamConfig.activityStream) || $state.current.name ==='activityStream') ? true : false; - } - }); + scope.loadingLicense = false; + scope.activityStreamActive = ($state.current.name === 'activityStream') ? true : false; + scope.activityStreamTooltip = ($state.current.name === 'activityStream') ? 'Hide Activity Stream' : 'View Activity Stream'; + scope.showActivityStreamButton = ((streamConfig && streamConfig.activityStream) || $state.current.name ==='activityStream') ? true : false; function onResize(){ BreadCrumbService.truncateCrumbs(); diff --git a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap.form.js index 598aa1d3e569..3f483b70a3bd 100644 --- a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap.form.js +++ b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap.form.js @@ -107,7 +107,7 @@ export default ['i18n', function(i18n) { }, save: { ngClick: 'vm.formSave()', - ngDisabled: "!ldap_auth || configuration_ldap_template_form.$invalid || configuration_ldap_template_form.$pending" + ngDisabled: "configuration_ldap_template_form.$invalid || configuration_ldap_template_form.$pending" } } }; diff --git a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap1.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap1.form.js index c8869c6ce4f1..e89ae9c59ab1 100644 --- a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap1.form.js +++ b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap1.form.js @@ -107,7 +107,7 @@ export default ['i18n', function(i18n) { }, save: { ngClick: 'vm.formSave()', - ngDisabled: "!ldap_auth || configuration_ldap1_template_form.$invalid || configuration_ldap1_template_form.$pending" + ngDisabled: "configuration_ldap1_template_form.$invalid || configuration_ldap1_template_form.$pending" } } }; diff --git a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap2.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap2.form.js index a7d4a8769f94..ee1b2a8751f1 100644 --- a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap2.form.js +++ b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-ldap2.form.js @@ -107,7 +107,7 @@ export default ['i18n', function(i18n) { }, save: { ngClick: 'vm.formSave()', - ngDisabled: "!ldap_auth || configuration_ldap2_template_form.$invalid || configuration_ldap2_template_form.$pending" + ngDisabled: "configuration_ldap2_template_form.$invalid || configuration_ldap2_template_form.$pending" } } }; diff --git a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-radius.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-radius.form.js index 87d0a3cd64db..568c3b45caf8 100644 --- a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-radius.form.js +++ b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-radius.form.js @@ -39,7 +39,7 @@ export default ['i18n', function(i18n) { }, save: { ngClick: 'vm.formSave()', - ngDisabled: "!enterprise_auth || configuration_radius_template_form.$invalid || configuration_radius_template_form.$pending" + ngDisabled: "configuration_radius_template_form.$invalid || configuration_radius_template_form.$pending" } } }; diff --git a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-saml.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-saml.form.js index ad103461f9cb..7062b9dc6a56 100644 --- a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-saml.form.js +++ b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-saml.form.js @@ -126,7 +126,7 @@ export default ['i18n', function(i18n) { }, save: { ngClick: 'vm.formSave()', - ngDisabled: "!enterprise_auth || configuration_saml_template_form.$invalid || configuration_saml_template_form.$pending" + ngDisabled: "configuration_saml_template_form.$invalid || configuration_saml_template_form.$pending" } } }; diff --git a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-tacacs.form.js b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-tacacs.form.js index da72bdb6ad35..f1fef0314751 100644 --- a/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-tacacs.form.js +++ b/awx/ui/client/src/configuration/forms/auth-form/sub-forms/auth-tacacs.form.js @@ -52,7 +52,7 @@ export default ['i18n', function(i18n) { }, save: { ngClick: 'vm.formSave()', - ngDisabled: "!enterprise_auth || configuration_tacacs_template_form.$invalid || configuration_tacacs_template_form.$pending" + ngDisabled: "configuration_tacacs_template_form.$invalid || configuration_tacacs_template_form.$pending" } } }; diff --git a/awx/ui/client/src/configuration/forms/settings-form.controller.js b/awx/ui/client/src/configuration/forms/settings-form.controller.js index 7f31d70f916a..011fbed2ae1e 100644 --- a/awx/ui/client/src/configuration/forms/settings-form.controller.js +++ b/awx/ui/client/src/configuration/forms/settings-form.controller.js @@ -81,13 +81,13 @@ export default [ $scope.configDataResolve = configDataResolve; $scope.formDefs = formDefs; - // check if it's auditor, show messageBar + // check if it's auditor, show messageBar $scope.show_auditor_bar = false; if($rootScope.user_is_system_auditor && Store('show_auditor_bar') !== false) { $scope.show_auditor_bar = true; } else { $scope.show_auditor_bar = false; - } + } var populateFromApi = function() { SettingsService.getCurrentValues() @@ -145,19 +145,6 @@ export default [ }); $scope.$broadcast('populated', data); }); - ConfigService.getConfig() - .then(function(data) { - $scope.ldap_auth = data.license_info.features.ldap; - $scope.enterprise_auth = data.license_info.features.enterprise_auth; - }) - .catch(function(data, status) { - ProcessErrors($scope, data, status, null, - { - hdr: i18n._('Error'), - msg: i18n._('There was an error getting config values: ') + status - } - ); - }); }; populateFromApi(); diff --git a/awx/ui/client/src/home/home.route.js b/awx/ui/client/src/home/home.route.js index 6c06e510e175..ef8b88220c0a 100644 --- a/awx/ui/client/src/home/home.route.js +++ b/awx/ui/client/src/home/home.route.js @@ -21,12 +21,10 @@ export default { label: N_("DASHBOARD") }, resolve: { - graphData: ['$q', 'jobStatusGraphData', '$rootScope', - function($q, jobStatusGraphData, $rootScope) { - return $rootScope.featuresConfigured.promise.then(function() { - return $q.all({ - jobStatus: jobStatusGraphData.get("month", "all"), - }); + graphData: ['$q', 'jobStatusGraphData', + function($q, jobStatusGraphData) { + return $q.all({ + jobStatus: jobStatusGraphData.get("month", "all"), }); } ] diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js index ae7e5d365534..ac80686a4fcf 100644 --- a/awx/ui/client/src/license/license.controller.js +++ b/awx/ui/client/src/license/license.controller.js @@ -9,9 +9,9 @@ import {N_} from "../i18n"; export default ['Wait', '$state', '$scope', '$rootScope', 'ProcessErrors', 'CheckLicense', 'moment','$window', - 'ConfigService', 'FeaturesService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', + 'ConfigService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, - $window, ConfigService, FeaturesService, pendoService, insightsEnablementService, i18n, config) { + $window, ConfigService, pendoService, insightsEnablementService, i18n, config) { const calcDaysRemaining = function(seconds) { // calculate the number of days remaining on the license @@ -105,8 +105,6 @@ export default ConfigService.delete(); ConfigService.getConfig(licenseInfo) .then(function(config) { - delete($rootScope.features); - FeaturesService.get(); if ($rootScope.licenseMissing === true) { if ($scope.newLicense.pendo) { diff --git a/awx/ui/client/src/login/loginModal/loginModal.controller.js b/awx/ui/client/src/login/loginModal/loginModal.controller.js index a63963c2bc28..1d92ae0f9471 100644 --- a/awx/ui/client/src/login/loginModal/loginModal.controller.js +++ b/awx/ui/client/src/login/loginModal/loginModal.controller.js @@ -42,10 +42,10 @@ export default ['$log', '$cookies', '$compile', '$rootScope', '$location', 'Authorization', 'Alert', 'Wait', 'Timer', 'Empty', '$scope', 'pendoService', 'ConfigService', - 'CheckLicense', 'FeaturesService', 'SocketService', + 'CheckLicense', 'SocketService', function ($log, $cookies, $compile, $rootScope, $location, Authorization, Alert, Wait, Timer, Empty, - scope, pendoService, ConfigService, CheckLicense, FeaturesService, + scope, pendoService, ConfigService, CheckLicense, SocketService) { var lastPath, lastUser, sessionExpired, loginAgain, preAuthUrl; @@ -97,7 +97,6 @@ export default ['$log', '$cookies', '$compile', '$rootScope', ConfigService.getConfig().then(function(){ CheckLicense.test(); pendoService.issuePendoIdentity(); - FeaturesService.get(); Wait("stop"); if(!Empty(preAuthUrl)){ $location.path(preAuthUrl); diff --git a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js index d5f5a25f959a..0c69e900adbd 100644 --- a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js +++ b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js @@ -48,9 +48,6 @@ let lists = [{ activityStreamTarget: 'organization' }, resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }], OrgUsersDataset: ['OrgUserList', 'QuerySet', '$stateParams', 'GetBasePath', function(list, qs, $stateParams, GetBasePath) { let path = GetBasePath(list.basePath) || list.basePath; @@ -98,9 +95,6 @@ let lists = [{ label: N_("TEAMS") }, resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }], OrgTeamList: ['TeamList', 'GetBasePath', '$stateParams', 'i18n', function(TeamList, GetBasePath, $stateParams, i18n) { let list = _.cloneDeep(TeamList); delete list.actions.add; @@ -144,9 +138,6 @@ let lists = [{ label: N_("INVENTORIES") }, resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }], OrgInventoryList: ['InventoryList', 'GetBasePath', '$stateParams', 'i18n', function(InventoryList, GetBasePath, $stateParams, i18n) { let list = _.cloneDeep(InventoryList); delete list.actions.add; @@ -196,9 +187,6 @@ let lists = [{ label: N_("PROJECTS") }, resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }], OrgProjectList: ['ProjectList', 'GetBasePath', '$stateParams', 'i18n', function(ProjectList, GetBasePath, $stateParams, i18n) { let list = _.cloneDeep(ProjectList); delete list.actions; @@ -258,9 +246,6 @@ let lists = [{ label: N_("ADMINS") }, resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }], OrgAdminsDataset: ['OrgAdminList', 'QuerySet', '$stateParams', 'GetBasePath', function(list, qs, $stateParams, GetBasePath) { let path = GetBasePath(list.basePath) || list.basePath; diff --git a/awx/ui/client/src/organizations/organizations.list.js b/awx/ui/client/src/organizations/organizations.list.js index 0a5da6c056fc..835f951fc043 100644 --- a/awx/ui/client/src/organizations/organizations.list.js +++ b/awx/ui/client/src/organizations/organizations.list.js @@ -32,7 +32,6 @@ export default [function() { mode: 'all', // One of: edit, select, all ngClick: 'addOrganization()', awToolTip: 'Create a new organization', - awFeature: 'multiple_organizations', actionClass: 'at-Button--add', actionId: 'button-add' } diff --git a/awx/ui/client/src/shared/features/features.controller.js b/awx/ui/client/src/shared/features/features.controller.js deleted file mode 100644 index 198ac661d254..000000000000 --- a/awx/ui/client/src/shared/features/features.controller.js +++ /dev/null @@ -1,16 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$rootScope', function ($rootScope) { - - this.isFeatureEnabled = function(feature){ - if(_.isEmpty($rootScope.features)){ - return false; - } else{ - return $rootScope.features[feature] || false; - } - }; -}]; diff --git a/awx/ui/client/src/shared/features/features.directive.js b/awx/ui/client/src/shared/features/features.directive.js deleted file mode 100644 index 341afba0a2df..000000000000 --- a/awx/ui/client/src/shared/features/features.directive.js +++ /dev/null @@ -1,41 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc overview - * @name features - * @scope - * @description enables/disables features based on license - * - * @ngdoc directive - * @name features.directive:awFeature - * @description The aw-feature directive works by taking in a string - * that maps to a license feature, and removes that feature from the - * DOM if it is a feature not supported by the user's license. - * For example, adding `aw-feature="system-tracking"` will enable or disable - * the system tracking button based on the license configuration on the - * /config endpoint. - * - * -*/ -import featureController from './features.controller'; - -export default [ '$rootScope', function($rootScope) { - return { - restrict: 'A', - controller: featureController, - link: function (scope, element, attrs, controller){ - if(attrs.awFeature.length > 0){ - $rootScope.featuresConfigured.promise.then(function() { - if(!controller.isFeatureEnabled(attrs.awFeature)){ - element.remove(); - } - }); - } - } - - }; -}]; diff --git a/awx/ui/client/src/shared/features/features.service.js b/awx/ui/client/src/shared/features/features.service.js deleted file mode 100644 index b4fa6b6b3b94..000000000000 --- a/awx/ui/client/src/shared/features/features.service.js +++ /dev/null @@ -1,35 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$rootScope', 'ConfigService', -function ($rootScope, ConfigService) { - return { - get: function(){ - if (_.isEmpty($rootScope.features)) { - var config = ConfigService.get(); - if(config){ - $rootScope.features = config.license_info.features; - if($rootScope.featuresConfigured){ - $rootScope.featuresConfigured.resolve($rootScope.features); - } - return $rootScope.features; - } - } - else{ - return $rootScope.features; - } - }, - - featureEnabled: function(feature) { - if($rootScope.features && $rootScope.features[feature] && $rootScope.features[feature] === true) { - return true; - } - else { - return false; - } - } - }; -}]; diff --git a/awx/ui/client/src/shared/features/main.js b/awx/ui/client/src/shared/features/main.js deleted file mode 100644 index 62dea24b43ec..000000000000 --- a/awx/ui/client/src/shared/features/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import awFeatureDirective from './features.directive'; -import FeaturesService from './features.service'; - -export default - angular.module('features', []) - .directive('awFeature', awFeatureDirective) - .service('FeaturesService', FeaturesService); diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index fc44a61a4b6c..d7b4a96bedb1 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -718,7 +718,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "'"; html += (field.ngShow) ? this.attr(field, 'ngShow') : ""; html += (field.ngHide) ? this.attr(field, 'ngHide') : ""; - html += (field.awFeature) ? "aw-feature=\"" + field.awFeature + "\" " : ""; html += ">\n"; var definedInFileMessage = i18n._('This setting has been set manually in a settings file and is now disabled.'); @@ -1511,9 +1510,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat if (button.ngClick) { html += this.attr(button, 'ngClick'); } - if (button.awFeature) { - html += this.attr(button, 'awFeature'); - } if (button.ngDisabled) { ngDisabled = (button.ngDisabled===true) ? this.form.name+"_form.$invalid" : button.ngDisabled; if (itm !== 'reset') { @@ -1561,9 +1557,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat if(button.ngShow){ html += this.attr(button, 'ngShow'); } - if (button.awFeature) { - html += this.attr(button, 'awFeature'); - } if(button.awToolTip) { html += " aw-tool-tip='" + button.awToolTip + "' data-placement='" + button.dataPlacement + "' data-tip-watch='" + button.dataTipWatch + "'"; } @@ -1728,9 +1721,6 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat if (button.ngClick) { html += this.attr(button, 'ngClick'); } - if (button.awFeature) { - html += this.attr(button, 'awFeature'); - } if (button.ngDisabled) { ngDisabled = (button.ngDisabled===true) ? `${this.form.name}_form.$invalid || ${this.form.name}_form.$pending`: button.ngDisabled; if (btn !== 'reset') { diff --git a/awx/ui/client/src/shared/generator-helpers.js b/awx/ui/client/src/shared/generator-helpers.js index af7f03500138..b1b627ffd5f9 100644 --- a/awx/ui/client/src/shared/generator-helpers.js +++ b/awx/ui/client/src/shared/generator-helpers.js @@ -718,7 +718,6 @@ angular.module('GeneratorHelpers', [systemStatus.name]) html += (options.ngClick) ? "ng-click=\"$eval(" + options.ngClick + ")\" " : ""; html += (options.ngShow) ? "ng-show=\"" + options.ngShow + "\" " : ""; html += (options.ngHide) ? "ng-hide=\"" + options.ngHide + "\" " : ""; - html += (options.awFeature) ? "aw-feature=\"" + options.awFeature + "\" " : ""; html += '>'; html += ''; html += (options.buttonContent) ? options.buttonContent : ""; diff --git a/awx/ui/client/src/shared/list-generator/list-actions.partial.html b/awx/ui/client/src/shared/list-generator/list-actions.partial.html index 88b2ab5b013f..5e3c93188451 100644 --- a/awx/ui/client/src/shared/list-generator/list-actions.partial.html +++ b/awx/ui/client/src/shared/list-generator/list-actions.partial.html @@ -22,7 +22,6 @@ ng-show="{{options.ngShow}}" ng-click="$eval(options.ngClick)" toolbar="true" - aw-feature="{{options.awFeature}}"> @@ -50,7 +49,6 @@ ng-click="$eval(options.ngClick)" ng-show="{{options.ngShow}}" toolbar="true" - aw-feature="{{options.awFeature}}"> diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js index ae74d22e7fd1..bb3b3942ffe9 100644 --- a/awx/ui/client/src/shared/main.js +++ b/awx/ui/client/src/shared/main.js @@ -28,7 +28,6 @@ import moment from './moment/main'; import config from './config/main'; import PromptDialog from './prompt-dialog'; import directives from './directives'; -import features from './features/main'; import orgAdminLookup from './org-admin-lookup/main'; import limitPanels from './limit-panels/main'; import multiSelectPreview from './multi-select-preview/main'; @@ -60,7 +59,6 @@ angular.module('shared', [ PromptDialog.name, directives.name, filters.name, - features.name, orgAdminLookup.name, limitPanels.name, multiSelectPreview.name, diff --git a/awx/ui/client/src/shared/socket/main.js b/awx/ui/client/src/shared/socket/main.js index 3b38053762bc..0b512aa8c178 100644 --- a/awx/ui/client/src/shared/socket/main.js +++ b/awx/ui/client/src/shared/socket/main.js @@ -4,10 +4,8 @@ * All Rights Reserved *************************************************/ -// import awFeatureDirective from './features.directive'; import socketService from './socket.service'; export default angular.module('socket', []) - // .directive('awFeature', awFeatureDirective) .service('SocketService', socketService); diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index 400f70e99956..290af2b93fb2 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -480,7 +480,6 @@ function(NotificationsList, i18n) { relatedButtons: { view_survey: { ngClick: 'editSurvey()', - awFeature: 'surveys', ngShow: '($state.is(\'templates.addJobTemplate\') || $state.is(\'templates.editJobTemplate\')) && survey_exists && !(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', label: i18n._('View Survey'), class: 'Form-primaryButton' @@ -488,7 +487,6 @@ function(NotificationsList, i18n) { add_survey: { ngClick: 'addSurvey()', ngShow: '($state.is(\'templates.addJobTemplate\') || $state.is(\'templates.editJobTemplate\')) && !survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', - awFeature: 'surveys', awToolTip: '{{surveyTooltip}}', dataPlacement: 'top', label: i18n._('Add Survey'), @@ -496,7 +494,6 @@ function(NotificationsList, i18n) { }, edit_survey: { ngClick: 'editSurvey()', - awFeature: 'surveys', ngShow: '($state.is(\'templates.addJobTemplate\') || $state.is(\'templates.editJobTemplate\')) && survey_exists && (job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', label: i18n._('Edit Survey'), class: 'Form-primaryButton', diff --git a/awx/ui/client/src/templates/workflows.form.js b/awx/ui/client/src/templates/workflows.form.js index c2777d08111e..fd515746a659 100644 --- a/awx/ui/client/src/templates/workflows.form.js +++ b/awx/ui/client/src/templates/workflows.form.js @@ -226,7 +226,6 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) { relatedButtons: { view_survey: { ngClick: 'editSurvey()', - awFeature: 'surveys', ngShow: '($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\')) && survey_exists && !(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)', label: i18n._('View Survey'), class: 'Form-primaryButton' @@ -234,7 +233,6 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) { add_survey: { ngClick: 'addSurvey()', ngShow: '!survey_exists && ($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\'))', - awFeature: 'surveys', awToolTip: '{{surveyTooltip}}', dataPlacement: 'top', label: i18n._('Add Survey'), @@ -242,7 +240,6 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) { }, edit_survey: { ngClick: 'editSurvey()', - awFeature: 'surveys', ngShow: 'survey_exists && ($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\'))', label: i18n._('Edit Survey'), class: 'Form-primaryButton', diff --git a/awx/ui/conf.py b/awx/ui/conf.py index 2f3745c9422f..6ef25b539c70 100644 --- a/awx/ui/conf.py +++ b/awx/ui/conf.py @@ -35,7 +35,6 @@ 'custom HTML or other markup languages are not supported.'), category=_('UI'), category_slug='ui', - feature_required='rebranding', ) register( @@ -50,7 +49,6 @@ placeholder='data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=', category=_('UI'), category_slug='ui', - feature_required='rebranding', ) register( diff --git a/awx/ui/test/spec/features/features.directive-test.js b/awx/ui/test/spec/features/features.directive-test.js deleted file mode 100644 index b931c8a92137..000000000000 --- a/awx/ui/test/spec/features/features.directive-test.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -describe('Directive: Features enabled/disabled', () => { - let $compile, - $scope, - q; - - beforeEach(angular.mock.module('features')); - - beforeEach(angular.mock.inject((_$compile_, _$rootScope_, $q) => { - $compile = _$compile_; - $scope = _$rootScope_; - q = $q; - - $scope.featuresConfigured = q.defer(); - })); - - it('Removes the element if feature is disabled', () => { - let element = $compile("