mirror of
https://github.com/ansible/awx.git
synced 2026-01-20 14:11:24 -03:30
Merge pull request #3581 from beeankha/basic_license_feature
Update Basic License Feature Access Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
This commit is contained in:
commit
3611f3491b
@ -73,7 +73,6 @@ from awx.main.redact import UriCleaner, REPLACE_STR
|
||||
|
||||
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 @@ class UserSerializer(BaseSerializer):
|
||||
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 @@ class UserSerializer(BaseSerializer):
|
||||
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 @@ class BaseOAuth2TokenSerializer(BaseSerializer):
|
||||
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 @@ class JobTemplateSerializer(JobTemplateMixin, UnifiedJobTemplateSerializer, JobO
|
||||
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)
|
||||
|
||||
@ -73,7 +73,7 @@ from awx.api.generics import (
|
||||
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.api.metadata import RoleMetadata, JobTypeMetadata
|
||||
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 @@ class AuthView(APIView):
|
||||
# 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 @@ class TeamProjectsList(SubListAPIView):
|
||||
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 @@ class UserAdminOfOrganizationsList(OrganizationCountsMixin, SubListAPIView):
|
||||
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 @@ class HostActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView):
|
||||
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 @@ class HostFactVersionsList(SystemTrackingEnforcementMixin, ParentMixin, ListAPIV
|
||||
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 @@ class JobTemplateSurveySpec(GenericAPIView):
|
||||
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 @@ class WorkflowJobTemplateCopy(WorkflowsEnforcementMixin, CopyAPIView):
|
||||
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 @@ class WorkflowJobTemplateLaunch(WorkflowsEnforcementMixin, RetrieveAPIView):
|
||||
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 @@ class WorkflowJobRelaunch(WorkflowsEnforcementMixin, GenericAPIView):
|
||||
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 @@ class WorkflowJobTemplateWorkflowNodesList(WorkflowsEnforcementMixin, SubListCre
|
||||
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 @@ class WorkflowJobTemplateObjectRolesList(WorkflowsEnforcementMixin, SubListAPIVi
|
||||
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 @@ class WorkflowJobTemplateActivityStreamList(WorkflowsEnforcementMixin, ActivityS
|
||||
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 @@ class WorkflowJobWorkflowNodesList(WorkflowsEnforcementMixin, SubListAPIView):
|
||||
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 @@ class WorkflowJobCancel(WorkflowsEnforcementMixin, RetrieveAPIView):
|
||||
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
|
||||
|
||||
@ -48,7 +48,6 @@ from awx.api.serializers import (
|
||||
JobTemplateSerializer,
|
||||
)
|
||||
from awx.api.views.mixin import (
|
||||
ActivityStreamEnforcementMixin,
|
||||
RelatedJobsPreventDeleteMixin,
|
||||
ControlledByScmMixin,
|
||||
)
|
||||
@ -149,7 +148,7 @@ class InventoryDetail(RelatedJobsPreventDeleteMixin, ControlledByScmMixin, Retri
|
||||
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
|
||||
|
||||
@ -31,48 +31,11 @@ from awx.main.models.organization import Team
|
||||
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.
|
||||
|
||||
@ -7,13 +7,8 @@ import logging
|
||||
# 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 @@ from awx.api.serializers import (
|
||||
InstanceGroupSerializer,
|
||||
)
|
||||
from awx.api.views.mixin import (
|
||||
ActivityStreamEnforcementMixin,
|
||||
RelatedJobsPreventDeleteMixin,
|
||||
OrganizationCountsMixin,
|
||||
)
|
||||
@ -69,24 +63,6 @@ class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
|
||||
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 @@ class OrganizationObjectRolesList(SubListAPIView):
|
||||
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)
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ from awx.main.utils import (
|
||||
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 @@ class ApiRootView(APIView):
|
||||
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 @@ class ApiV1ConfigView(APIView):
|
||||
# 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())
|
||||
|
||||
@ -78,9 +78,6 @@ register(
|
||||
# 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,
|
||||
|
||||
@ -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', {}))
|
||||
|
||||
@ -68,7 +68,7 @@ class SettingsRegistry(object):
|
||||
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 @@ class SettingsRegistry(object):
|
||||
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 @@ class SettingsRegistry(object):
|
||||
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 @@ class SettingsRegistry(object):
|
||||
# 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 @@ class SettingsRegistry(object):
|
||||
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 @@ class SettingsRegistry(object):
|
||||
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 = (
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -28,7 +28,6 @@ from awx.api.versioning import reverse, get_request_version
|
||||
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 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
||||
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 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
||||
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 = {}
|
||||
|
||||
@ -41,8 +41,6 @@ from awx.main.models import (
|
||||
)
|
||||
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 @@ class BaseAccess(object):
|
||||
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 @@ class BaseAccess(object):
|
||||
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 @@ class OrganizationAccess(NotificationAttachMixin, BaseAccess):
|
||||
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 @@ class JobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
# 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 @@ class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
# 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 @@ class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
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
|
||||
|
||||
|
||||
@ -21,7 +21,6 @@ register(
|
||||
help_text=_('Enable capturing activity for the activity stream.'),
|
||||
category=_('System'),
|
||||
category_slug='system',
|
||||
feature_required='activity_streams',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -31,7 +30,6 @@ register(
|
||||
help_text=_('Enable capturing activity for the activity stream when running inventory sync.'),
|
||||
category=_('System'),
|
||||
category_slug='system',
|
||||
feature_required='activity_streams',
|
||||
)
|
||||
|
||||
register(
|
||||
|
||||
@ -13,7 +13,6 @@ from django.utils.timezone import now
|
||||
|
||||
# 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 @@ class CleanupFacts(object):
|
||||
# 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 @@ class Command(BaseCommand):
|
||||
def string_time_to_timestamp(self, time_string):
|
||||
units = {
|
||||
'y': 'years',
|
||||
'd': 'days',
|
||||
'd': 'days',
|
||||
'w': 'weeks',
|
||||
'm': 'months'
|
||||
}
|
||||
@ -131,8 +130,6 @@ class Command(BaseCommand):
|
||||
@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.')
|
||||
|
||||
@ -1088,7 +1088,7 @@ class Command(BaseCommand):
|
||||
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
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from unittest import mock
|
||||
import pytest
|
||||
|
||||
from awx.api.versioning import reverse
|
||||
@ -8,16 +7,12 @@ from awx.main.access import ActivityStreamAccess
|
||||
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'],
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# Python
|
||||
from unittest import mock
|
||||
import pytest
|
||||
from datetime import timedelta
|
||||
import urllib.parse
|
||||
@ -13,14 +12,6 @@ from awx.main.utils import timestamp_apiformat
|
||||
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):
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from unittest import mock
|
||||
import pytest
|
||||
import json
|
||||
|
||||
@ -8,14 +7,6 @@ from awx.main.utils import timestamp_apiformat
|
||||
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):
|
||||
|
||||
@ -7,7 +7,6 @@ import os
|
||||
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 test_organization_delete_with_active_jobs(delete, admin, organization, organ
|
||||
|
||||
assert resp.data['error'] == u"Resource is being used by running jobs."
|
||||
assert resp_sorted == expect_sorted
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -6,17 +6,10 @@ import json
|
||||
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):
|
||||
|
||||
@ -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.fact import Fact
|
||||
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
|
||||
|
||||
@ -6,12 +6,6 @@ from awx.api.versioning import reverse
|
||||
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():
|
||||
|
||||
@ -13,7 +13,6 @@ from awx.main.access import (
|
||||
SystemJobTemplateAccess,
|
||||
)
|
||||
|
||||
from awx.conf.license import LicenseForbids
|
||||
from awx.main.models import (
|
||||
Credential,
|
||||
CredentialType,
|
||||
@ -21,7 +20,6 @@ from awx.main.models import (
|
||||
Project,
|
||||
Role,
|
||||
Organization,
|
||||
Instance,
|
||||
)
|
||||
|
||||
|
||||
@ -204,31 +202,20 @@ def test_jt_add_scan_job_check(job_template_with_ids, user_unit):
|
||||
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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -31,7 +31,6 @@ from social_core.backends.saml import SAMLAuth as BaseSAMLAuth
|
||||
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 @@ class LDAPBackend(BaseLDAPBackend):
|
||||
|
||||
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 @@ class LDAPBackend(BaseLDAPBackend):
|
||||
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 @@ class TACACSPlusBackend(object):
|
||||
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 @@ class SAMLAuth(BaseSAMLAuth):
|
||||
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 @@ class SAMLAuth(BaseSAMLAuth):
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@ -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 @@ register(
|
||||
category=_('RADIUS'),
|
||||
category_slug='radius',
|
||||
placeholder='radius.example.com',
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -467,7 +450,6 @@ register(
|
||||
help_text=_('Port of RADIUS server.'),
|
||||
category=_('RADIUS'),
|
||||
category_slug='radius',
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -479,7 +461,6 @@ register(
|
||||
help_text=_('Shared secret for authenticating to RADIUS server.'),
|
||||
category=_('RADIUS'),
|
||||
category_slug='radius',
|
||||
feature_required='enterprise_auth',
|
||||
encrypted=True,
|
||||
)
|
||||
|
||||
@ -496,7 +477,6 @@ register(
|
||||
help_text=_('Hostname of TACACS+ server.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -509,7 +489,6 @@ register(
|
||||
help_text=_('Port number of TACACS+ server.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -522,7 +501,6 @@ register(
|
||||
help_text=_('Shared secret for authenticating to TACACS+ server.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
feature_required='enterprise_auth',
|
||||
encrypted=True,
|
||||
)
|
||||
|
||||
@ -535,7 +513,6 @@ register(
|
||||
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 @@ register(
|
||||
help_text=_('Choose the authentication protocol used by TACACS+ client.'),
|
||||
category=_('TACACS+'),
|
||||
category_slug='tacacsplus',
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
@ -953,7 +929,6 @@ register(
|
||||
category=_('SAML'),
|
||||
category_slug='saml',
|
||||
depends_on=['TOWER_URL_BASE'],
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -966,7 +941,6 @@ register(
|
||||
'metadata file, you can download one from this URL.'),
|
||||
category=_('SAML'),
|
||||
category_slug='saml',
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -980,7 +954,6 @@ register(
|
||||
'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 @@ register(
|
||||
'and include the certificate content here.'),
|
||||
category=_('SAML'),
|
||||
category_slug='saml',
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1009,7 +981,6 @@ register(
|
||||
'and include the private key content here.'),
|
||||
category=_('SAML'),
|
||||
category_slug='saml',
|
||||
feature_required='enterprise_auth',
|
||||
encrypted=True,
|
||||
)
|
||||
|
||||
@ -1029,7 +1000,6 @@ register(
|
||||
('url', 'http://www.example.com'),
|
||||
])),
|
||||
]),
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1047,7 +1017,6 @@ register(
|
||||
('givenName', 'Technical Contact'),
|
||||
('emailAddress', 'techsup@example.com'),
|
||||
]),
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1065,7 +1034,6 @@ register(
|
||||
('givenName', 'Support Contact'),
|
||||
('emailAddress', 'support@example.com'),
|
||||
]),
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1102,7 +1070,6 @@ register(
|
||||
('attr_email', 'User.email'),
|
||||
])),
|
||||
]),
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1135,7 +1102,6 @@ register(
|
||||
("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 @@ register(
|
||||
category=_('SAML'),
|
||||
category_slug='saml',
|
||||
placeholder=collections.OrderedDict(),
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1167,7 +1132,6 @@ register(
|
||||
('department', 'department'),
|
||||
('manager_full_name', 'manager_full_name')
|
||||
],
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1180,7 +1144,6 @@ register(
|
||||
category=_('SAML'),
|
||||
category_slug='saml',
|
||||
placeholder=SOCIAL_AUTH_ORGANIZATION_MAP_PLACEHOLDER,
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1193,7 +1156,6 @@ register(
|
||||
category=_('SAML'),
|
||||
category_slug='saml',
|
||||
placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER,
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1211,7 +1173,6 @@ register(
|
||||
('remove', True),
|
||||
('remove_admins', True),
|
||||
]),
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -1253,7 +1214,6 @@ register(
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
feature_required='enterprise_auth',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -25,7 +25,6 @@ from awx.sso.ldap_group_types import PosixUIDGroupType # noqa
|
||||
|
||||
# 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,
|
||||
@ -135,17 +134,6 @@ class AuthenticationBackendsField(fields.StringListField):
|
||||
('django.contrib.auth.backends.ModelBackend', []),
|
||||
])
|
||||
|
||||
REQUIRED_BACKEND_FEATURE = {
|
||||
'awx.sso.backends.LDAPBackend': 'ldap',
|
||||
'awx.sso.backends.LDAPBackend1': 'ldap',
|
||||
'awx.sso.backends.LDAPBackend2': 'ldap',
|
||||
'awx.sso.backends.LDAPBackend3': 'ldap',
|
||||
'awx.sso.backends.LDAPBackend4': 'ldap',
|
||||
'awx.sso.backends.LDAPBackend5': 'ldap',
|
||||
'awx.sso.backends.RADIUSBackend': 'enterprise_auth',
|
||||
'awx.sso.backends.SAMLAuth': 'enterprise_auth',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_all_required_settings(cls):
|
||||
all_required_settings = set(['LICENSE'])
|
||||
@ -164,15 +152,12 @@ class AuthenticationBackendsField(fields.StringListField):
|
||||
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 all([getattr(settings, rs, None) for rs in required_settings]):
|
||||
continue
|
||||
if all([getattr(settings, rs, None) for rs in required_settings]):
|
||||
continue
|
||||
backends = [x for x in backends if x != backend]
|
||||
return backends
|
||||
|
||||
@ -782,4 +767,3 @@ class SAMLTeamAttrField(BaseDictWithChildField):
|
||||
'remove': fields.BooleanField(required=False),
|
||||
'saml_attr': fields.CharField(required=False, allow_null=True),
|
||||
}
|
||||
|
||||
|
||||
@ -13,9 +13,6 @@ from social_core.exceptions import AuthException
|
||||
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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,9 +411,9 @@ 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();
|
||||
if (!$rootScope.basePathsLoaded) {
|
||||
// create a promise that will resolve when base paths are loaded
|
||||
$rootScope.basePathsLoaded = $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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -23,7 +23,7 @@ export default {
|
||||
resolve: {
|
||||
graphData: ['$q', 'jobStatusGraphData', '$rootScope',
|
||||
function($q, jobStatusGraphData, $rootScope) {
|
||||
return $rootScope.featuresConfigured.promise.then(function() {
|
||||
return $rootScope.basePathsLoaded.promise.then(function() {
|
||||
return $q.all({
|
||||
jobStatus: jobStatusGraphData.get("month", "all"),
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'
|
||||
}
|
||||
|
||||
@ -36,11 +36,17 @@ angular.module('ApiLoader', ['Utilities'])
|
||||
data.base = base;
|
||||
$rootScope.defaultUrls = data;
|
||||
Store('api', data);
|
||||
if($rootScope.basePathsLoaded){
|
||||
$rootScope.basePathsLoaded.resolve();
|
||||
}
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
$rootScope.defaultUrls = {
|
||||
status: 'error'
|
||||
};
|
||||
if($rootScope.basePathsLoaded){
|
||||
$rootScope.basePathsLoaded.reject();
|
||||
}
|
||||
ProcessErrors(null, data, status, null, {
|
||||
hdr: 'Error',
|
||||
msg: 'Failed to read ' + base + '. GET status: ' + status
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}];
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
@ -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);
|
||||
@ -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') {
|
||||
|
||||
@ -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 += '<span translate>';
|
||||
html += (options.buttonContent) ? options.buttonContent : "";
|
||||
|
||||
@ -21,8 +21,7 @@
|
||||
ng-disabled="{{options.ngDisabled}}"
|
||||
ng-show="{{options.ngShow}}"
|
||||
ng-click="$eval(options.ngClick)"
|
||||
toolbar="true"
|
||||
aw-feature="{{options.awFeature}}">
|
||||
toolbar="true">
|
||||
<span ng-bind-html="options.buttonContent" translate></span>
|
||||
</button>
|
||||
</span>
|
||||
@ -49,8 +48,7 @@
|
||||
ng-disabled="{{options.ngDisabled}}"
|
||||
ng-click="$eval(options.ngClick)"
|
||||
ng-show="{{options.ngShow}}"
|
||||
toolbar="true"
|
||||
aw-feature="{{options.awFeature}}">
|
||||
toolbar="true">
|
||||
<span ng-if="options.buttonContent" ng-bind-html="options.buttonContent" translate></span>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -35,7 +35,6 @@ register(
|
||||
'custom HTML or other markup languages are not supported.'),
|
||||
category=_('UI'),
|
||||
category_slug='ui',
|
||||
feature_required='rebranding',
|
||||
)
|
||||
|
||||
register(
|
||||
@ -50,7 +49,6 @@ register(
|
||||
placeholder='data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=',
|
||||
category=_('UI'),
|
||||
category_slug='ui',
|
||||
feature_required='rebranding',
|
||||
)
|
||||
|
||||
register(
|
||||
|
||||
@ -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("<div aw-feature='system-tracking'></div")($scope);
|
||||
$scope.$digest();
|
||||
expect(element.html()).toBe('');
|
||||
});
|
||||
});
|
||||
@ -62,7 +62,6 @@ register(
|
||||
category=None,
|
||||
depends_on=None,
|
||||
placeholder=rest_framework.fields.empty,
|
||||
feature_required=rest_framework.fields.empty,
|
||||
encrypted=False,
|
||||
defined_in_file=False,
|
||||
)
|
||||
@ -78,7 +77,6 @@ Here is the details of each argument:
|
||||
| `category` | transformable string, like `_('foobar')` | The human-readable form of `category_slug`, mainly for display. |
|
||||
| `depends_on` | `list` of `str`s | A list of setting names this setting depends on. A setting this setting depends on is another tower configuration setting whose changes may affect the value of this setting. |
|
||||
| `placeholder` | transformable string, like `_('foobar')` | A human-readable string displaying a typical value for the setting, mainly used by UI |
|
||||
| `feature_required` | `str` | Indicator of which feature this setting belongs, a user whose license does not allow a feature cannot access its related settings. |
|
||||
| `encrypted` | `boolean` | Flag determining whether the setting value should be encrypted |
|
||||
| `defined_in_file` | `boolean` | Flag determining whether a value has been manually set in settings file. |
|
||||
|
||||
|
||||
@ -6,6 +6,5 @@ python_files = *.py
|
||||
addopts = --reuse-db --nomigrations --tb=native
|
||||
markers =
|
||||
ac: access control test
|
||||
license_feature: ensure license features are accessible or not depending on license
|
||||
survey: tests related to survey feature
|
||||
inventory_import: tests of code used by inventory import command
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user