flows: add SESSION_KEY_APPLICATION_PRE

whenever a user tries to access an application without being authenticated to passbook, we now show notice which application they are going to continue to.
This commit is contained in:
Jens Langhammer 2020-07-12 22:47:46 +02:00
parent a3baa100d4
commit b452e751ea
7 changed files with 44 additions and 4 deletions

View file

@ -29,6 +29,7 @@ LOGGER = get_logger()
# Argument used to redirect user after login # Argument used to redirect user after login
NEXT_ARG_NAME = "next" NEXT_ARG_NAME = "next"
SESSION_KEY_PLAN = "passbook_flows_plan" SESSION_KEY_PLAN = "passbook_flows_plan"
SESSION_KEY_APPLICATION_PRE = "passbook_flows_application_pre"
SESSION_KEY_GET = "passbook_flows_get" SESSION_KEY_GET = "passbook_flows_get"
@ -198,8 +199,14 @@ class FlowExecutorView(View):
def cancel(self): def cancel(self):
"""Cancel current execution and return a redirect""" """Cancel current execution and return a redirect"""
if SESSION_KEY_PLAN in self.request.session: keys_to_delete = [
del self.request.session[SESSION_KEY_PLAN] SESSION_KEY_APPLICATION_PRE,
SESSION_KEY_PLAN,
SESSION_KEY_GET,
]
for key in keys_to_delete:
if key in self.request.session:
del self.request.session[key]
class FlowPermissionDeniedView(PermissionDeniedView): class FlowPermissionDeniedView(PermissionDeniedView):

View file

@ -3,12 +3,14 @@ from typing import Optional
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import AccessMixin from django.contrib.auth.mixins import AccessMixin
from django.contrib.auth.views import redirect_to_login
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from structlog import get_logger from structlog import get_logger
from passbook.core.models import Application, Provider, User from passbook.core.models import Application, Provider, User
from passbook.flows.views import SESSION_KEY_APPLICATION_PRE
from passbook.policies.engine import PolicyEngine from passbook.policies.engine import PolicyEngine
from passbook.policies.types import PolicyResult from passbook.policies.types import PolicyResult
@ -25,6 +27,15 @@ class PolicyAccessMixin(BaseMixin, AccessMixin):
"""Mixin class for usage in Authorization views. """Mixin class for usage in Authorization views.
Provider functions to check application access, etc""" Provider functions to check application access, etc"""
def handle_no_permission(self, application: Optional[Application] = None):
if application:
self.request.session[SESSION_KEY_APPLICATION_PRE] = application
return redirect_to_login(
self.request.get_full_path(),
self.get_login_url(),
self.get_redirect_field_name(),
)
def handle_no_permission_authorized(self) -> HttpResponse: def handle_no_permission_authorized(self) -> HttpResponse:
"""Function called when user has no permissions but is authorized""" """Function called when user has no permissions but is authorized"""
# TODO: Remove this URL and render the view instead # TODO: Remove this URL and render the view instead

View file

@ -49,6 +49,10 @@ class AuthorizationFlowInitView(PolicyAccessMixin, LoginRequiredMixin, View):
application = self.provider_to_application(provider) application = self.provider_to_application(provider)
except Application.DoesNotExist: except Application.DoesNotExist:
return self.handle_no_permission_authorized() return self.handle_no_permission_authorized()
# Check if user is unauthenticated, so we pass the application
# for the identification stage
if not request.user.is_authenticated:
return self.handle_no_permission(application)
# Check permissions # Check permissions
result = self.user_has_access(application) result = self.user_has_access(application)
if not result.passing: if not result.passing:

View file

@ -42,6 +42,10 @@ class AuthorizationFlowInitView(PolicyAccessMixin, LoginRequiredMixin, View):
application = self.provider_to_application(provider) application = self.provider_to_application(provider)
except Application.DoesNotExist: except Application.DoesNotExist:
return self.handle_no_permission_authorized() return self.handle_no_permission_authorized()
# Check if user is unauthenticated, so we pass the application
# for the identification stage
if not request.user.is_authenticated:
return self.handle_no_permission(application)
# Check permissions # Check permissions
result = self.user_has_access(application) result = self.user_has_access(application)
if not result.passing: if not result.passing:

View file

@ -47,7 +47,7 @@ REQUEST_KEY_RELAY_STATE = "RelayState"
SESSION_KEY_AUTH_N_REQUEST = "authn_request" SESSION_KEY_AUTH_N_REQUEST = "authn_request"
class SAMLSSOView(LoginRequiredMixin, PolicyAccessMixin, View): class SAMLSSOView(PolicyAccessMixin, LoginRequiredMixin, View):
""""SAML SSO Base View, which plans a flow and injects our final stage. """"SAML SSO Base View, which plans a flow and injects our final stage.
Calls get/post handler.""" Calls get/post handler."""
@ -62,7 +62,7 @@ class SAMLSSOView(LoginRequiredMixin, PolicyAccessMixin, View):
SAMLProvider, pk=self.application.provider_id SAMLProvider, pk=self.application.provider_id
) )
if not request.user.is_authenticated: if not request.user.is_authenticated:
return self.handle_no_permission() return self.handle_no_permission(self.application)
if not self.user_has_access(self.application).passing: if not self.user_has_access(self.application).passing:
return self.handle_no_permission_authorized() return self.handle_no_permission_authorized()
# Call the method handler, which checks the SAML Request # Call the method handler, which checks the SAML Request

View file

@ -12,6 +12,7 @@ from structlog import get_logger
from passbook.core.models import Source, User from passbook.core.models import Source, User
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
from passbook.flows.stage import StageView from passbook.flows.stage import StageView
from passbook.flows.views import SESSION_KEY_APPLICATION_PRE
from passbook.stages.identification.forms import IdentificationForm from passbook.stages.identification.forms import IdentificationForm
from passbook.stages.identification.models import IdentificationStage from passbook.stages.identification.models import IdentificationStage
@ -34,6 +35,12 @@ class IdentificationStageView(FormView, StageView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
current_stage: IdentificationStage = self.executor.current_stage current_stage: IdentificationStage = self.executor.current_stage
# If the user has been redirected to us whilst trying to access an
# application, SESSION_KEY_APPLICATION_PRE is set in the session
if SESSION_KEY_APPLICATION_PRE in self.request.session:
kwargs["application_pre"] = self.request.session[
SESSION_KEY_APPLICATION_PRE
]
# Check for related enrollment and recovery flow, add URL to view # Check for related enrollment and recovery flow, add URL to view
if current_stage.enrollment_flow: if current_stage.enrollment_flow:
kwargs["enroll_url"] = reverse( kwargs["enroll_url"] = reverse(

View file

@ -11,6 +11,13 @@
<div class="pf-c-login__main-body"> <div class="pf-c-login__main-body">
<form method="POST" class="pf-c-form"> <form method="POST" class="pf-c-form">
{% block above_form %} {% block above_form %}
{% if application_pre %}
<p>
{% blocktrans with app_name=application_pre.name %}
Login to continue to <strong>{{ app_name }}</strong>.
{% endblocktrans %}
</p>
{% endif %}
{% endblock %} {% endblock %}
{% include 'partials/form.html' %} {% include 'partials/form.html' %}