diff --git a/awx/api/generics.py b/awx/api/generics.py index b584500383..8fc546f2e5 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -9,6 +9,7 @@ import time # Django from django.conf import settings from django.db import connection +from django.http import QueryDict from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string from django.utils.encoding import smart_text @@ -328,10 +329,11 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView): # Make a copy of the data provided (since it's readonly) in order to # inject additional data. - if hasattr(request.data, 'dict'): - data = request.data.dict() + if hasattr(request.data, 'copy'): + data = request.data.copy() else: - data = request.data + data = QueryDict('') + data.update(request.data) # add the parent key to the post data using the pk from the URL parent_key = getattr(self, 'parent_key', None) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 177dc29ca6..b1709ed7f8 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1701,11 +1701,51 @@ class CredentialSerializerCreate(CredentialSerializer): model = Credential fields = ('*', 'user', 'team', 'organization') + def validate(self, attrs): + owner_fields = set() + for field in ('user', 'team', 'organization'): + if field in attrs: + if attrs[field]: + owner_fields.add(field) + else: + 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): - # Remove the user, team, and organization processed in view - for field in ['user', 'team', 'organization']: - validated_data.pop(field, None) - return super(CredentialSerializer, self).create(validated_data) + user = validated_data.pop('user', None) + team = validated_data.pop('team', None) + credential = super(CredentialSerializer, self).create(validated_data) + if user: + credential.owner_role.members.add(user) + if team: + credential.owner_role.parents.add(team.member_role) + return credential + + +class UserCredentialSerializerCreate(CredentialSerializerCreate): + + class Meta: + model = Credential + fields = ('*', '-team', '-organization') + + +class TeamCredentialSerializerCreate(CredentialSerializerCreate): + + class Meta: + model = Credential + fields = ('*', '-user', '-organization') + + +class OrganizationCredentialSerializerCreate(CredentialSerializerCreate): + + class Meta: + model = Credential + fields = ('*', '-user', '-team') class JobOptionsSerializer(BaseSerializer): @@ -2731,4 +2771,3 @@ class FactSerializer(BaseFactSerializer): res = super(FactSerializer, self).get_related(obj) res['host'] = obj.host.get_absolute_url() return res - diff --git a/awx/api/templates/api/_result_fields_common.md b/awx/api/templates/api/_result_fields_common.md index e2a408d7c6..b45c2dfc1f 100644 --- a/awx/api/templates/api/_result_fields_common.md +++ b/awx/api/templates/api/_result_fields_common.md @@ -1,5 +1,5 @@ {% for fn, fm in serializer_fields.items %}{% spaceless %} -{% if write_only and fm.read_only or not write_only and fm.write_only %} +{% if write_only and fm.read_only or not write_only and fm.write_only or write_only and fn == parent_key %} {% else %} * `{{ fn }}`: {{ fm.help_text|capfirst }} ({{ fm.type }}{% if write_only and fm.required %}, required{% endif %}{% if write_only and fm.read_only %}, read-only{% endif %}{% if write_only and not fm.choices and not fm.required %}, default=`{% if fm.type == "string" or fm.type == "email" %}"{% firstof fm.default "" %}"{% else %}{% if fm.type == "field" and not fm.default %}None{% else %}{{ fm.default }}{% endif %}{% endif %}`{% endif %}){% if fm.choices %}{% for c in fm.choices %} - `{% if c.0 == "" %}""{% else %}{{ c.0 }}{% endif %}`{% if c.1 != c.0 %}: {{ c.1 }}{% endif %}{% if write_only and c.0 == fm.default %} (default){% endif %}{% endfor %}{% endif %}{% endif %} diff --git a/awx/api/views.py b/awx/api/views.py index 7370901b53..9db03cfe55 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1313,55 +1313,15 @@ class UserAccessList(ResourceAccessList): resource_model = User new_in_300 = True + class CredentialList(ListCreateAPIView): model = Credential serializer_class = CredentialSerializerCreate - def post(self, request, *args, **kwargs): - - # Check the validity of POST data, including special fields - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - - for field in [x for x in ['user', 'team', 'organization'] if x in request.data and request.data[x] in ('', None)]: - request.data.pop(field) - kwargs.pop(field, None) - - if not any([x in request.data for x in ['user', 'team', 'organization']]): - return Response({"detail": "Missing 'user', 'team', or 'organization'."}, status=status.HTTP_400_BAD_REQUEST) - - if sum([1 if x in request.data else 0 for x in ['user', 'team', 'organization']]) != 1: - return Response({"detail": "Expecting exactly one of 'user', 'team', or 'organization'."}, status=status.HTTP_400_BAD_REQUEST) - - if 'user' in request.data: - user = User.objects.get(pk=request.data['user']) - can_add_params = {'user': user.id} - if 'team' in request.data: - team = Team.objects.get(pk=request.data['team']) - can_add_params = {'team': team.id} - if 'organization' in request.data: - organization = Organization.objects.get(pk=request.data['organization']) - can_add_params = {'organization': organization.id} - - if not self.request.user.can_access(Credential, 'add', can_add_params): - raise PermissionDenied() - - ret = super(CredentialList, self).post(request, *args, **kwargs) - credential = Credential.objects.get(id=ret.data['id']) - - if 'user' in request.data: - credential.owner_role.members.add(user) - if 'team' in request.data: - credential.owner_role.parents.add(team.member_role) - if 'organization' in request.data: - credential.organization = organization - credential.save() - - return ret - class CredentialOwnerUsersList(SubListAPIView): + model = User serializer_class = UserSerializer parent_model = Credential @@ -1370,6 +1330,7 @@ class CredentialOwnerUsersList(SubListAPIView): class CredentialOwnerTeamsList(SubListAPIView): + model = Team serializer_class = TeamSerializer parent_model = Credential @@ -1386,53 +1347,48 @@ class CredentialOwnerTeamsList(SubListAPIView): return self.model.objects.filter(pk__in=teams) -class UserCredentialsList(CredentialList): +class UserCredentialsList(SubListCreateAPIView): model = Credential - serializer_class = CredentialSerializer + serializer_class = UserCredentialSerializerCreate + parent_model = User + parent_key = 'user' def get_queryset(self): - user = get_object_or_404(User,pk=self.kwargs['pk']) - if not self.request.user.can_access(User, 'read', user): - raise PermissionDenied() + user = self.get_parent_object() + self.check_parent_access(user) visible_creds = Credential.accessible_objects(self.request.user, 'read_role') user_creds = Credential.accessible_objects(user, 'read_role') return user_creds & visible_creds - def post(self, request, *args, **kwargs): - request.data['user'] = self.kwargs['pk'] - # The following post takes care of ensuring the current user can add a cred to this user - return super(UserCredentialsList, self).post(request, args, kwargs) -class TeamCredentialsList(CredentialList): +class TeamCredentialsList(SubListCreateAPIView): model = Credential - serializer_class = CredentialSerializer + serializer_class = TeamCredentialSerializerCreate + parent_model = Team + parent_key = 'team' def get_queryset(self): - team = get_object_or_404(Team, pk=self.kwargs['pk']) - if not self.request.user.can_access(Team, 'read', team): - raise PermissionDenied() + team = self.get_parent_object() + self.check_parent_access(team) visible_creds = Credential.accessible_objects(self.request.user, 'read_role') team_creds = Credential.objects.filter(owner_role__parents=team.member_role) return team_creds & visible_creds - def post(self, request, *args, **kwargs): - request.data['team'] = self.kwargs['pk'] - # The following post takes care of ensuring the current user can add a cred to this user - return super(TeamCredentialsList, self).post(request, args, kwargs) -class OrganizationCredentialList(CredentialList): +class OrganizationCredentialList(SubListCreateAPIView): model = Credential - serializer_class = CredentialSerializer + serializer_class = OrganizationCredentialSerializerCreate + parent_model = Organization + parent_key = 'organization' def get_queryset(self): - organization = Organization.objects.get(pk=self.kwargs['pk']) - if not self.request.user.can_access(Organization, 'read', organization): - raise PermissionDenied() + organization = self.get_parent_object() + self.check_parent_access(organization) user_visible = Credential.accessible_objects(self.request.user, 'read_role').all() org_set = Credential.accessible_objects(organization.admin_role, 'read_role').all() @@ -1442,13 +1398,6 @@ class OrganizationCredentialList(CredentialList): return org_set & user_visible - def post(self, request, *args, **kwargs): - organization = Organization.objects.get(pk=self.kwargs['pk']) - request.data['organization'] = organization.id - # The following post takes care of ensuring the current user can add a cred to this user - return super(OrganizationCredentialList, self).post(request, args, kwargs) - - class CredentialDetail(RetrieveUpdateDestroyAPIView):