Added example SP not using repoze.who .
This commit is contained in:
@@ -97,7 +97,7 @@ CONFIG = {
|
||||
"key_file": full_path("pki/mykey.pem"),
|
||||
"cert_file": full_path("pki/mycert.pem"),
|
||||
"metadata": {
|
||||
"local": [full_path("../sp/sp.xml")],
|
||||
"local": [full_path("../sp-wsgi/sp.xml")],
|
||||
},
|
||||
"organization": {
|
||||
"display_name": "Rolands Identiteter",
|
||||
|
34
example/sp-repoze/sp.xml
Normal file
34
example/sp-repoze/sp.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="http://www.w3.org/2000/09/xmldsig#" entityID="http://localhost:8087/sp.xml"><ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:KeyDescriptor use="encryption"><ns1:KeyInfo><ns1:X509Data><ns1:X509Certificate>MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||
</ns1:X509Certificate></ns1:X509Data></ns1:KeyInfo></ns0:KeyDescriptor><ns0:KeyDescriptor use="signing"><ns1:KeyInfo><ns1:X509Data><ns1:X509Certificate>MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||
</ns1:X509Certificate></ns1:X509Data></ns1:KeyInfo></ns0:KeyDescriptor><ns0:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8087/slo" /><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8087" index="1" /></ns0:SPSSODescriptor><ns0:Organization><ns0:OrganizationName xml:lang="en">Exempel AB</ns0:OrganizationName><ns0:OrganizationDisplayName xml:lang="se">Exempel AB</ns0:OrganizationDisplayName><ns0:OrganizationDisplayName xml:lang="en">Example Co.</ns0:OrganizationDisplayName><ns0:OrganizationURL xml:lang="en">http://www.example.com/roland</ns0:OrganizationURL></ns0:Organization><ns0:ContactPerson contactType="technical"><ns0:GivenName>John</ns0:GivenName><ns0:SurName>Smith</ns0:SurName><ns0:EmailAddress>john.smith@example.com</ns0:EmailAddress></ns0:ContactPerson></ns0:EntityDescriptor>
|
51
example/sp-repoze/sp_conf.py
Normal file
51
example/sp-repoze/sp_conf.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from saml2 import BINDING_HTTP_REDIRECT
|
||||
from saml2.saml import NAME_FORMAT_URI
|
||||
|
||||
BASE= "http://localhost:8087"
|
||||
#BASE= "http://lingon.catalogix.se:8087"
|
||||
|
||||
CONFIG = {
|
||||
"entityid": "%s/sp.xml" % BASE,
|
||||
"description": "My SP",
|
||||
"service": {
|
||||
"sp": {
|
||||
"name": "Rolands SP",
|
||||
"endpoints": {
|
||||
"assertion_consumer_service": [BASE],
|
||||
"single_logout_service": [(BASE + "/slo",
|
||||
BINDING_HTTP_REDIRECT)],
|
||||
},
|
||||
"required_attributes": ["surname", "givenname",
|
||||
"edupersonaffiliation"],
|
||||
"optional_attributes": ["title"],
|
||||
}
|
||||
},
|
||||
"debug": 1,
|
||||
"key_file": "pki/mykey.pem",
|
||||
"cert_file": "pki/mycert.pem",
|
||||
"attribute_map_dir": "./attributemaps",
|
||||
"metadata": {"local": ["../idp2/idp.xml"]},
|
||||
# -- below used by make_metadata --
|
||||
"organization": {
|
||||
"name": "Exempel AB",
|
||||
"display_name": [("Exempel AB", "se"), ("Example Co.", "en")],
|
||||
"url": "http://www.example.com/roland",
|
||||
},
|
||||
"contact_person": [{
|
||||
"given_name":"John",
|
||||
"sur_name": "Smith",
|
||||
"email_address": ["john.smith@example.com"],
|
||||
"contact_type": "technical",
|
||||
},
|
||||
],
|
||||
#"xmlsec_binary":"/opt/local/bin/xmlsec1",
|
||||
"name_form": NAME_FORMAT_URI,
|
||||
"logger": {
|
||||
"rotating": {
|
||||
"filename": "sp.log",
|
||||
"maxBytes": 100000,
|
||||
"backupCount": 5,
|
||||
},
|
||||
"loglevel": "debug",
|
||||
}
|
||||
}
|
41
example/sp-wsgi/conf.py
Normal file
41
example/sp-wsgi/conf.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from saml2.entity_category.edugain import COC
|
||||
from saml2 import BINDING_HTTP_REDIRECT
|
||||
from saml2 import BINDING_HTTP_POST
|
||||
from saml2.saml import NAME_FORMAT_URI
|
||||
|
||||
try:
|
||||
from saml2.sigver import get_xmlsec_binary
|
||||
except ImportError:
|
||||
get_xmlsec_binary = None
|
||||
|
||||
|
||||
if get_xmlsec_binary:
|
||||
xmlsec_path = get_xmlsec_binary(["/opt/local/bin","/usr/local/bin"])
|
||||
else:
|
||||
xmlsec_path = '/usr/local/bin/xmlsec1'
|
||||
|
||||
# Make sure the same port number appear in service_conf.py
|
||||
BASE = "http://localhost:8087"
|
||||
|
||||
CONFIG = {
|
||||
"entityid": "%s/%ssp.xml" % (BASE, ""),
|
||||
'entity_category': [COC],
|
||||
"description": "Example SP",
|
||||
"service": {
|
||||
"sp": {
|
||||
"endpoints": {
|
||||
"assertion_consumer_service": [
|
||||
("%s/acs/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
||||
("%s/acs/post" % BASE, BINDING_HTTP_POST)
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
"key_file": "pki/mykey.pem",
|
||||
"cert_file": "pki/mycert.pem",
|
||||
"xmlsec_binary": xmlsec_path,
|
||||
"metadata": {"local": ["../idp2/idp.xml"]},
|
||||
#"metadata": {"mdfile": ["./swamid2.md"]},
|
||||
#"metadata": {"local": ["./swamid-2.0.xml"]},
|
||||
"name_form": NAME_FORMAT_URI,
|
||||
}
|
18
example/sp-wsgi/pki/mycert.pem
Normal file
18
example/sp-wsgi/pki/mycert.pem
Normal file
@@ -0,0 +1,18 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||
-----END CERTIFICATE-----
|
15
example/sp-wsgi/pki/mykey.pem
Normal file
15
example/sp-wsgi/pki/mykey.pem
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQDkJWP7bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr
|
||||
6/ROgW96ZeQ57fzVy2MCFiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43
|
||||
qCfLx+clUlOvtnsoMiiRmo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQAB
|
||||
AoGAbx9rKH91DCw/ZEPhHsVXJ6cYHxGcMoAWvnMMC9WUN+bNo4gNL205DLfsxXA1
|
||||
jqXFXZj3+38vSFumGPA6IvXrN+Wyp3+Lz3QGc4K5OdHeBtYlxa6EsrxPgvuxYDUB
|
||||
vx3xdWPMjy06G/ML+pR9XHnRaPNubXQX3UxGBuLjwNXVmyECQQD2/D84tYoCGWoq
|
||||
5FhUBxFUy2nnOLKYC/GGxBTX62iLfMQ3fbQcdg2pJsB5rrniyZf7UL+9FOsAO9k1
|
||||
8DO7G12DAkEA7Hkdg1KEw4ZfjnnjEa+KqpyLTLRQ91uTVW6kzR+4zY719iUJ/PXE
|
||||
PxJqm1ot7mJd1LW+bWtjLpxs7jYH19V+kQJBAIEpn2JnxdmdMuFlcy/WVmDy09pg
|
||||
0z0imdexeXkFmjHAONkQOv3bWv+HzYaVMo8AgCOksfEPHGqN4eUMTfFeuUMCQF+5
|
||||
E1JSd/2yCkJhYqKJHae8oMLXByNqRXTCyiFioutK4JPYIHfugJdLfC4QziD+Xp85
|
||||
RrGCU+7NUWcIJhqfiJECQAIgUAzfzhdj5AyICaFPaOQ+N8FVMLcTyqeTXP0sIlFk
|
||||
JStVibemTRCbxdXXM7OVipz1oW3PBVEO3t/VyjiaGGg=
|
||||
-----END RSA PRIVATE KEY-----
|
16
example/sp-wsgi/service_conf.py
Normal file
16
example/sp-wsgi/service_conf.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from saml2.assertion import Policy
|
||||
|
||||
PORT = 8087
|
||||
HTTPS = False
|
||||
|
||||
# Which groups of entity categories to use
|
||||
POLICY = Policy(
|
||||
{
|
||||
"default": {"entity_categories": ["swamid", "edugain"]}
|
||||
}
|
||||
)
|
||||
|
||||
# HTTPS cert information
|
||||
SERVER_CERT = "pki/ssl.crt"
|
||||
SERVER_KEY = "pki/ssl.pem"
|
||||
CERT_CHAIN = ""
|
740
example/sp-wsgi/sp.py
Executable file
740
example/sp-wsgi/sp.py
Executable file
@@ -0,0 +1,740 @@
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
import re
|
||||
import argparse
|
||||
import service_conf
|
||||
|
||||
from Cookie import SimpleCookie
|
||||
from urlparse import parse_qs
|
||||
import sys
|
||||
|
||||
from saml2 import BINDING_HTTP_REDIRECT
|
||||
from saml2 import BINDING_SOAP
|
||||
from saml2 import time_util
|
||||
from saml2 import ecp
|
||||
from saml2 import BINDING_HTTP_ARTIFACT
|
||||
from saml2 import BINDING_HTTP_POST
|
||||
from saml2.client import Saml2Client
|
||||
from saml2.ecp_client import PAOS_HEADER_INFO
|
||||
from saml2.httputil import geturl, make_cookie, parse_cookie
|
||||
from saml2.httputil import get_post
|
||||
from saml2.httputil import Response
|
||||
from saml2.httputil import BadRequest
|
||||
from saml2.httputil import ServiceError
|
||||
from saml2.httputil import SeeOther
|
||||
from saml2.httputil import Unauthorized
|
||||
from saml2.httputil import NotFound
|
||||
from saml2.httputil import Redirect
|
||||
from saml2.httputil import NotImplemented
|
||||
from saml2.response import StatusError
|
||||
from saml2.response import VerificationError
|
||||
from saml2.s_utils import UnknownPrincipal
|
||||
from saml2.s_utils import UnsupportedBinding
|
||||
from saml2.s_utils import sid
|
||||
from saml2.s_utils import rndstr
|
||||
#from srtest import exception_trace
|
||||
|
||||
logger = logging.getLogger("")
|
||||
hdlr = logging.FileHandler('spx.log')
|
||||
base_formatter = logging.Formatter(
|
||||
"%(asctime)s %(name)s:%(levelname)s %(message)s")
|
||||
|
||||
hdlr.setFormatter(base_formatter)
|
||||
logger.addHandler(hdlr)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
SP = None
|
||||
SEED = ""
|
||||
POLICY = None
|
||||
|
||||
|
||||
def dict_to_table(ava, lev=0, width=1):
|
||||
txt = ['<table border=%s bordercolor="black">\n' % width]
|
||||
for prop, valarr in ava.items():
|
||||
txt.append("<tr>\n")
|
||||
if isinstance(valarr, basestring):
|
||||
txt.append("<th>%s</th>\n" % str(prop))
|
||||
try:
|
||||
txt.append("<td>%s</td>\n" % valarr.encode("utf8"))
|
||||
except AttributeError:
|
||||
txt.append("<td>%s</td>\n" % valarr)
|
||||
elif isinstance(valarr, list):
|
||||
i = 0
|
||||
n = len(valarr)
|
||||
for val in valarr:
|
||||
if not i:
|
||||
txt.append("<th rowspan=%d>%s</td>\n" % (len(valarr), prop))
|
||||
else:
|
||||
txt.append("<tr>\n")
|
||||
if isinstance(val, dict):
|
||||
txt.append("<td>\n")
|
||||
txt.extend(dict_to_table(val, lev + 1, width - 1))
|
||||
txt.append("</td>\n")
|
||||
else:
|
||||
try:
|
||||
txt.append("<td>%s</td>\n" % val.encode("utf8"))
|
||||
except AttributeError:
|
||||
txt.append("<td>%s</td>\n" % val)
|
||||
if n > 1:
|
||||
txt.append("</tr>\n")
|
||||
n -= 1
|
||||
i += 1
|
||||
elif isinstance(valarr, dict):
|
||||
txt.append("<th>%s</th>\n" % prop)
|
||||
txt.append("<td>\n")
|
||||
txt.extend(dict_to_table(valarr, lev + 1, width - 1))
|
||||
txt.append("</td>\n")
|
||||
txt.append("</tr>\n")
|
||||
txt.append('</table>\n')
|
||||
return txt
|
||||
|
||||
|
||||
def handle_static(environ, start_response, path):
|
||||
"""
|
||||
Creates a response for a static file. There might be a longer path
|
||||
then just /static/... if so strip the path leading up to static.
|
||||
|
||||
:param environ: wsgi enviroment
|
||||
:param start_response: wsgi start response
|
||||
:param path: the static file and path to the file.
|
||||
:return: wsgi response for the static file.
|
||||
"""
|
||||
try:
|
||||
text = open(path).read()
|
||||
if path.endswith(".ico"):
|
||||
resp = Response(text, headers=[('Content-Type', "image/x-icon")])
|
||||
elif path.endswith(".html"):
|
||||
resp = Response(text, headers=[('Content-Type', 'text/html')])
|
||||
elif path.endswith(".txt"):
|
||||
resp = Response(text, headers=[('Content-Type', 'text/plain')])
|
||||
elif path.endswith(".css"):
|
||||
resp = Response(text, headers=[('Content-Type', 'text/css')])
|
||||
elif path.endswith(".js"):
|
||||
resp = Response(text, headers=[('Content-Type', 'text/javascript')])
|
||||
elif path.endswith(".png"):
|
||||
resp = Response(text, headers=[('Content-Type', 'image/png')])
|
||||
else:
|
||||
resp = Response(text)
|
||||
except IOError:
|
||||
resp = NotFound()
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
class ECPResponse(object):
|
||||
code = 200
|
||||
title = 'OK'
|
||||
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def __call__(self, environ, start_response):
|
||||
start_response('%s %s' % (self.code, self.title),
|
||||
[('Content-Type', "text/xml")])
|
||||
return [self.content]
|
||||
|
||||
|
||||
def _expiration(timeout, tformat=None):
|
||||
# Wed, 06-Jun-2012 01:34:34 GMT
|
||||
if not tformat:
|
||||
tformat = '%a, %d-%b-%Y %T GMT'
|
||||
|
||||
if timeout == "now":
|
||||
return time_util.instant(tformat)
|
||||
else:
|
||||
# validity time should match lifetime of assertions
|
||||
return time_util.in_a_while(minutes=timeout, format=tformat)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
def __init__(self):
|
||||
self.uid2user = {}
|
||||
self.cookie_name = "spauthn"
|
||||
self.outstanding_queries = {}
|
||||
self.relay_state = {}
|
||||
self.user = {}
|
||||
self.result = {}
|
||||
|
||||
def kaka2user(self, kaka):
|
||||
logger.debug("KAKA: %s" % kaka)
|
||||
if kaka:
|
||||
cookie_obj = SimpleCookie(kaka)
|
||||
morsel = cookie_obj.get(self.cookie_name, None)
|
||||
if morsel:
|
||||
try:
|
||||
return self.uid2user[morsel.value]
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
logger.debug("No spauthn cookie")
|
||||
return None
|
||||
|
||||
def delete_cookie(self, environ=None, kaka=None):
|
||||
if not kaka:
|
||||
kaka = environ.get("HTTP_COOKIE", '')
|
||||
logger.debug("delete KAKA: %s" % kaka)
|
||||
if kaka:
|
||||
_name = self.cookie_name
|
||||
cookie_obj = SimpleCookie(kaka)
|
||||
morsel = cookie_obj.get(_name, None)
|
||||
cookie = SimpleCookie()
|
||||
cookie[_name] = ""
|
||||
cookie[_name]['path'] = "/"
|
||||
logger.debug("Expire: %s" % morsel)
|
||||
cookie[_name]["expires"] = _expiration("dawn")
|
||||
return tuple(cookie.output().split(": ", 1))
|
||||
return None
|
||||
|
||||
def user2kaka(self, user):
|
||||
uid = rndstr(32)
|
||||
self.uid2user[uid] = user
|
||||
cookie = SimpleCookie()
|
||||
cookie[self.cookie_name] = uid
|
||||
cookie[self.cookie_name]['path'] = "/"
|
||||
cookie[self.cookie_name]["expires"] = _expiration(480)
|
||||
logger.debug("Cookie expires: %s" % cookie[self.cookie_name]["expires"])
|
||||
return tuple(cookie.output().split(": ", 1))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# RECEIVERS
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Service(object):
|
||||
def __init__(self, environ, start_response, user=None):
|
||||
self.environ = environ
|
||||
logger.debug("ENVIRON: %s" % environ)
|
||||
self.start_response = start_response
|
||||
self.user = user
|
||||
self.sp = None
|
||||
|
||||
def unpack_redirect(self):
|
||||
if "QUERY_STRING" in self.environ:
|
||||
_qs = self.environ["QUERY_STRING"]
|
||||
return dict([(k, v[0]) for k, v in parse_qs(_qs).items()])
|
||||
else:
|
||||
return None
|
||||
|
||||
def unpack_post(self):
|
||||
_dict = parse_qs(get_post(self.environ))
|
||||
logger.debug("unpack_post:: %s" % _dict)
|
||||
try:
|
||||
return dict([(k, v[0]) for k, v in _dict.items()])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def unpack_soap(self):
|
||||
try:
|
||||
query = get_post(self.environ)
|
||||
return {"SAMLResponse": query, "RelayState": ""}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def unpack_either(self):
|
||||
if self.environ["REQUEST_METHOD"] == "GET":
|
||||
_dict = self.unpack_redirect()
|
||||
elif self.environ["REQUEST_METHOD"] == "POST":
|
||||
_dict = self.unpack_post()
|
||||
else:
|
||||
_dict = None
|
||||
logger.debug("_dict: %s" % _dict)
|
||||
return _dict
|
||||
|
||||
def operation(self, _dict, binding):
|
||||
logger.debug("_operation: %s" % _dict)
|
||||
if not _dict:
|
||||
resp = BadRequest('Error parsing request or no request')
|
||||
return resp(self.environ, self.start_response)
|
||||
else:
|
||||
try:
|
||||
_relay_state = _dict["RelayState"]
|
||||
except KeyError:
|
||||
_relay_state = ""
|
||||
if "SAMLResponse" in _dict:
|
||||
return self.do(_dict["SAMLResponse"], binding,
|
||||
_relay_state, mtype="response")
|
||||
elif "SAMLRequest" in _dict:
|
||||
return self.do(_dict["SAMLRequest"], binding,
|
||||
_relay_state, mtype="request")
|
||||
|
||||
def artifact_operation(self, _dict):
|
||||
if not _dict:
|
||||
resp = BadRequest("Missing query")
|
||||
return resp(self.environ, self.start_response)
|
||||
else:
|
||||
# exchange artifact for response
|
||||
request = self.sp.artifact2message(_dict["SAMLart"], "spsso")
|
||||
return self.do(request, BINDING_HTTP_ARTIFACT, _dict["RelayState"])
|
||||
|
||||
def response(self, binding, http_args):
|
||||
if binding == BINDING_HTTP_ARTIFACT:
|
||||
resp = Redirect()
|
||||
else:
|
||||
resp = Response(http_args["data"], headers=http_args["headers"])
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
def do(self, query, binding, relay_state="", mtype="response"):
|
||||
pass
|
||||
|
||||
def redirect(self):
|
||||
""" Expects a HTTP-redirect response """
|
||||
|
||||
_dict = self.unpack_redirect()
|
||||
return self.operation(_dict, BINDING_HTTP_REDIRECT)
|
||||
|
||||
def post(self):
|
||||
""" Expects a HTTP-POST response """
|
||||
|
||||
_dict = self.unpack_post()
|
||||
return self.operation(_dict, BINDING_HTTP_POST)
|
||||
|
||||
def artifact(self):
|
||||
# Can be either by HTTP_Redirect or HTTP_POST
|
||||
_dict = self.unpack_either()
|
||||
return self.artifact_operation(_dict)
|
||||
|
||||
def soap(self):
|
||||
"""
|
||||
Single log out using HTTP_SOAP binding
|
||||
"""
|
||||
logger.debug("- SOAP -")
|
||||
_dict = self.unpack_soap()
|
||||
logger.debug("_dict: %s" % _dict)
|
||||
return self.operation(_dict, BINDING_SOAP)
|
||||
|
||||
def uri(self):
|
||||
_dict = self.unpack_either()
|
||||
return self.operation(_dict, BINDING_SOAP)
|
||||
|
||||
def not_authn(self):
|
||||
resp = Unauthorized('Unknown user')
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Attribute Consuming service
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ACS(Service):
|
||||
def __init__(self, sp, environ, start_response, cache=None, **kwargs):
|
||||
Service.__init__(self, environ, start_response)
|
||||
self.sp = sp
|
||||
self.outstanding_queries = cache.outstanding_queries
|
||||
self.cache = cache
|
||||
self.response = None
|
||||
self.kwargs = kwargs
|
||||
|
||||
def do(self, response, binding, relay_state="", mtype="response"):
|
||||
"""
|
||||
:param response: The SAML response, transport encoded
|
||||
:param binding: Which binding the query came in over
|
||||
"""
|
||||
#tmp_outstanding_queries = dict(self.outstanding_queries)
|
||||
if not response:
|
||||
logger.info("Missing Response")
|
||||
resp = Unauthorized('Unknown user')
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
try:
|
||||
self.response = self.sp.parse_authn_request_response(
|
||||
response, binding, self.outstanding_queries)
|
||||
except UnknownPrincipal, excp:
|
||||
logger.error("UnknownPrincipal: %s" % (excp,))
|
||||
resp = ServiceError("UnknownPrincipal: %s" % (excp,))
|
||||
return resp(self.environ, self.start_response)
|
||||
except UnsupportedBinding, excp:
|
||||
logger.error("UnsupportedBinding: %s" % (excp,))
|
||||
resp = ServiceError("UnsupportedBinding: %s" % (excp,))
|
||||
return resp(self.environ, self.start_response)
|
||||
except VerificationError, err:
|
||||
resp = ServiceError("Verification error: %s" % (err,))
|
||||
return resp(self.environ, self.start_response)
|
||||
except Exception, err:
|
||||
resp = ServiceError("Other error: %s" % (err,))
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
logger.info("AVA: %s" % self.response.ava)
|
||||
resp = Response(dict_to_table(self.response.ava))
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
def verify_attributes(self, ava):
|
||||
logger.info("SP: %s" % self.sp.config.entityid)
|
||||
rest = POLICY.get_entity_categories_restriction(
|
||||
self.sp.config.entityid, self.sp.metadata)
|
||||
|
||||
akeys = [k.lower() for k in ava.keys()]
|
||||
|
||||
res = {"less": [], "more": []}
|
||||
for key, attr in rest.items():
|
||||
if key not in ava:
|
||||
if key not in akeys:
|
||||
res["less"].append(key)
|
||||
|
||||
for key, attr in ava.items():
|
||||
_key = key.lower()
|
||||
if _key not in rest:
|
||||
res["more"].append(key)
|
||||
|
||||
return res
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# REQUESTERS
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class SSO(object):
|
||||
def __init__(self, sp, environ, start_response, cache=None,
|
||||
wayf=None, discosrv=None, bindings=None):
|
||||
self.sp = sp
|
||||
self.environ = environ
|
||||
self.start_response = start_response
|
||||
self.cache = cache
|
||||
self.idp_query_param = "IdpQuery"
|
||||
self.wayf = wayf
|
||||
self.discosrv = discosrv
|
||||
if bindings:
|
||||
self.bindings = bindings
|
||||
else:
|
||||
self.bindings = [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST,
|
||||
BINDING_HTTP_ARTIFACT]
|
||||
logger.debug("--- SSO ---")
|
||||
|
||||
def response(self, binding, http_args, do_not_start_response=False):
|
||||
if binding == BINDING_HTTP_ARTIFACT:
|
||||
resp = Redirect()
|
||||
elif binding == BINDING_HTTP_REDIRECT:
|
||||
for param, value in http_args["headers"]:
|
||||
if param == "Location":
|
||||
resp = SeeOther(str(value))
|
||||
break
|
||||
else:
|
||||
resp = ServiceError("Parameter error")
|
||||
else:
|
||||
resp = Response(http_args["data"], headers=http_args["headers"])
|
||||
|
||||
if do_not_start_response:
|
||||
return resp
|
||||
else:
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
def _wayf_redirect(self, came_from):
|
||||
sid_ = sid()
|
||||
self.cache.outstanding_queries[sid_] = came_from
|
||||
logger.debug("Redirect to WAYF function: %s" % self.wayf)
|
||||
return -1, SeeOther(headers=[('Location', "%s?%s" % (self.wayf, sid_))])
|
||||
|
||||
def _pick_idp(self, came_from):
|
||||
"""
|
||||
If more than one idp and if none is selected, I have to do wayf or
|
||||
disco
|
||||
"""
|
||||
|
||||
_cli = self.sp
|
||||
|
||||
logger.debug("[_pick_idp] %s" % self.environ)
|
||||
if "HTTP_PAOS" in self.environ:
|
||||
if self.environ["HTTP_PAOS"] == PAOS_HEADER_INFO:
|
||||
if 'application/vnd.paos+xml' in self.environ["HTTP_ACCEPT"]:
|
||||
# Where should I redirect the user to
|
||||
# entityid -> the IdP to use
|
||||
# relay_state -> when back from authentication
|
||||
|
||||
logger.debug("- ECP client detected -")
|
||||
|
||||
_rstate = rndstr()
|
||||
self.cache.relay_state[_rstate] = geturl(self.environ)
|
||||
_entityid = _cli.config.ecp_endpoint(
|
||||
self.environ["REMOTE_ADDR"])
|
||||
|
||||
if not _entityid:
|
||||
return -1, ServiceError("No IdP to talk to")
|
||||
logger.debug("IdP to talk to: %s" % _entityid)
|
||||
return ecp.ecp_auth_request(_cli, _entityid, _rstate)
|
||||
else:
|
||||
return -1, ServiceError('Faulty Accept header')
|
||||
else:
|
||||
return -1, ServiceError('unknown ECP version')
|
||||
|
||||
# Find all IdPs
|
||||
idps = self.sp.metadata.with_descriptor("idpsso")
|
||||
|
||||
idp_entity_id = None
|
||||
|
||||
kaka = self.environ.get("HTTP_COOKIE", '')
|
||||
if kaka:
|
||||
try:
|
||||
(idp_entity_id, _) = parse_cookie("ve_disco", "SEED_SAW", kaka)
|
||||
except ValueError:
|
||||
pass
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# Any specific IdP specified in a query part
|
||||
query = self.environ.get("QUERY_STRING")
|
||||
if not idp_entity_id and query:
|
||||
try:
|
||||
_idp_entity_id = dict(parse_qs(query))[
|
||||
self.idp_query_param][0]
|
||||
if _idp_entity_id in idps:
|
||||
idp_entity_id = _idp_entity_id
|
||||
except KeyError:
|
||||
logger.debug("No IdP entity ID in query: %s" % query)
|
||||
pass
|
||||
|
||||
if not idp_entity_id:
|
||||
|
||||
if self.wayf:
|
||||
if query:
|
||||
try:
|
||||
wayf_selected = dict(parse_qs(query))[
|
||||
"wayf_selected"][0]
|
||||
except KeyError:
|
||||
return self._wayf_redirect(came_from)
|
||||
idp_entity_id = wayf_selected
|
||||
else:
|
||||
return self._wayf_redirect(came_from)
|
||||
elif self.discosrv:
|
||||
if query:
|
||||
idp_entity_id = _cli.parse_discovery_service_response(
|
||||
query=self.environ.get("QUERY_STRING"))
|
||||
if not idp_entity_id:
|
||||
sid_ = sid()
|
||||
self.cache.outstanding_queries[sid_] = came_from
|
||||
logger.debug("Redirect to Discovery Service function")
|
||||
eid = _cli.config.entityid
|
||||
ret = _cli.config.getattr("endpoints",
|
||||
"sp")["discovery_response"][0][0]
|
||||
ret += "?sid=%s" % sid_
|
||||
loc = _cli.create_discovery_service_request(
|
||||
self.discosrv, eid, **{"return": ret})
|
||||
return -1, SeeOther(loc)
|
||||
elif len(idps) == 1:
|
||||
# idps is a dictionary
|
||||
idp_entity_id = idps.keys()[0]
|
||||
elif not len(idps):
|
||||
return -1, ServiceError('Misconfiguration')
|
||||
else:
|
||||
return -1, NotImplemented("No WAYF or DS present!")
|
||||
|
||||
logger.info("Chosen IdP: '%s'" % idp_entity_id)
|
||||
return 0, idp_entity_id
|
||||
|
||||
def redirect_to_auth(self, _cli, entity_id, came_from, vorg_name=""):
|
||||
try:
|
||||
_binding, destination = _cli.pick_binding(
|
||||
"single_sign_on_service", self.bindings, "idpsso",
|
||||
entity_id=entity_id)
|
||||
logger.debug("binding: %s, destination: %s" % (_binding,
|
||||
destination))
|
||||
req = _cli.create_authn_request(destination, vorg=vorg_name)
|
||||
_rstate = rndstr()
|
||||
self.cache.relay_state[_rstate] = came_from
|
||||
ht_args = _cli.apply_binding(_binding, "%s" % req, destination,
|
||||
relay_state=_rstate)
|
||||
_sid = req.id
|
||||
logger.debug("ht_args: %s" % ht_args)
|
||||
except Exception, exc:
|
||||
logger.exception(exc)
|
||||
resp = ServiceError(
|
||||
"Failed to construct the AuthnRequest: %s" % exc)
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
# remember the request
|
||||
self.cache.outstanding_queries[_sid] = came_from
|
||||
return self.response(_binding, ht_args, do_not_start_response=True)
|
||||
|
||||
def do(self):
|
||||
_cli = self.sp
|
||||
|
||||
# Which page was accessed to get here
|
||||
came_from = geturl(self.environ)
|
||||
logger.debug("[sp.challenge] RelayState >> '%s'" % came_from)
|
||||
|
||||
# Am I part of a virtual organization or more than one ?
|
||||
try:
|
||||
vorg_name = _cli.vorg._name
|
||||
except AttributeError:
|
||||
vorg_name = ""
|
||||
|
||||
logger.debug("[sp.challenge] VO: %s" % vorg_name)
|
||||
|
||||
# If more than one idp and if none is selected, I have to do wayf
|
||||
(done, response) = self._pick_idp(came_from)
|
||||
# Three cases: -1 something went wrong or Discovery service used
|
||||
# 0 I've got an IdP to send a request to
|
||||
# >0 ECP in progress
|
||||
logger.debug("_idp_pick returned: %s" % done)
|
||||
if done == -1:
|
||||
return response(self.environ, self.start_response)
|
||||
elif done > 0:
|
||||
self.cache.outstanding_queries[done] = came_from
|
||||
return ECPResponse(response)
|
||||
else:
|
||||
entity_id = response
|
||||
# Do the AuthnRequest
|
||||
resp = self.redirect_to_auth(_cli, entity_id, came_from, vorg_name)
|
||||
return resp(self.environ, self.start_response)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def not_found(environ, start_response):
|
||||
"""Called if no URL matches."""
|
||||
resp = NotFound('Not Found')
|
||||
return resp(environ, start_response)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def main(environ, start_response, _sp):
|
||||
_sso = SSO(_sp, environ, start_response, cache=CACHE, **ARGS)
|
||||
return _sso.do()
|
||||
|
||||
|
||||
#noinspection PyUnusedLocal
|
||||
def verify_login_cookie(environ, start_response, _sp):
|
||||
_sso = SSO(_sp, environ, start_response, cache=CACHE, **ARGS)
|
||||
return _sso.do()
|
||||
|
||||
|
||||
def disco(environ, start_response, _sp):
|
||||
query = parse_qs(environ["QUERY_STRING"])
|
||||
entity_id = query["entityID"][0]
|
||||
_sid = query["sid"][0]
|
||||
came_from = CACHE.outstanding_queries[_sid]
|
||||
_sso = SSO(_sp, environ, start_response, cache=CACHE, **ARGS)
|
||||
resp = _sso.redirect_to_auth(_sso.sp, entity_id, came_from)
|
||||
|
||||
# Add cookie
|
||||
kaka = make_cookie("ve_disco", entity_id, "SEED_SAW")
|
||||
resp.headers.append(kaka)
|
||||
return resp(environ, start_response)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# map urls to functions
|
||||
urls = [
|
||||
# Hmm, place holder, NOT used
|
||||
('place', ("holder", None)),
|
||||
(r'^$', main),
|
||||
(r'^login', verify_login_cookie),
|
||||
(r'^disco', disco)
|
||||
]
|
||||
|
||||
|
||||
def add_urls():
|
||||
base = "acs"
|
||||
|
||||
urls.append(("%s/post$" % base, (ACS, "post", SP)))
|
||||
urls.append(("%s/post/(.*)$" % base, (ACS, "post", SP)))
|
||||
urls.append(("%s/redirect$" % base, (ACS, "redirect", SP)))
|
||||
urls.append(("%s/redirect/(.*)$" % base, (ACS, "redirect", SP)))
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def application(environ, start_response):
|
||||
"""
|
||||
The main WSGI application. Dispatch the current request to
|
||||
the functions from above.
|
||||
|
||||
If nothing matches call the `not_found` function.
|
||||
|
||||
:param environ: The HTTP application environment
|
||||
:param start_response: The application to run when the handling of the
|
||||
request is done
|
||||
:return: The response as a list of lines
|
||||
"""
|
||||
path = environ.get('PATH_INFO', '').lstrip('/')
|
||||
logger.debug("<application> PATH: '%s'" % path)
|
||||
|
||||
logger.debug("Finding callback to run")
|
||||
try:
|
||||
for regex, spec in urls:
|
||||
match = re.search(regex, path)
|
||||
if match is not None:
|
||||
if isinstance(spec, tuple):
|
||||
callback, func_name, _sp = spec
|
||||
cls = callback(_sp, environ, start_response, cache=CACHE)
|
||||
func = getattr(cls, func_name)
|
||||
return func()
|
||||
else:
|
||||
return spec(environ, start_response, SP)
|
||||
if re.match(".*static/.*", path):
|
||||
return handle_static(environ, start_response, path)
|
||||
return not_found(environ, start_response)
|
||||
except StatusError, err:
|
||||
logging.error("StatusError: %s" % err)
|
||||
resp = BadRequest("%s" % err)
|
||||
return resp(environ, start_response)
|
||||
except Exception, err:
|
||||
#_err = exception_trace("RUN", err)
|
||||
#logging.error(exception_trace("RUN", _err))
|
||||
print >> sys.stderr, err
|
||||
resp = ServiceError("%s" % err)
|
||||
return resp(environ, start_response)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
PORT = service_conf.PORT
|
||||
# ------- HTTPS -------
|
||||
# These should point to relevant files
|
||||
SERVER_CERT = service_conf.SERVER_CERT
|
||||
SERVER_KEY = service_conf.SERVER_KEY
|
||||
# This is of course the certificate chain for the CA that signed
|
||||
# you cert and all the way up to the top
|
||||
CERT_CHAIN = service_conf.CERT_CHAIN
|
||||
|
||||
if __name__ == '__main__':
|
||||
from cherrypy import wsgiserver
|
||||
from cherrypy.wsgiserver import ssl_pyopenssl
|
||||
|
||||
_parser = argparse.ArgumentParser()
|
||||
_parser.add_argument('-d', dest='debug', action='store_true',
|
||||
help="Print debug information")
|
||||
_parser.add_argument('-D', dest='discosrv',
|
||||
help="Which disco server to use")
|
||||
_parser.add_argument('-s', dest='seed',
|
||||
help="Cookie seed")
|
||||
_parser.add_argument('-W', dest='wayf', action='store_true',
|
||||
help="Which WAYF url to use")
|
||||
_parser.add_argument("config", help="SAML client config")
|
||||
|
||||
ARGS = {}
|
||||
_args = _parser.parse_args()
|
||||
if _args.discosrv:
|
||||
ARGS["discosrv"] = _args.discosrv
|
||||
if _args.wayf:
|
||||
ARGS["wayf"] = _args.wayf
|
||||
|
||||
CACHE = Cache()
|
||||
CNFBASE = _args.config
|
||||
if _args.seed:
|
||||
SEED = _args.seed
|
||||
else:
|
||||
SEED = "SnabbtInspel"
|
||||
|
||||
SP = Saml2Client(config_file="%s" % CNFBASE)
|
||||
|
||||
POLICY = service_conf.POLICY
|
||||
|
||||
add_urls()
|
||||
|
||||
SRV = wsgiserver.CherryPyWSGIServer(('0.0.0.0', PORT), application)
|
||||
|
||||
if service_conf.HTTPS:
|
||||
SRV.ssl_adapter = ssl_pyopenssl.pyOpenSSLAdapter(SERVER_CERT,
|
||||
SERVER_KEY, CERT_CHAIN)
|
||||
logger.info("Server starting")
|
||||
print "SP listening on port: %s" % PORT
|
||||
try:
|
||||
SRV.start()
|
||||
except KeyboardInterrupt:
|
||||
SRV.stop()
|
34
example/sp-wsgi/sp.xml
Normal file
34
example/sp-wsgi/sp.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:ns2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ns4="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" entityID="http://localhost:8087/sp.xml"><ns0:Extensions><ns1:EntityAttributes><ns2:Attribute Name="http://macedir.org/entity-category"><ns2:AttributeValue xsi:type="xs:string">http://www.geant.net/uri/dataprotection-code-of-conduct/v1</ns2:AttributeValue></ns2:Attribute></ns1:EntityAttributes></ns0:Extensions><ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:KeyDescriptor use="encryption"><ns4:KeyInfo><ns4:X509Data><ns4:X509Certificate>MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||
</ns4:X509Certificate></ns4:X509Data></ns4:KeyInfo></ns0:KeyDescriptor><ns0:KeyDescriptor use="signing"><ns4:KeyInfo><ns4:X509Data><ns4:X509Certificate>MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV
|
||||
BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx
|
||||
EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz
|
||||
MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l
|
||||
YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw
|
||||
DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7
|
||||
bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC
|
||||
FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR
|
||||
mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW
|
||||
BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9
|
||||
o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW
|
||||
BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE
|
||||
AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
|
||||
BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO
|
||||
zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN
|
||||
+vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI=
|
||||
</ns4:X509Certificate></ns4:X509Data></ns4:KeyInfo></ns0:KeyDescriptor><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8087/acs/redirect" index="1" /><ns0:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8087/acs/post" index="2" /></ns0:SPSSODescriptor></ns0:EntityDescriptor>
|
Reference in New Issue
Block a user