198 lines
5.7 KiB
Python
198 lines
5.7 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
|
|
"""
|
|
Contains classes used in the SAML ECP profile
|
|
"""
|
|
import logging
|
|
from saml2.client_base import ACTOR
|
|
from saml2.ecp_client import SERVICE
|
|
|
|
from saml2 import element_to_extension_element
|
|
from saml2 import samlp
|
|
from saml2 import soap
|
|
from saml2 import BINDING_SOAP, BINDING_PAOS
|
|
|
|
from saml2.profile import paos
|
|
from saml2.profile import ecp
|
|
|
|
#from saml2.client import Saml2Client
|
|
from saml2.server import Server
|
|
|
|
from saml2.schema import soapenv
|
|
|
|
from saml2.response import authn_response
|
|
|
|
from saml2 import saml
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def ecp_capable(headers):
|
|
if "application/vnd.paos+xml" in headers["Accept"]:
|
|
if "PAOS" in headers:
|
|
if 'ver="%s";"%s"' % (paos.NAMESPACE,
|
|
SERVICE) in headers["PAOS"]:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
#noinspection PyUnusedLocal
|
|
def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
|
|
""" Makes an authentication request.
|
|
|
|
:param entityid: The entity ID of the IdP to send the request to
|
|
:param relay_state: To where the user should be returned after
|
|
successfull log in.
|
|
:param sign: Whether the request should be signed or not.
|
|
:return: AuthnRequest response
|
|
"""
|
|
|
|
eelist = []
|
|
|
|
# ----------------------------------------
|
|
# <paos:Request>
|
|
# ----------------------------------------
|
|
my_url = cls.service_urls(BINDING_PAOS)[0]
|
|
|
|
# must_understand and actor according to the standard
|
|
#
|
|
paos_request = paos.Request(must_understand="1", actor=ACTOR,
|
|
response_consumer_url=my_url,
|
|
service=SERVICE)
|
|
|
|
eelist.append(element_to_extension_element(paos_request))
|
|
|
|
# ----------------------------------------
|
|
# <samlp:AuthnRequest>
|
|
# ----------------------------------------
|
|
|
|
logger.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP))
|
|
|
|
location = cls._sso_location(entityid, binding=BINDING_SOAP)
|
|
req_id, authn_req = cls.create_authn_request(
|
|
location, binding=BINDING_PAOS, service_url_binding=BINDING_PAOS)
|
|
|
|
body = soapenv.Body()
|
|
body.extension_elements = [element_to_extension_element(authn_req)]
|
|
|
|
# ----------------------------------------
|
|
# <ecp:Request>
|
|
# ----------------------------------------
|
|
|
|
# idp = samlp.IDPEntry(
|
|
# provider_id = "https://idp.example.org/entity",
|
|
# name = "Example identity provider",
|
|
# loc = "https://idp.example.org/saml2/sso",
|
|
# )
|
|
#
|
|
# idp_list = samlp.IDPList(idp_entry= [idp])
|
|
|
|
idp_list = None
|
|
ecp_request = ecp.Request(
|
|
actor=ACTOR,
|
|
must_understand="1",
|
|
provider_name=None,
|
|
issuer=saml.Issuer(text=authn_req.issuer.text),
|
|
idp_list=idp_list)
|
|
|
|
eelist.append(element_to_extension_element(ecp_request))
|
|
|
|
# ----------------------------------------
|
|
# <ecp:RelayState>
|
|
# ----------------------------------------
|
|
|
|
relay_state = ecp.RelayState(actor=ACTOR, must_understand="1",
|
|
text=relay_state)
|
|
|
|
eelist.append(element_to_extension_element(relay_state))
|
|
|
|
header = soapenv.Header()
|
|
header.extension_elements = eelist
|
|
|
|
# ----------------------------------------
|
|
# The SOAP envelope
|
|
# ----------------------------------------
|
|
|
|
soap_envelope = soapenv.Envelope(header=header, body=body)
|
|
|
|
return req_id, "%s" % soap_envelope
|
|
|
|
|
|
def handle_ecp_authn_response(cls, soap_message, outstanding=None):
|
|
rdict = soap.class_instances_from_soap_enveloped_saml_thingies(
|
|
soap_message, [paos, ecp, samlp])
|
|
|
|
_relay_state = None
|
|
for item in rdict["header"]:
|
|
if item.c_tag == "RelayState" and item.c_namespace == ecp.NAMESPACE:
|
|
_relay_state = item
|
|
|
|
response = authn_response(cls.config, cls.service_urls(), outstanding,
|
|
allow_unsolicited=True)
|
|
|
|
response.loads("%s" % rdict["body"], False, soap_message)
|
|
response.verify()
|
|
cls.users.add_information_about_person(response.session_info())
|
|
|
|
return response, _relay_state
|
|
|
|
|
|
def ecp_response(target_url, response):
|
|
|
|
# ----------------------------------------
|
|
# <ecp:Response
|
|
# ----------------------------------------
|
|
|
|
ecp_response = ecp.Response(assertion_consumer_service_url=target_url)
|
|
header = soapenv.Header()
|
|
header.extension_elements = [element_to_extension_element(ecp_response)]
|
|
|
|
# ----------------------------------------
|
|
# <samlp:Response
|
|
# ----------------------------------------
|
|
|
|
body = soapenv.Body()
|
|
body.extension_elements = [element_to_extension_element(response)]
|
|
|
|
soap_envelope = soapenv.Envelope(header=header, body=body)
|
|
|
|
return "%s" % soap_envelope
|
|
|
|
|
|
class ECPServer(Server):
|
|
""" This deals with what the IdP has to do
|
|
|
|
TODO: Still tentative
|
|
"""
|
|
def __init__(self, config_file="", config=None, cache=None):
|
|
Server.__init__(self, config_file, config, cache)
|
|
|
|
def parse_ecp_authn_query(self):
|
|
pass
|
|
|
|
def ecp_response(self):
|
|
|
|
# ----------------------------------------
|
|
# <ecp:Response
|
|
# ----------------------------------------
|
|
target_url = ""
|
|
|
|
ecp_response = ecp.Response(assertion_consumer_service_url=target_url)
|
|
header = soapenv.Body()
|
|
header.extension_elements = [element_to_extension_element(ecp_response)]
|
|
|
|
# ----------------------------------------
|
|
# <samlp:Response
|
|
# ----------------------------------------
|
|
|
|
response = samlp.Response()
|
|
body = soapenv.Body()
|
|
body.extension_elements = [element_to_extension_element(response)]
|
|
|
|
soap_envelope = soapenv.Envelope(header=header, body=body)
|
|
|
|
return "%s" % soap_envelope
|