This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/passbook/sources/saml/views.py
Jens L 4915205678
WIP Use Flows for Sources and Providers (#32)
* core: start migrating to flows for authorisation

* sources/oauth: start type-hinting

* core: create default user

* core: only show user delete button if an unenrollment flow exists

* flows: Correctly check initial policies on flow with context

* policies: add more verbosity to engine

* sources/oauth: migrate to flows

* sources/oauth: fix typing errors

* flows: add more tests

* sources/oauth: start implementing unittests

* sources/ldap: add option to disable user sync, move connection init to model

* sources/ldap: re-add default PropertyMappings

* providers/saml: re-add default PropertyMappings

* admin: fix missing stage count

* stages/identification: fix sources not being shown

* crypto: fix being unable to save with private key

* crypto: re-add default self-signed keypair

* policies: rewrite cache_key to prevent wrong cache

* sources/saml: migrate to flows for auth and enrollment

* stages/consent: add new stage

* admin: fix PropertyMapping widget not rendering properly

* core: provider.authorization_flow is mandatory

* flows: add support for "autosubmit" attribute on form

* flows: add InMemoryStage for dynamic stages

* flows: optionally allow empty flows from FlowPlanner

* providers/saml: update to authorization_flow

* sources/*: fix flow executor URL

* flows: fix pylint error

* flows: wrap responses in JSON object to easily handle redirects

* flow: dont cache plan's context

* providers/oauth: rewrite OAuth2 Provider to use flows

* providers/*: update docstrings of models

* core: fix forms not passing help_text through safe

* flows: fix HttpResponses not being converted to JSON

* providers/oidc: rewrite to use flows

* flows: fix linting
2020-06-07 16:35:08 +02:00

119 lines
4.4 KiB
Python

"""saml sp views"""
from django.contrib.auth import logout
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.views import View
from django.views.decorators.csrf import csrf_exempt
from signxml.util import strip_pem_header
from passbook.lib.views import bad_request_message
from passbook.providers.saml.utils import get_random_id, render_xml
from passbook.providers.saml.utils.encoding import nice64
from passbook.providers.saml.utils.time import get_time_string
from passbook.sources.saml.exceptions import (
MissingSAMLResponse,
UnsupportedNameIDFormat,
)
from passbook.sources.saml.models import SAMLBindingTypes, SAMLSource
from passbook.sources.saml.processors.base import Processor
from passbook.sources.saml.utils import build_full_url, get_issuer
from passbook.sources.saml.xml_render import get_authnrequest_xml
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
sso_destination = request.GET.get("next", None)
request.session["sso_destination"] = sso_destination
parameters = {
"ACS_URL": build_full_url("acs", request, source),
"DESTINATION": source.idp_url,
"AUTHN_REQUEST_ID": get_random_id(),
"ISSUE_INSTANT": get_time_string(),
"ISSUER": get_issuer(request, source),
}
authn_req = get_authnrequest_xml(parameters, signed=False)
_request = nice64(str.encode(authn_req))
if source.binding_type == SAMLBindingTypes.Redirect:
return redirect(source.idp_url + "?" + urlencode({"SAMLRequest": _request}))
if source.binding_type == SAMLBindingTypes.POST:
return render(
request,
"saml/sp/login.html",
{
"request_url": source.idp_url,
"request": _request,
"token": sso_destination,
"source": source,
},
)
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 = Processor(source)
try:
processor.parse(request)
except MissingSAMLResponse 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(View):
"""Single-Logout-View"""
def dispatch(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
logout(request)
return render(
request,
"saml/sp/sso_single_logout.html",
{
"idp_logout_url": source.idp_logout_url,
"autosubmit": source.auto_logout,
},
)
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)
issuer = get_issuer(request, source)
cert_stripped = strip_pem_header(
source.signing_kp.certificate_data.replace("\r", "")
).replace("\n", "")
return render_xml(
request,
"saml/sp/xml/sp_sso_descriptor.xml",
{
"acs_url": build_full_url("acs", request, source),
"issuer": issuer,
"cert_public_key": cert_stripped,
},
)