mirror of
https://github.com/ansible/awx.git
synced 2026-03-17 17:07:33 -02:30
Merge branch 'release_3.0.2' into devel
* release_3.0.2: (126 commits) Disable permissions tab in Credential > Edit form if Credential is private (#3288) Tweaked the popover text for job and skip tags on JT add/edit Workaround a cascade setnull polymorphic issue flake8 Fixed old test expectations Made it so the credential organization field can't be changed Skip some unit tests Fixed org auditor visibility of team credentials Fix sosreport fix credential kind options for list interpret any code below 300 as success bail when status code is over 300 Make CloudForms inventory_script work Use no_log when handling passwords Prevent ignored task from being displayed as failing. making ec2 cred optional on group->edit making ec2 credential optional for ec2 inventory Revert "Fix to ensure org auditors can see team credentials" Fixed team credential list to work with corrected permissions Making the username and password fields optional ...
This commit is contained in:
@@ -526,8 +526,10 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
|
||||
serializer_class = InventorySourceSerializer
|
||||
elif isinstance(obj, JobTemplate):
|
||||
serializer_class = JobTemplateSerializer
|
||||
elif isinstance(obj, SystemJobTemplate):
|
||||
serializer_class = SystemJobTemplateSerializer
|
||||
if serializer_class:
|
||||
serializer = serializer_class(instance=obj)
|
||||
serializer = serializer_class(instance=obj, context=self.context)
|
||||
return serializer.to_representation(obj)
|
||||
else:
|
||||
return super(UnifiedJobTemplateSerializer, self).to_representation(obj)
|
||||
@@ -590,7 +592,7 @@ class UnifiedJobSerializer(BaseSerializer):
|
||||
elif isinstance(obj, SystemJob):
|
||||
serializer_class = SystemJobSerializer
|
||||
if serializer_class:
|
||||
serializer = serializer_class(instance=obj)
|
||||
serializer = serializer_class(instance=obj, context=self.context)
|
||||
ret = serializer.to_representation(obj)
|
||||
else:
|
||||
ret = super(UnifiedJobSerializer, self).to_representation(obj)
|
||||
@@ -637,7 +639,7 @@ class UnifiedJobListSerializer(UnifiedJobSerializer):
|
||||
elif isinstance(obj, SystemJob):
|
||||
serializer_class = SystemJobListSerializer
|
||||
if serializer_class:
|
||||
serializer = serializer_class(instance=obj)
|
||||
serializer = serializer_class(instance=obj, context=self.context)
|
||||
ret = serializer.to_representation(obj)
|
||||
else:
|
||||
ret = super(UnifiedJobListSerializer, self).to_representation(obj)
|
||||
@@ -1285,7 +1287,8 @@ class CustomInventoryScriptSerializer(BaseSerializer):
|
||||
request = self.context.get('request', None)
|
||||
if request.user not in obj.admin_role and \
|
||||
not request.user.is_superuser and \
|
||||
not request.user.is_system_auditor:
|
||||
not request.user.is_system_auditor and \
|
||||
not (obj.organization is not None and request.user in obj.organization.auditor_role):
|
||||
ret['script'] = None
|
||||
return ret
|
||||
|
||||
@@ -1713,19 +1716,21 @@ class CredentialSerializerCreate(CredentialSerializer):
|
||||
attrs.pop(field)
|
||||
if not owner_fields:
|
||||
raise serializers.ValidationError({"detail": "Missing 'user', 'team', or 'organization'."})
|
||||
elif len(owner_fields) > 1:
|
||||
raise serializers.ValidationError({"detail": "Expecting exactly one of 'user', 'team', or 'organization'."})
|
||||
|
||||
return super(CredentialSerializerCreate, self).validate(attrs)
|
||||
|
||||
def create(self, validated_data):
|
||||
user = validated_data.pop('user', None)
|
||||
team = validated_data.pop('team', None)
|
||||
if team:
|
||||
validated_data['organization'] = team.organization
|
||||
credential = super(CredentialSerializerCreate, self).create(validated_data)
|
||||
if user:
|
||||
credential.admin_role.members.add(user)
|
||||
if team:
|
||||
credential.admin_role.parents.add(team.member_role)
|
||||
if not credential.organization or team.organization.id != credential.organization.id:
|
||||
raise serializers.ValidationError({"detail": "Credential organization must be set and match before assigning to a team"})
|
||||
credential.admin_role.parents.add(team.admin_role)
|
||||
credential.use_role.parents.add(team.member_role)
|
||||
return credential
|
||||
|
||||
|
||||
@@ -1823,7 +1828,7 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
|
||||
class Meta:
|
||||
model = JobTemplate
|
||||
fields = ('*', 'host_config_key', 'ask_variables_on_launch', 'ask_limit_on_launch',
|
||||
'ask_tags_on_launch', 'ask_job_type_on_launch', 'ask_inventory_on_launch',
|
||||
'ask_tags_on_launch', 'ask_skip_tags_on_launch', 'ask_job_type_on_launch', 'ask_inventory_on_launch',
|
||||
'ask_credential_on_launch', 'survey_enabled', 'become_enabled', 'allow_simultaneous')
|
||||
|
||||
def get_related(self, obj):
|
||||
@@ -1907,6 +1912,7 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
||||
passwords_needed_to_start = serializers.ReadOnlyField()
|
||||
ask_variables_on_launch = serializers.ReadOnlyField()
|
||||
ask_limit_on_launch = serializers.ReadOnlyField()
|
||||
ask_skip_tags_on_launch = serializers.ReadOnlyField()
|
||||
ask_tags_on_launch = serializers.ReadOnlyField()
|
||||
ask_job_type_on_launch = serializers.ReadOnlyField()
|
||||
ask_inventory_on_launch = serializers.ReadOnlyField()
|
||||
@@ -1915,8 +1921,8 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
||||
class Meta:
|
||||
model = Job
|
||||
fields = ('*', 'job_template', 'passwords_needed_to_start', 'ask_variables_on_launch',
|
||||
'ask_limit_on_launch', 'ask_tags_on_launch', 'ask_job_type_on_launch',
|
||||
'ask_inventory_on_launch', 'ask_credential_on_launch')
|
||||
'ask_limit_on_launch', 'ask_tags_on_launch', 'ask_skip_tags_on_launch',
|
||||
'ask_job_type_on_launch', 'ask_inventory_on_launch', 'ask_credential_on_launch')
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(JobSerializer, self).get_related(obj)
|
||||
@@ -1977,7 +1983,7 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
||||
return ret
|
||||
if 'job_template' in ret and not obj.job_template:
|
||||
ret['job_template'] = None
|
||||
if obj.job_template and obj.job_template.survey_enabled and 'extra_vars' in ret:
|
||||
if 'extra_vars' in ret:
|
||||
ret['extra_vars'] = obj.display_extra_vars()
|
||||
return ret
|
||||
|
||||
@@ -2281,14 +2287,15 @@ class JobLaunchSerializer(BaseSerializer):
|
||||
fields = ('can_start_without_user_input', 'passwords_needed_to_start',
|
||||
'extra_vars', 'limit', 'job_tags', 'skip_tags', 'job_type', 'inventory',
|
||||
'credential', 'ask_variables_on_launch', 'ask_tags_on_launch',
|
||||
'ask_job_type_on_launch', 'ask_limit_on_launch',
|
||||
'ask_skip_tags_on_launch', 'ask_job_type_on_launch', 'ask_limit_on_launch',
|
||||
'ask_inventory_on_launch', 'ask_credential_on_launch',
|
||||
'survey_enabled', 'variables_needed_to_start',
|
||||
'credential_needed_to_start', 'inventory_needed_to_start',
|
||||
'job_template_data', 'defaults')
|
||||
read_only_fields = ('ask_variables_on_launch', 'ask_limit_on_launch',
|
||||
'ask_tags_on_launch', 'ask_job_type_on_launch',
|
||||
'ask_inventory_on_launch', 'ask_credential_on_launch')
|
||||
read_only_fields = (
|
||||
'ask_variables_on_launch', 'ask_limit_on_launch', 'ask_tags_on_launch',
|
||||
'ask_skip_tags_on_launch', 'ask_job_type_on_launch',
|
||||
'ask_inventory_on_launch', 'ask_credential_on_launch')
|
||||
extra_kwargs = {
|
||||
'credential': {'write_only': True,},
|
||||
'limit': {'write_only': True,},
|
||||
@@ -2675,6 +2682,8 @@ class ActivityStreamSerializer(BaseSerializer):
|
||||
fval = getattr(thisItem, field, None)
|
||||
if fval is not None:
|
||||
thisItemDict[field] = fval
|
||||
if fk == 'group':
|
||||
thisItemDict['inventory_id'] = getattr(thisItem, 'inventory_id', None)
|
||||
summary_fields[fk].append(thisItemDict)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -8,6 +8,8 @@ The response will include the following fields:
|
||||
configured to prompt for variables upon launch (boolean, read-only)
|
||||
* `ask_tags_on_launch`: Flag indicating whether the job_template is
|
||||
configured to prompt for tags upon launch (boolean, read-only)
|
||||
* `ask_skip_tags_on_launch`: Flag indicating whether the job_template is
|
||||
configured to prompt for skip_tags upon launch (boolean, read-only)
|
||||
* `ask_job_type_on_launch`: Flag indicating whether the job_template is
|
||||
configured to prompt for job_type upon launch (boolean, read-only)
|
||||
* `ask_limit_on_launch`: Flag indicating whether the job_template is
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# Copyright (c) 2015 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
@@ -201,7 +200,7 @@ class ApiV1ConfigView(APIView):
|
||||
'''Return various sitewide configuration settings.'''
|
||||
|
||||
license_reader = TaskSerializer()
|
||||
license_data = license_reader.from_database(show_key=request.user.is_superuser)
|
||||
license_data = license_reader.from_database(show_key=request.user.is_superuser or request.user.is_system_auditor)
|
||||
if license_data and 'features' in license_data and 'activity_streams' in license_data['features']:
|
||||
license_data['features']['activity_streams'] &= tower_settings.ACTIVITY_STREAM_ENABLED
|
||||
|
||||
@@ -225,7 +224,10 @@ class ApiV1ConfigView(APIView):
|
||||
user_ldap_fields.extend(getattr(settings, 'AUTH_LDAP_USER_FLAGS_BY_GROUP', {}).keys())
|
||||
data['user_ldap_fields'] = user_ldap_fields
|
||||
|
||||
if request.user.is_superuser or Organization.accessible_objects(request.user, 'admin_role').exists():
|
||||
if request.user.is_superuser \
|
||||
or request.user.is_system_auditor \
|
||||
or Organization.accessible_objects(request.user, 'admin_role').exists() \
|
||||
or Organization.accessible_objects(request.user, 'auditor_role').exists():
|
||||
data.update(dict(
|
||||
project_base_dir = settings.PROJECTS_ROOT,
|
||||
project_local_paths = Project.get_local_path_choices(),
|
||||
@@ -880,12 +882,19 @@ class TeamRolesList(SubListCreateAttachDetachAPIView):
|
||||
data = dict(msg="Role 'id' field is missing.")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
role = Role.objects.get(pk=sub_id)
|
||||
content_type = ContentType.objects.get_for_model(Organization)
|
||||
if role.content_type == content_type:
|
||||
role = get_object_or_400(Role, pk=sub_id)
|
||||
org_content_type = ContentType.objects.get_for_model(Organization)
|
||||
if role.content_type == org_content_type:
|
||||
data = dict(msg="You cannot assign an Organization role as a child role for a Team.")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
team = get_object_or_404(Team, pk=self.kwargs['pk'])
|
||||
credential_content_type = ContentType.objects.get_for_model(Credential)
|
||||
if role.content_type == credential_content_type:
|
||||
if not role.content_object.organization or role.content_object.organization.id != team.organization.id:
|
||||
data = dict(msg="You cannot grant credential access to a team when the Organization field isn't set, or belongs to a different organization")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return super(TeamRolesList, self).post(request, *args, **kwargs)
|
||||
|
||||
class TeamObjectRolesList(SubListAPIView):
|
||||
@@ -1209,7 +1218,24 @@ class UserRolesList(SubListCreateAttachDetachAPIView):
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if sub_id == self.request.user.admin_role.pk:
|
||||
raise PermissionDenied('You may not remove your own admin_role.')
|
||||
raise PermissionDenied('You may not perform any action with your own admin_role.')
|
||||
|
||||
user = get_object_or_400(User, pk=self.kwargs['pk'])
|
||||
role = get_object_or_400(Role, pk=sub_id)
|
||||
user_content_type = ContentType.objects.get_for_model(User)
|
||||
if role.content_type == user_content_type:
|
||||
raise PermissionDenied('You may not change the membership of a users admin_role')
|
||||
|
||||
credential_content_type = ContentType.objects.get_for_model(Credential)
|
||||
if role.content_type == credential_content_type:
|
||||
if role.content_object.organization and user not in role.content_object.organization.member_role:
|
||||
data = dict(msg="You cannot grant credential access to a user not in the credentials' organization")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not role.content_object.organization and not request.user.is_superuser:
|
||||
data = dict(msg="You cannot grant private credential access to another user")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
return super(UserRolesList, self).post(request, *args, **kwargs)
|
||||
|
||||
@@ -1388,8 +1414,8 @@ class TeamCredentialsList(SubListCreateAPIView):
|
||||
self.check_parent_access(team)
|
||||
|
||||
visible_creds = Credential.accessible_objects(self.request.user, 'read_role')
|
||||
team_creds = Credential.objects.filter(admin_role__parents=team.member_role)
|
||||
return team_creds & visible_creds
|
||||
team_creds = Credential.objects.filter(Q(use_role__parents=team.member_role) | Q(admin_role__parents=team.member_role))
|
||||
return (team_creds & visible_creds).distinct()
|
||||
|
||||
|
||||
class OrganizationCredentialList(SubListCreateAPIView):
|
||||
@@ -2975,7 +3001,17 @@ class JobJobTasksList(BaseJobEventsList):
|
||||
# and these are what we're interested in here.
|
||||
STARTING_EVENTS = ('playbook_on_task_start', 'playbook_on_setup')
|
||||
|
||||
queryset = JobEvent.start_event_queryset(parent_task, STARTING_EVENTS)
|
||||
# We need to pull information about each start event.
|
||||
#
|
||||
# This is super tricky, because this table has a one-to-many
|
||||
# relationship with itself (parent-child), and we're getting
|
||||
# information for an arbitrary number of children. This means we
|
||||
# need stats on grandchildren, sorted by child.
|
||||
queryset = (JobEvent.objects.filter(parent__parent=parent_task,
|
||||
parent__event__in=STARTING_EVENTS)
|
||||
.values('parent__id', 'event', 'changed', 'failed')
|
||||
.annotate(num=Count('event'))
|
||||
.order_by('parent__id'))
|
||||
|
||||
# The data above will come back in a list, but we are going to
|
||||
# want to access it based on the parent id, so map it into a
|
||||
@@ -3034,10 +3070,13 @@ class JobJobTasksList(BaseJobEventsList):
|
||||
# make appropriate changes to the task data.
|
||||
for child_data in data.get(task_start_event.id, []):
|
||||
if child_data['event'] == 'runner_on_failed':
|
||||
task_data['failed'] = True
|
||||
task_data['host_count'] += child_data['num']
|
||||
task_data['reported_hosts'] += child_data['num']
|
||||
task_data['failed_count'] += child_data['num']
|
||||
if child_data['failed']:
|
||||
task_data['failed'] = True
|
||||
task_data['failed_count'] += child_data['num']
|
||||
else:
|
||||
task_data['skipped_count'] += child_data['num']
|
||||
elif child_data['event'] == 'runner_on_ok':
|
||||
task_data['host_count'] += child_data['num']
|
||||
task_data['reported_hosts'] += child_data['num']
|
||||
@@ -3625,7 +3664,6 @@ class RoleDetail(RetrieveAPIView):
|
||||
|
||||
model = Role
|
||||
serializer_class = RoleSerializer
|
||||
permission_classes = (IsAuthenticated,)
|
||||
new_in_300 = True
|
||||
|
||||
|
||||
@@ -3648,6 +3686,26 @@ class RoleUsersList(SubListCreateAttachDetachAPIView):
|
||||
if not sub_id:
|
||||
data = dict(msg="User 'id' field is missing.")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
user = get_object_or_400(User, pk=sub_id)
|
||||
role = self.get_parent_object()
|
||||
if role == self.request.user.admin_role:
|
||||
raise PermissionDenied('You may not perform any action with your own admin_role.')
|
||||
|
||||
user_content_type = ContentType.objects.get_for_model(User)
|
||||
if role.content_type == user_content_type:
|
||||
raise PermissionDenied('You may not change the membership of a users admin_role')
|
||||
|
||||
credential_content_type = ContentType.objects.get_for_model(Credential)
|
||||
if role.content_type == credential_content_type:
|
||||
if role.content_object.organization and user not in role.content_object.organization.member_role:
|
||||
data = dict(msg="You cannot grant credential access to a user not in the credentials' organization")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not role.content_object.organization and not request.user.is_superuser:
|
||||
data = dict(msg="You cannot grant private credential access to another user")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return super(RoleUsersList, self).post(request, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -3672,13 +3730,20 @@ class RoleTeamsList(SubListAPIView):
|
||||
data = dict(msg="Team 'id' field is missing.")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
team = get_object_or_400(Team, pk=sub_id)
|
||||
role = Role.objects.get(pk=self.kwargs['pk'])
|
||||
content_type = ContentType.objects.get_for_model(Organization)
|
||||
if role.content_type == content_type:
|
||||
|
||||
organization_content_type = ContentType.objects.get_for_model(Organization)
|
||||
if role.content_type == organization_content_type:
|
||||
data = dict(msg="You cannot assign an Organization role as a child role for a Team.")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
team = Team.objects.get(pk=sub_id)
|
||||
credential_content_type = ContentType.objects.get_for_model(Credential)
|
||||
if role.content_type == credential_content_type:
|
||||
if not role.content_object.organization or role.content_object.organization.id != team.organization.id:
|
||||
data = dict(msg="You cannot grant credential access to a team when the Organization field isn't set, or belongs to a different organization")
|
||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
action = 'attach'
|
||||
if request.data.get('disassociate', None):
|
||||
action = 'unattach'
|
||||
|
||||
Reference in New Issue
Block a user