diff --git a/awx/api/views.py b/awx/api/views.py index 4de82197e3..a635263140 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1613,12 +1613,12 @@ class OAuth2UserTokenList(SubListCreateAPIView): relationship = 'main_oauth2accesstoken' parent_key = 'user' swagger_topic = 'Authentication' - - + + class UserAuthorizedTokenList(SubListCreateAPIView): view_name = _("OAuth2 User Authorized Access Tokens") - + model = OAuth2AccessToken serializer_class = UserAuthorizedTokenSerializer parent_model = User @@ -1628,12 +1628,12 @@ class UserAuthorizedTokenList(SubListCreateAPIView): def get_queryset(self): return get_access_token_model().objects.filter(application__isnull=False, user=self.request.user) - + class OrganizationApplicationList(SubListCreateAPIView): view_name = _("Organization OAuth2 Applications") - + model = OAuth2Application serializer_class = OAuth2ApplicationSerializer parent_model = Organization @@ -1643,16 +1643,16 @@ class OrganizationApplicationList(SubListCreateAPIView): class UserPersonalTokenList(SubListCreateAPIView): - + view_name = _("OAuth2 Personal Access Tokens") - + model = OAuth2AccessToken serializer_class = UserPersonalTokenSerializer parent_model = User relationship = 'main_oauth2accesstoken' parent_key = 'user' swagger_topic = 'Authentication' - + def get_queryset(self): return get_access_token_model().objects.filter(application__isnull=True, user=self.request.user) @@ -4084,6 +4084,29 @@ class JobDetail(UnifiedJobDeletionMixin, RetrieveUpdateDestroyAPIView): metadata_class = JobTypeMetadata serializer_class = JobDetailSerializer + # NOTE: When removing the V1 API in 3.4, delete the following four methods, + # and let this class inherit from RetrieveDestroyAPIView instead of + # RetrieveUpdateDestroyAPIView. + @property + def allowed_methods(self): + methods = super(JobDetail, self).allowed_methods + if get_request_version(getattr(self, 'request', None)) > 1: + methods.remove('PUT') + methods.remove('PATCH') + return methods + + def put(self, request, *args, **kwargs): + if get_request_version(self.request) > 1: + return Response({"error": _("PUT not allowed for Job Details in version 2 of the API")}, + status=status.HTTP_405_METHOD_NOT_ALLOWED) + return super(JobDetail, self).put(request, *args, **kwargs) + + def patch(self, request, *args, **kwargs): + if get_request_version(self.request) > 1: + return Response({"error": _("PUT not allowed for Job Details in version 2 of the API")}, + status=status.HTTP_405_METHOD_NOT_ALLOWED) + return super(JobDetail, self).patch(request, *args, **kwargs) + def update(self, request, *args, **kwargs): obj = self.get_object() # Only allow changes (PUT/PATCH) when job status is "new". diff --git a/awx/main/tests/functional/api/test_job.py b/awx/main/tests/functional/api/test_job.py index fce3c6fb5a..8cda6a6cfe 100644 --- a/awx/main/tests/functional/api/test_job.py +++ b/awx/main/tests/functional/api/test_job.py @@ -1,14 +1,16 @@ +# Python import pytest import mock - from dateutil.parser import parse from dateutil.relativedelta import relativedelta +from crum import impersonate +# Django rest framework from rest_framework.exceptions import PermissionDenied +# AWX from awx.api.versioning import reverse from awx.api.views import RelatedJobsPreventDeleteMixin, UnifiedJobDeletionMixin - from awx.main.models import ( JobTemplate, User, @@ -17,8 +19,6 @@ from awx.main.models import ( ProjectUpdate, ) -from crum import impersonate - @pytest.mark.django_db def test_extra_credentials(get, organization_factory, job_template_factory, credential): @@ -167,6 +167,33 @@ def test_block_related_unprocessed_events(mocker, organization, project, delete, view.perform_destroy(organization) +@pytest.mark.django_db +def test_disallowed_http_update_methods(put, patch, post, inventory, project, admin_user): + jt = JobTemplate.objects.create( + name='test_disallowed_methods', inventory=inventory, + project=project + ) + job = jt.create_unified_job() + post( + url=reverse('api:job_detail', kwargs={'pk': job.pk, 'version': 'v2'}), + data={}, + user=admin_user, + expect=405 + ) + put( + url=reverse('api:job_detail', kwargs={'pk': job.pk, 'version': 'v2'}), + data={}, + user=admin_user, + expect=405 + ) + patch( + url=reverse('api:job_detail', kwargs={'pk': job.pk, 'version': 'v2'}), + data={}, + user=admin_user, + expect=405 + ) + + class TestControllerNode(): @pytest.fixture def project_update(self, project): diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d6b75726bc..06f28b477f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -67,6 +67,8 @@ [[#1873](https://github.com/ansible/awx/issues/1873)]. * Switched authentication to Django sessions. * Implemented OAuth2 support for token based authentication [[#21](https://github.com/ansible/awx/issues/21)]. +* Added the ability to forcibly expire sessions through `awx-manage expire_sessions`. +* Disallowed using HTTP PUT/PATCH methods to modify existing jobs in Job Details API endpoint. 3.2.0 =====