From a0433773d8ee109bd97eb01eebc4568998a5c371 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Fri, 25 May 2018 14:13:04 -0400 Subject: [PATCH] don't allow Accept:application/json on /api/login/ see: https://github.com/ansible/tower/issues/1672 --- awx/api/generics.py | 24 +++++++++++++++++++++-- awx/main/tests/functional/test_session.py | 14 +++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/awx/api/generics.py b/awx/api/generics.py index c62f3cc6dd..4f13e8585c 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -24,13 +24,14 @@ from django.contrib.auth import views as auth_views # Django REST Framework from rest_framework.authentication import get_authorization_header -from rest_framework.exceptions import PermissionDenied, AuthenticationFailed, ParseError +from rest_framework.exceptions import PermissionDenied, AuthenticationFailed, ParseError, NotAcceptable from rest_framework import generics from rest_framework.response import Response from rest_framework import status from rest_framework import views from rest_framework.permissions import AllowAny -from rest_framework.renderers import JSONRenderer +from rest_framework.renderers import StaticHTMLRenderer, JSONRenderer +from rest_framework.negotiation import DefaultContentNegotiation # cryptography from cryptography.fernet import InvalidToken @@ -64,6 +65,25 @@ analytics_logger = logging.getLogger('awx.analytics.performance') class LoggedLoginView(auth_views.LoginView): + def get(self, request, *args, **kwargs): + # The django.auth.contrib login form doesn't perform the content + # negotiation we've come to expect from DRF; add in code to catch + # situations where Accept != text/html (or */*) and reply with + # an HTTP 406 + try: + DefaultContentNegotiation().select_renderer( + request, + [StaticHTMLRenderer], + 'html' + ) + except NotAcceptable: + resp = Response(status=status.HTTP_406_NOT_ACCEPTABLE) + resp.accepted_renderer = StaticHTMLRenderer() + resp.accepted_media_type = 'text/plain' + resp.renderer_context = {} + return resp + return super(LoggedLoginView, self).get(request, *args, **kwargs) + def post(self, request, *args, **kwargs): original_user = getattr(request, 'user', None) ret = super(LoggedLoginView, self).post(request, *args, **kwargs) diff --git a/awx/main/tests/functional/test_session.py b/awx/main/tests/functional/test_session.py index 90f33626ea..352581ae1e 100644 --- a/awx/main/tests/functional/test_session.py +++ b/awx/main/tests/functional/test_session.py @@ -24,6 +24,20 @@ class AlwaysPassBackend(object): return '{}.{}'.format(cls.__module__, cls.__name__) +@pytest.mark.django_db +@pytest.mark.parametrize('accept, status', [ + ['*/*', 200], + ['text/html', 200], + ['application/json', 406] +]) +def test_login_json_not_allowed(get, accept, status): + get( + '/api/login/', + HTTP_ACCEPT=accept, + expect=status + ) + + @pytest.mark.skip(reason="Needs Update - CA") @pytest.mark.django_db def test_session_create_delete(admin, post, get):