118 lines
4.6 KiB
Python
118 lines
4.6 KiB
Python
"""saml sp views"""
|
|
from django.contrib.auth import logout
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.http import Http404, HttpRequest, HttpResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.utils.decorators import method_decorator
|
|
from django.utils.http import urlencode
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.views import View
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from signxml import InvalidSignature
|
|
|
|
from passbook.lib.views import bad_request_message
|
|
from passbook.providers.saml.utils.encoding import deflate_and_base64_encode, nice64
|
|
from passbook.sources.saml.exceptions import (
|
|
MissingSAMLResponse,
|
|
UnsupportedNameIDFormat,
|
|
)
|
|
from passbook.sources.saml.models import SAMLBindingTypes, SAMLSource
|
|
from passbook.sources.saml.processors.metadata import MetadataProcessor
|
|
from passbook.sources.saml.processors.request import RequestProcessor
|
|
from passbook.sources.saml.processors.response import ResponseProcessor
|
|
|
|
|
|
class InitiateView(View):
|
|
"""Get the Form with SAML Request, which sends us to the IDP"""
|
|
|
|
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
|
"""Replies with an XHTML SSO Request."""
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
|
if not source.enabled:
|
|
raise Http404
|
|
relay_state = request.GET.get("next", "")
|
|
request.session["sso_destination"] = relay_state
|
|
auth_n_req = RequestProcessor(source, request).build_auth_n()
|
|
# If the source is configured for Redirect bindings, we can just redirect there
|
|
if source.binding_type == SAMLBindingTypes.Redirect:
|
|
saml_request = deflate_and_base64_encode(auth_n_req)
|
|
url_args = urlencode(
|
|
{"SAMLRequest": saml_request, "RelayState": relay_state}
|
|
)
|
|
return redirect(f"{source.sso_url}?{url_args}")
|
|
# As POST Binding we show a form
|
|
saml_request = nice64(auth_n_req)
|
|
if source.binding_type == SAMLBindingTypes.POST:
|
|
return render(
|
|
request,
|
|
"saml/sp/login.html",
|
|
{
|
|
"request_url": source.sso_url,
|
|
"request": saml_request,
|
|
"relay_state": relay_state,
|
|
"source": source,
|
|
},
|
|
)
|
|
# Or an auto-submit form
|
|
if source.binding_type == SAMLBindingTypes.POST_AUTO:
|
|
return render(
|
|
request,
|
|
"generic/autosubmit_form.html",
|
|
{
|
|
"title": _("Redirecting to %(app)s..." % {"app": source.name}),
|
|
"attrs": {"SAMLRequest": saml_request, "RelayState": relay_state},
|
|
"url": source.sso_url,
|
|
},
|
|
)
|
|
raise Http404
|
|
|
|
|
|
@method_decorator(csrf_exempt, name="dispatch")
|
|
class ACSView(View):
|
|
"""AssertionConsumerService, consume assertion and log user in"""
|
|
|
|
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
|
"""Handles a POSTed SSO Assertion and logs the user in."""
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
|
if not source.enabled:
|
|
raise Http404
|
|
processor = ResponseProcessor(source)
|
|
try:
|
|
processor.parse(request)
|
|
except MissingSAMLResponse as exc:
|
|
return bad_request_message(request, str(exc))
|
|
except InvalidSignature as exc:
|
|
return bad_request_message(request, str(exc))
|
|
|
|
try:
|
|
return processor.prepare_flow(request)
|
|
except UnsupportedNameIDFormat as exc:
|
|
return bad_request_message(request, str(exc))
|
|
|
|
|
|
class SLOView(LoginRequiredMixin, View):
|
|
"""Single-Logout-View"""
|
|
|
|
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
|
"""Replies with an XHTML SSO Request."""
|
|
# TODO: Replace with flows
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
|
if not source.enabled:
|
|
raise Http404
|
|
logout(request)
|
|
return render(
|
|
request,
|
|
"saml/sp/sso_single_logout.html",
|
|
{"idp_logout_url": source.slo_url},
|
|
)
|
|
|
|
|
|
class MetadataView(View):
|
|
"""Return XML Metadata for IDP"""
|
|
|
|
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
|
"""Replies with the XML Metadata SPSSODescriptor."""
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
|
metadata = MetadataProcessor(source, request).build_entity_descriptor()
|
|
return HttpResponse(metadata, content_type="text/xml")
|