2019-11-07 16:02:56 +00:00
|
|
|
"""saml sp views"""
|
2020-06-07 14:35:08 +00:00
|
|
|
from django.contrib.auth import logout
|
2019-11-07 17:02:59 +00:00
|
|
|
from django.http import Http404, HttpRequest, HttpResponse
|
2020-06-07 14:35:08 +00:00
|
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
2019-11-07 16:02:56 +00:00
|
|
|
from django.utils.decorators import method_decorator
|
2020-06-07 14:35:08 +00:00
|
|
|
from django.utils.http import urlencode
|
2019-11-07 16:02:56 +00:00
|
|
|
from django.views import View
|
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
2020-02-20 16:05:11 +00:00
|
|
|
from signxml.util import strip_pem_header
|
2019-11-07 16:02:56 +00:00
|
|
|
|
2020-02-20 20:33:45 +00:00
|
|
|
from passbook.lib.views import bad_request_message
|
2020-02-14 14:19:48 +00:00
|
|
|
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
|
2020-02-20 20:33:45 +00:00
|
|
|
from passbook.sources.saml.exceptions import (
|
|
|
|
MissingSAMLResponse,
|
|
|
|
UnsupportedNameIDFormat,
|
2019-12-31 11:51:16 +00:00
|
|
|
)
|
2020-06-07 14:35:08 +00:00
|
|
|
from passbook.sources.saml.models import SAMLBindingTypes, SAMLSource
|
2020-02-20 20:33:45 +00:00
|
|
|
from passbook.sources.saml.processors.base import Processor
|
|
|
|
from passbook.sources.saml.utils import build_full_url, get_issuer
|
2019-11-07 16:02:56 +00:00
|
|
|
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"""
|
|
|
|
|
2020-02-18 21:12:51 +00:00
|
|
|
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
2019-11-07 16:02:56 +00:00
|
|
|
"""Replies with an XHTML SSO Request."""
|
2020-02-18 21:12:51 +00:00
|
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
2019-11-07 16:35:25 +00:00
|
|
|
if not source.enabled:
|
|
|
|
raise Http404
|
2019-12-31 11:51:16 +00:00
|
|
|
sso_destination = request.GET.get("next", None)
|
|
|
|
request.session["sso_destination"] = sso_destination
|
2019-11-07 16:02:56 +00:00
|
|
|
parameters = {
|
2019-12-31 11:51:16 +00:00
|
|
|
"ACS_URL": build_full_url("acs", request, source),
|
|
|
|
"DESTINATION": source.idp_url,
|
|
|
|
"AUTHN_REQUEST_ID": get_random_id(),
|
|
|
|
"ISSUE_INSTANT": get_time_string(),
|
2020-02-20 16:23:27 +00:00
|
|
|
"ISSUER": get_issuer(request, source),
|
2019-11-07 16:02:56 +00:00
|
|
|
}
|
|
|
|
authn_req = get_authnrequest_xml(parameters, signed=False)
|
|
|
|
_request = nice64(str.encode(authn_req))
|
2020-06-07 14:35:08 +00:00
|
|
|
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
|
2019-11-07 16:02:56 +00:00
|
|
|
|
|
|
|
|
2019-12-31 11:51:16 +00:00
|
|
|
@method_decorator(csrf_exempt, name="dispatch")
|
2019-11-07 16:02:56 +00:00
|
|
|
class ACSView(View):
|
|
|
|
"""AssertionConsumerService, consume assertion and log user in"""
|
|
|
|
|
2020-02-18 21:12:51 +00:00
|
|
|
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
2019-11-07 16:02:56 +00:00
|
|
|
"""Handles a POSTed SSO Assertion and logs the user in."""
|
2020-02-18 21:12:51 +00:00
|
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
2019-11-07 16:35:25 +00:00
|
|
|
if not source.enabled:
|
|
|
|
raise Http404
|
2020-02-20 20:33:45 +00:00
|
|
|
processor = Processor(source)
|
|
|
|
try:
|
|
|
|
processor.parse(request)
|
|
|
|
except MissingSAMLResponse as exc:
|
|
|
|
return bad_request_message(request, str(exc))
|
|
|
|
|
|
|
|
try:
|
2020-06-07 14:35:08 +00:00
|
|
|
return processor.prepare_flow(request)
|
2020-02-20 20:33:45 +00:00
|
|
|
except UnsupportedNameIDFormat as exc:
|
|
|
|
return bad_request_message(request, str(exc))
|
2019-11-07 16:02:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SLOView(View):
|
|
|
|
"""Single-Logout-View"""
|
|
|
|
|
2020-02-18 21:12:51 +00:00
|
|
|
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
2019-11-07 16:02:56 +00:00
|
|
|
"""Replies with an XHTML SSO Request."""
|
2020-02-18 21:12:51 +00:00
|
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
2019-11-07 16:35:25 +00:00
|
|
|
if not source.enabled:
|
|
|
|
raise Http404
|
2019-11-07 16:02:56 +00:00
|
|
|
logout(request)
|
2019-12-31 11:51:16 +00:00
|
|
|
return render(
|
|
|
|
request,
|
|
|
|
"saml/sp/sso_single_logout.html",
|
|
|
|
{
|
|
|
|
"idp_logout_url": source.idp_logout_url,
|
|
|
|
"autosubmit": source.auto_logout,
|
|
|
|
},
|
|
|
|
)
|
2019-11-07 16:02:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
class MetadataView(View):
|
|
|
|
"""Return XML Metadata for IDP"""
|
|
|
|
|
2020-02-18 21:12:51 +00:00
|
|
|
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
2019-11-07 16:02:56 +00:00
|
|
|
"""Replies with the XML Metadata SPSSODescriptor."""
|
2020-02-18 21:12:51 +00:00
|
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug)
|
2020-02-20 16:23:27 +00:00
|
|
|
issuer = get_issuer(request, source)
|
2020-03-03 22:35:38 +00:00
|
|
|
cert_stripped = strip_pem_header(
|
|
|
|
source.signing_kp.certificate_data.replace("\r", "")
|
|
|
|
).replace("\n", "")
|
2019-12-31 11:51:16 +00:00
|
|
|
return render_xml(
|
|
|
|
request,
|
2020-02-20 16:23:27 +00:00
|
|
|
"saml/sp/xml/sp_sso_descriptor.xml",
|
2019-12-31 11:51:16 +00:00
|
|
|
{
|
|
|
|
"acs_url": build_full_url("acs", request, source),
|
2020-02-20 16:23:27 +00:00
|
|
|
"issuer": issuer,
|
2020-02-20 16:05:11 +00:00
|
|
|
"cert_public_key": cert_stripped,
|
2019-12-31 11:51:16 +00:00
|
|
|
},
|
|
|
|
)
|