Added example SP not using repoze.who .
This commit is contained in:
@@ -97,7 +97,7 @@ CONFIG = {
|
|||||||
"key_file": full_path("pki/mykey.pem"),
|
"key_file": full_path("pki/mykey.pem"),
|
||||||
"cert_file": full_path("pki/mycert.pem"),
|
"cert_file": full_path("pki/mycert.pem"),
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"local": [full_path("../sp/sp.xml")],
|
"local": [full_path("../sp-wsgi/sp.xml")],
|
||||||
},
|
},
|
||||||
"organization": {
|
"organization": {
|
||||||
"display_name": "Rolands Identiteter",
|
"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