Prettier DRF pages when using trusted proxy (#15579)

This is a rather hacky, but fixes the DRF pages when going through a
trusted proxy.

Notably: This is meant to primarily fix the DRF pages on downstream
builds while leaving the upstream to function as-is.

When using a trusted proxy, the DRF login and logout endpoints now
redirect to the Platform login page (which respects ?next) and logout
endpoint respectively.

The CSS and JS is inlined because the trusted proxy might only proxy
to /api/ and not /static/ which is a harder problem to solve.

Signed-off-by: Rick Elrod <rick@elrod.me>
This commit is contained in:
Rick Elrod 2024-10-15 22:50:11 +02:00 committed by GitHub
parent 1acf8cfde6
commit 6dea7bfe17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 4 deletions

View File

@ -14,7 +14,7 @@ from django.core.exceptions import FieldDoesNotExist
from django.db import connection, transaction
from django.db.models.fields.related import OneToOneRel
from django.http import QueryDict
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from django.template.loader import render_to_string
from django.utils.encoding import smart_str
from django.utils.safestring import mark_safe
@ -36,7 +36,7 @@ from awx_plugins.interfaces._temporary_private_licensing_api import detect_serve
# django-ansible-base
from ansible_base.rest_filters.rest_framework.field_lookup_backend import FieldLookupBackend
from ansible_base.lib.utils.models import get_all_field_names
from ansible_base.lib.utils.requests import get_remote_host
from ansible_base.lib.utils.requests import get_remote_host, is_proxied_request
from ansible_base.rbac.models import RoleEvaluation, RoleDefinition
from ansible_base.rbac.permission_registry import permission_registry
from ansible_base.jwt_consumer.common.util import validate_x_trusted_proxy_header
@ -82,6 +82,12 @@ analytics_logger = logging.getLogger('awx.analytics.performance')
class LoggedLoginView(auth_views.LoginView):
def get(self, request, *args, **kwargs):
if is_proxied_request():
next = request.GET.get('next', "")
if next:
next = f"?next={next}"
return redirect(f"/{next}")
# 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
@ -97,6 +103,15 @@ class LoggedLoginView(auth_views.LoginView):
return super(LoggedLoginView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if is_proxied_request():
# Give a message, saying to login via AAP
return Response(
{
'detail': _('Please log in via Platform Authentication.'),
},
status=status.HTTP_401_UNAUTHORIZED,
)
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
ip = get_remote_host(request) # request.META.get('REMOTE_ADDR', None)
if request.user.is_authenticated:
@ -119,6 +134,12 @@ class LoggedLogoutView(auth_views.LogoutView):
success_url_allowed_hosts = set(settings.LOGOUT_ALLOWED_HOSTS.split(",")) if settings.LOGOUT_ALLOWED_HOSTS else set()
def dispatch(self, request, *args, **kwargs):
if is_proxied_request():
# 1) We intentionally don't obey ?next= here, just always redirect to platform login
# 2) Hack to prevent rewrites of Location header
qs = "?__gateway_no_rewrite__=1&next=/"
return redirect(f"/api/gateway/v1/logout/{qs}")
original_user = getattr(request, 'user', None)
ret = super(LoggedLogoutView, self).dispatch(request, *args, **kwargs)
current_user = getattr(request, 'user', None)

View File

@ -320,6 +320,10 @@ TEMPLATES = [
'social_django.context_processors.login_redirect',
],
'builtins': ['awx.main.templatetags.swagger'],
'libraries': {
"ansible_base.lib.templatetags.requests": "ansible_base.lib.templatetags.requests",
"ansible_base.lib.templatetags.util": "ansible_base.lib.templatetags.util",
},
},
'DIRS': [
os.path.join(BASE_DIR, 'templates'),

View File

@ -1,11 +1,19 @@
{% extends 'rest_framework/base.html' %}
{% load i18n static %}
{% load i18n static ansible_base.lib.templatetags.requests ansible_base.lib.templatetags.util %}
{% block title %}{{ name }} &middot; {% trans 'AWX REST API' %}{% endblock %}
{% block bootstrap_theme %}
{% is_proxied_request as proxied %}
<link rel="stylesheet" type="text/css" href="{% static 'rest_framework/css/bootstrap.min.css' %}" />
{% if proxied %}
<style>
{# inline_file from DAB #}
{% inline_file "static/api/api.css" True %}
</style>
{% else %}
<link rel="stylesheet" type="text/css" href="{% static 'api/api.css' %}?v={{tower_version}}" />
{% endif %}
{% endblock %}
{% block style %}
@ -24,7 +32,6 @@
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'api:api_root_view' %}">
<img class="logo" src="{% static 'media/logo-header.svg' %}">
<span>{% trans 'REST API' %}</span>
</a>
<a class="navbar-title" href="{{ request.get_full_path }}">
@ -74,5 +81,14 @@
<a class="toggle-description js-tooltip" href="#" title="Show/Hide Description"><span class="glyphicon glyphicon-question-sign"></span></a>
</div>
{{ block.super }}
{% is_proxied_request as proxied %}
{% if proxied %}
<script>
{# inline_file from DAB #}
{% inline_file "static/api/api.js" True %}
</script>
{% else %}
<script src="{% static 'api/api.js' %}?v={{tower_version}}"></script>
{% endif %}
{% endblock %}