providers/oauth: add support for consent stage, cleanup

This commit is contained in:
Jens Langhammer 2020-06-20 23:30:53 +02:00
parent c97b946a00
commit 4d81172a48
6 changed files with 36 additions and 78 deletions

View File

@ -23,7 +23,7 @@ class OAuth2Provider(Provider, AbstractApplication):
def html_setup_urls(self, request: HttpRequest) -> Optional[str]: def html_setup_urls(self, request: HttpRequest) -> Optional[str]:
"""return template and context modal with URLs for authorize, token, openid-config, etc""" """return template and context modal with URLs for authorize, token, openid-config, etc"""
return render_to_string( return render_to_string(
"oauth2_provider/setup_url_modal.html", "providers/oauth/setup_url_modal.html",
{ {
"provider": self, "provider": self,
"authorize_url": request.build_absolute_uri( "authorize_url": request.build_absolute_uri(

View File

@ -1,73 +0,0 @@
{% extends "login/base.html" %}
{% load passbook_utils %}
{% load i18n %}
{% block card_title %}
{% trans 'Authorize Application' %}
{% endblock %}
{% block card %}
<form method="POST" class="pf-c-form">
{% csrf_token %}
{% if not error %}
{% csrf_token %}
{% for field in form %}
{% if field.is_hidden %}
{{ field }}
{% endif %}
{% endfor %}
<div class="pf-c-form__group">
<p class="subtitle">
{% blocktrans with remote=application.name %}
You're about to sign into {{ remote }}.
{% endblocktrans %}
</p>
<p>{% trans "Application requires following permissions" %}</p>
<ul class="pf-c-list">
{% for scope in scopes_descriptions %}
<li>{{ scope }}</li>
{% endfor %}
</ul>
{{ form.errors }}
{{ form.non_field_errors }}
</div>
<div class="pf-c-form__group">
<p>
{% blocktrans with user=user %}
You are logged in as {{ user }}. Not you?
{% endblocktrans %}
<a href="{% url 'passbook_flows:default-invalidation' %}">{% trans 'Logout' %}</a>
</p>
</div>
<div class="pf-c-form__group pf-m-action">
<input type="submit" class="pf-c-button pf-m-primary" name="allow" value="{% trans 'Continue' %}">
<a href="{% back %}" class="pf-c-button pf-m-secondary">{% trans "Cancel" %}</a>
</div>
<div class="pf-c-form__group" style="display: none;" id="loading">
<div class="pf-c-form__horizontal-group">
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</div>
{% else %}
<div class="login-group">
<p class="subtitle">
{% blocktrans with err=error.error %}Error: {{ err }}{% endblocktrans %}
</p>
<p>{{ error.description }}</p>
</div>
{% endif %}
</form>
{% endblock %}
{% block scripts %}
<script>
document.querySelector("form").addEventListener("submit", (e) => {
document.getElementById("loading").removeAttribute("style");
});
</script>
{% endblock %}

View File

@ -1 +0,0 @@
{% extends "base/skeleton.html" %}

View File

@ -0,0 +1,20 @@
{% extends 'login/form_with_user.html' %}
{% load i18n %}
{% block beneath_form %}
<div class="pf-c-form__group">
<p>
{% blocktrans with name=context.application.name %}
You're about to sign into {{ name }}.
{% endblocktrans %}
</p>
<p>{% trans "Application requires following permissions" %}</p>
<ul class="pf-c-list">
{% for scope in context.scope_descriptions %}
<li>{{ scope }}</li>
{% endfor %}
</ul>
{{ hidden_inputs }}
</div>
{% endblock %}

View File

@ -1,9 +1,11 @@
"""passbook OAuth2 Views""" """passbook OAuth2 Views"""
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.views import View from django.views import View
from oauth2_provider.exceptions import OAuthToolkitError from oauth2_provider.exceptions import OAuthToolkitError
from oauth2_provider.scopes import get_scopes_backend
from oauth2_provider.views.base import AuthorizationView from oauth2_provider.views.base import AuthorizationView
from structlog import get_logger from structlog import get_logger
@ -20,6 +22,7 @@ from passbook.flows.stage import StageView
from passbook.flows.views import SESSION_KEY_PLAN from passbook.flows.views import SESSION_KEY_PLAN
from passbook.lib.utils.urls import redirect_with_qs from passbook.lib.utils.urls import redirect_with_qs
from passbook.providers.oauth.models import OAuth2Provider from passbook.providers.oauth.models import OAuth2Provider
from passbook.stages.consent.stage import PLAN_CONTEXT_CONSENT_TEMPLATE
LOGGER = get_logger() LOGGER = get_logger()
@ -32,9 +35,10 @@ PLAN_CONTEXT_CODE_CHALLENGE = "code_challenge"
PLAN_CONTEXT_CODE_CHALLENGE_METHOD = "code_challenge_method" PLAN_CONTEXT_CODE_CHALLENGE_METHOD = "code_challenge_method"
PLAN_CONTEXT_SCOPE = "scope" PLAN_CONTEXT_SCOPE = "scope"
PLAN_CONTEXT_NONCE = "nonce" PLAN_CONTEXT_NONCE = "nonce"
PLAN_CONTEXT_SCOPE_DESCRIPTION = "scope_descriptions"
class AuthorizationFlowInitView(AccessMixin, View): class AuthorizationFlowInitView(AccessMixin, LoginRequiredMixin, View):
"""OAuth2 Flow initializer, checks access to application and starts flow""" """OAuth2 Flow initializer, checks access to application and starts flow"""
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -54,8 +58,11 @@ class AuthorizationFlowInitView(AccessMixin, View):
return redirect("passbook_providers_oauth:oauth2-permission-denied") return redirect("passbook_providers_oauth:oauth2-permission-denied")
# Regardless, we start the planner and return to it # Regardless, we start the planner and return to it
planner = FlowPlanner(provider.authorization_flow) planner = FlowPlanner(provider.authorization_flow)
# planner.use_cache = False
planner.allow_empty_flows = True planner.allow_empty_flows = True
# Save scope descriptions
scopes = request.GET.get(PLAN_CONTEXT_SCOPE)
all_scopes = get_scopes_backend().get_all_scopes()
plan = planner.plan( plan = planner.plan(
self.request, self.request,
{ {
@ -65,10 +72,15 @@ class AuthorizationFlowInitView(AccessMixin, View):
PLAN_CONTEXT_REDIRECT_URI: request.GET.get(PLAN_CONTEXT_REDIRECT_URI), PLAN_CONTEXT_REDIRECT_URI: request.GET.get(PLAN_CONTEXT_REDIRECT_URI),
PLAN_CONTEXT_RESPONSE_TYPE: request.GET.get(PLAN_CONTEXT_RESPONSE_TYPE), PLAN_CONTEXT_RESPONSE_TYPE: request.GET.get(PLAN_CONTEXT_RESPONSE_TYPE),
PLAN_CONTEXT_STATE: request.GET.get(PLAN_CONTEXT_STATE), PLAN_CONTEXT_STATE: request.GET.get(PLAN_CONTEXT_STATE),
PLAN_CONTEXT_SCOPE: request.GET.get(PLAN_CONTEXT_SCOPE), PLAN_CONTEXT_SCOPE: scopes,
PLAN_CONTEXT_NONCE: request.GET.get(PLAN_CONTEXT_NONCE), PLAN_CONTEXT_NONCE: request.GET.get(PLAN_CONTEXT_NONCE),
PLAN_CONTEXT_SCOPE_DESCRIPTION: [
all_scopes[scope] for scope in scopes.split(" ")
],
PLAN_CONTEXT_CONSENT_TEMPLATE: "providers/oauth/consent.html",
}, },
) )
plan.append(in_memory_stage(OAuth2Stage)) plan.append(in_memory_stage(OAuth2Stage))
self.request.session[SESSION_KEY_PLAN] = plan self.request.session[SESSION_KEY_PLAN] = plan
return redirect_with_qs( return redirect_with_qs(