diff --git a/src/backend/demo/forms.py b/src/backend/demo/forms.py new file mode 100644 index 00000000..4f8383ea --- /dev/null +++ b/src/backend/demo/forms.py @@ -0,0 +1,68 @@ +"""Forms for the demo helpers.""" + +from django import forms + + +class TokenExchangeDemoForm(forms.Form): + """Simple form to craft a token exchange request payload.""" + + client_id = forms.CharField( + required=False, + label="client_id (Basic Auth)", + widget=forms.TextInput(attrs={"autocomplete": "off"}), + help_text="Used only to build the Authorization header", + ) + client_secret = forms.CharField( + required=False, + label="client_secret (Basic Auth)", + widget=forms.PasswordInput(render_value=True, attrs={"autocomplete": "off"}), + help_text="Used only to build the Authorization header", + ) + grant_type = forms.CharField( + required=True, + initial="urn:ietf:params:oauth:grant-type:token-exchange", + widget=forms.TextInput(attrs={"size": 60}), + ) + subject_token = forms.CharField( + required=True, + widget=forms.Textarea( + attrs={"rows": 3, "placeholder": "Access token to exchange"} + ), + ) + subject_token_type = forms.CharField( + required=True, + initial="urn:ietf:params:oauth:token-type:access_token", + widget=forms.TextInput(attrs={"size": 60}), + ) + requested_token_type = forms.CharField( + required=False, + widget=forms.TextInput(attrs={"size": 60}), + help_text="Optional: e.g. urn:ietf:params:oauth:token-type:jwt", + ) + audience = forms.CharField( + required=False, + widget=forms.TextInput(attrs={"size": 60}), + help_text="Optional: space separated audience identifiers", + ) + scope = forms.CharField( + required=False, + widget=forms.TextInput(attrs={"size": 60}), + help_text="Optional: space separated scopes", + ) + actor_token = forms.CharField( + required=False, + widget=forms.Textarea(attrs={"rows": 2}), + ) + actor_token_type = forms.CharField( + required=False, + widget=forms.TextInput(attrs={"size": 60}), + ) + resource = forms.CharField( + required=False, + widget=forms.TextInput(attrs={"size": 60}), + ) + expires_in = forms.IntegerField( + required=False, + min_value=1, + help_text="Optional: lifetime in seconds", + ) diff --git a/src/backend/demo/templates/demo/token_exchange_form.html b/src/backend/demo/templates/demo/token_exchange_form.html new file mode 100644 index 00000000..f521307e --- /dev/null +++ b/src/backend/demo/templates/demo/token_exchange_form.html @@ -0,0 +1,175 @@ + + + + + Token exchange demo + + + +

Token exchange demo

+

Use this form to POST to /auth/token/exchange/. Basic Auth is built from the client_id and client_secret fields.

+
+ {% csrf_token %} +
{{ form.client_id.label_tag }}{{ form.client_id }}{{ form.client_id.help_text }}
+
{{ form.client_secret.label_tag }}{{ form.client_secret }}{{ form.client_secret.help_text }}
+
{{ form.grant_type.label_tag }}{{ form.grant_type }}
+
{{ form.subject_token.label_tag }}{{ form.subject_token }}
+
{{ form.subject_token_type.label_tag }}{{ form.subject_token_type }}
+
{{ form.requested_token_type.label_tag }}{{ form.requested_token_type }}{{ form.requested_token_type.help_text }}
+
{{ form.audience.label_tag }}{{ form.audience }}{{ form.audience.help_text }}
+
{{ form.scope.label_tag }}{{ form.scope }}{{ form.scope.help_text }}
+
{{ form.actor_token.label_tag }}{{ form.actor_token }}
+
{{ form.actor_token_type.label_tag }}{{ form.actor_token_type }}
+
{{ form.resource.label_tag }}{{ form.resource }}
+
{{ form.expires_in.label_tag }}{{ form.expires_in }}{{ form.expires_in.help_text }}
+ +
+ +
+
+

+  
+ + + + + + diff --git a/src/backend/demo/urls.py b/src/backend/demo/urls.py new file mode 100644 index 00000000..bfd9c870 --- /dev/null +++ b/src/backend/demo/urls.py @@ -0,0 +1,15 @@ +"""URL configuration for the demo helpers.""" + +from django.urls import path + +from .views import TokenExchangeDemoView + +app_name = "demo" + +urlpatterns = [ + path( + "token-exchange/", + TokenExchangeDemoView.as_view(), + name="token-exchange-demo", + ), +] diff --git a/src/backend/demo/views.py b/src/backend/demo/views.py new file mode 100644 index 00000000..687f73c3 --- /dev/null +++ b/src/backend/demo/views.py @@ -0,0 +1,30 @@ +"""Views for demo application.""" + +from django.urls import reverse_lazy +from django.views.generic import TemplateView + +from .forms import TokenExchangeDemoForm + + +class TokenExchangeDemoView(TemplateView): + """Demo view to help test token exchange flows.""" + + template_name = "demo/token_exchange_form.html" + form_class = TokenExchangeDemoForm + http_method_names = ["get"] + + def get_context_data(self, **kwargs): + """Add form and token exchange URL to the context.""" + context = super().get_context_data(**kwargs) + context["form"] = self.form_class( + initial={ + "client_id": "client_id", + "client_secret": "client_secret", + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token_type": "urn:ietf:params:oauth:token-type:access_token", + "subject_token": self.request.session.get("oidc_access_token"), + "requested_token_type": "urn:ietf:params:oauth:token-type:jwt", + } + ) + context["token_exchange_url"] = reverse_lazy("token-exchange") + return context diff --git a/src/backend/people/urls.py b/src/backend/people/urls.py index 277d2dd6..8e6bc513 100644 --- a/src/backend/people/urls.py +++ b/src/backend/people/urls.py @@ -41,6 +41,12 @@ if settings.DEBUG: + debug_urls.urlpatterns ) + # We double-check here just in case + if settings.ENVIRONMENT != "production": + urlpatterns += [ + path("demo/", include("demo.urls")) + ] + if settings.USE_SWAGGER or settings.DEBUG: urlpatterns += [ path(