e36d7928e4
add New fields for - assertion_valid_not_before - assertion_valid_not_on_or_after - session_valid_not_on_or_after allow flexible time durations for these fields fall back to Provider's ACS if none is specified in AuthNRequest
109 lines
3.9 KiB
Python
109 lines
3.9 KiB
Python
"""saml sp views"""
|
|
import base64
|
|
|
|
from defusedxml import ElementTree
|
|
from django.contrib.auth import login, logout
|
|
from django.http import Http404, HttpRequest, HttpResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render, reverse
|
|
from django.utils.decorators import method_decorator
|
|
from django.views import View
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
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.models import SAMLSource
|
|
from passbook.sources.saml.utils import (
|
|
_get_user_from_response,
|
|
build_full_url,
|
|
get_entity_id,
|
|
)
|
|
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: str) -> HttpResponse:
|
|
"""Replies with an XHTML SSO Request."""
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
|
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_entity_id(request, source),
|
|
}
|
|
authn_req = get_authnrequest_xml(parameters, signed=False)
|
|
_request = nice64(str.encode(authn_req))
|
|
return render(
|
|
request,
|
|
"saml/sp/login.html",
|
|
{
|
|
"request_url": source.idp_url,
|
|
"request": _request,
|
|
"token": sso_destination,
|
|
"source": source,
|
|
},
|
|
)
|
|
|
|
|
|
@method_decorator(csrf_exempt, name="dispatch")
|
|
class ACSView(View):
|
|
"""AssertionConsumerService, consume assertion and log user in"""
|
|
|
|
def post(self, request: HttpRequest, source: str) -> HttpResponse:
|
|
"""Handles a POSTed SSO Assertion and logs the user in."""
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
|
if not source.enabled:
|
|
raise Http404
|
|
# sso_session = request.POST.get('RelayState', None)
|
|
data = request.POST.get("SAMLResponse", None)
|
|
response = base64.b64decode(data)
|
|
root = ElementTree.fromstring(response)
|
|
user = _get_user_from_response(root)
|
|
# attributes = _get_attributes_from_response(root)
|
|
login(request, user, backend="django.contrib.auth.backends.ModelBackend")
|
|
return redirect(reverse("passbook_core:overview"))
|
|
|
|
|
|
class SLOView(View):
|
|
"""Single-Logout-View"""
|
|
|
|
def dispatch(self, request: HttpRequest, source: str) -> HttpResponse:
|
|
"""Replies with an XHTML SSO Request."""
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
|
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: str) -> HttpResponse:
|
|
"""Replies with the XML Metadata SPSSODescriptor."""
|
|
source: SAMLSource = get_object_or_404(SAMLSource, slug=source)
|
|
entity_id = get_entity_id(request, source)
|
|
return render_xml(
|
|
request,
|
|
"saml/sp/xml/spssodescriptor.xml",
|
|
{
|
|
"acs_url": build_full_url("acs", request, source),
|
|
"entity_id": entity_id,
|
|
"cert_public_key": source.signing_cert,
|
|
},
|
|
)
|