This commit is contained in:
Fredrik Thulin
2013-05-29 08:34:56 +02:00
17 changed files with 118680 additions and 10441 deletions

View File

@@ -82,11 +82,17 @@ def delete_cookie(environ, name):
#noinspection PyUnusedLocal
def whoami(environ, start_response, user):
identity = environ["repoze.who.identity"]["user"]
if not identity:
nameid = environ["repoze.who.identity"]["login"]
ava = environ["repoze.who.identity"]["user"]
if not nameid:
return not_authn(environ, start_response)
response = ["<h2>Your identity are supposed to be</h2>"]
response.extend(dict_to_table(identity))
if ava:
response = ["<h2>Your identity are supposed to be</h2>"]
response.extend(dict_to_table(ava))
else:
response = [
"<h2>The system did not return any information about you</h2>"]
response.extend("<a href='logout'>Logout</a>")
resp = Response(response)
return resp(environ, start_response)

View File

@@ -9,7 +9,7 @@ CONFIG = {
"description": "My SP",
"service": {
"sp": {
"name" : "Rolands SP",
"name": "Rolands SP",
"endpoints": {
"assertion_consumer_service": [BASE],
"single_logout_service": [(BASE + "/slo",
@@ -38,7 +38,7 @@ CONFIG = {
"contact_type": "technical",
},
],
#"xmlsec_binary":"/usr/local/bin/xmlsec1",
#"xmlsec_binary":"/opt/local/bin/xmlsec1",
"name_form": NAME_FORMAT_URI,
"logger": {
"rotating": {

View File

@@ -63,7 +63,7 @@ if sys.version_info < (2, 7):
setup(
name='pysaml2',
version='1.0.1',
version='1.0.2',
description='Python implementation of SAML Version 2 to be used in a WSGI environment',
# long_description = read("README"),
author='Roland Hedberg',

View File

@@ -3,8 +3,6 @@ from saml2.samlp import RequestedAuthnContext
__author__ = 'rolandh'
import hashlib
from saml2 import extension_elements_to_elements
UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified"
@@ -36,6 +34,7 @@ CMP_TYPE = ['exact', 'minimum', 'maximum', 'better']
class AuthnBroker(object):
def __init__(self):
self.db = {"info": {}, "key": {}}
self.next = 0
def exact(self, a, b):
return a == b
@@ -81,17 +80,8 @@ class AuthnBroker(object):
else:
raise NotImplementedError()
m = hashlib.md5()
for attr in ["method", "level", "authn_auth"]:
m.update(str(_info[attr]))
try:
_txt = "%s" % _info["decl"]
except KeyError:
pass
else:
m.update(_txt)
_ref = m.hexdigest()
self.next += 1
_ref = str(self.next)
self.db["info"][_ref] = _info
try:
self.db["key"][key].append(_ref)

View File

@@ -694,7 +694,7 @@ class Base(Entity):
:param url: The URL of the discovery service
:param entity_id: The unique identifier of the service provider
:param return_url: The discovery service MUST redirect the user agent
:param return: The discovery service MUST redirect the user agent
to this location in response to this request
:param policy: A parameter name used to indicate the desired behavior
controlling the processing of the discovery service
@@ -707,12 +707,20 @@ class Base(Entity):
:return: A URL
"""
args = {"entityID": entity_id}
for key in ["return_url", "policy", "returnIDParam"]:
for key in ["policy", "returnIDParam"]:
try:
args[key] = kwargs[key]
except KeyError:
pass
try:
args["return"] = kwargs["return_url"]
except KeyError:
try:
args["return"] = kwargs["return"]
except KeyError:
pass
if "isPassive" in kwargs:
if kwargs["isPassive"]:
args["isPassive"] = "true"

View File

@@ -23,15 +23,15 @@ class DiscoveryServer(Entity):
# verify
for key in ["isPassive", "return_url", "returnIDParam", "policy"]:
for key in ["isPassive", "return", "returnIDParam", "policy"]:
try:
assert len(dsr[key]) == 1
dsr[key] = dsr[key][0]
except KeyError:
pass
if "return_url" in dsr:
part = urlparse(dsr["return_url"])
if "return" in dsr:
part = urlparse(dsr["return"])
if part.query:
qp = parse_qs(part.query)
if "returnIDParam" in dsr:
@@ -40,7 +40,7 @@ class DiscoveryServer(Entity):
assert "entityID" not in qp.keys()
else:
# If metadata not used this is mandatory
raise VerificationError("Missing mandatory parameter 'return_url'")
raise VerificationError("Missing mandatory parameter 'return'")
if "policy" not in dsr:
dsr["policy"] = IDPDISC_POLICY
@@ -62,9 +62,12 @@ class DiscoveryServer(Entity):
# -------------------------------------------------------------------------
def create_discovery_service_response(self, return_url,
def create_discovery_service_response(self, return_url=None,
returnIDParam="entityID",
entity_id=None):
entity_id=None, **kwargs):
if return_url is None:
return_url = kwargs["return"]
if entity_id:
qp = urlencode({returnIDParam: entity_id})

View File

@@ -10,8 +10,10 @@ from saml2 import md
NAMESPACE = 'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol'
BINDING_DISCO = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
class DiscoveryResponse(md.IndexedEndpointType_):
"""The urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:DiscoveryResponse element """
"""The urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:
DiscoveryResponse element """
c_tag = 'DiscoveryResponse'
c_namespace = NAMESPACE
@@ -20,6 +22,7 @@ class DiscoveryResponse(md.IndexedEndpointType_):
c_child_order = md.IndexedEndpointType_.c_child_order[:]
c_cardinality = md.IndexedEndpointType_.c_cardinality.copy()
def discovery_response_from_string(xml_string):
return saml2.create_class_from_xml_string(DiscoveryResponse, xml_string)

View File

@@ -88,8 +88,15 @@ class SeeOther(Response):
_status = '303 See Other'
def __call__(self, environ, start_response, **kwargs):
location = self.message
self.headers.append(('location', location))
location = ""
if self.message:
location = self.message
self.headers.append(('location', location))
else:
for param, item in self.headers:
if param == "location":
location = item
break
start_response(self.status, self.headers)
return self.response((location, location, location))
@@ -147,16 +154,19 @@ def extract(environ, empty=False, err=False):
return formdata
def geturl(environ, query=True, path=True):
def geturl(environ, query=True, path=True, use_server_name=False):
"""Rebuilds a request URL (from PEP 333).
You may want to chose to use the environment variables
server_name and server_port instead of http_host in some case.
The parameter use_server_name allows you to chose.
:param query: Is QUERY_STRING included in URI (default: True)
:param path: Is path included in URI (default: True)
:param use_server_name: If SERVER_NAME/_HOST should be used instead of
HTTP_HOST
"""
url = [environ['wsgi.url_scheme'] + '://']
if environ.get('HTTP_HOST'):
url.append(environ['HTTP_HOST'])
else:
if use_server_name:
url.append(environ['SERVER_NAME'])
if environ['wsgi.url_scheme'] == 'https':
if environ['SERVER_PORT'] != '443':
@@ -164,6 +174,8 @@ def geturl(environ, query=True, path=True):
else:
if environ['SERVER_PORT'] != '80':
url.append(':' + environ['SERVER_PORT'])
else:
url.append(environ['HTTP_HOST'])
if path:
url.append(getpath(environ))
if query and environ.get('QUERY_STRING'):

View File

@@ -108,7 +108,7 @@ class MetaData(object):
return self.entity.keys()
def values(self):
return self.entity.keys()
return self.entity.values()
def __contains__(self, item):
return item in self.entity
@@ -399,7 +399,8 @@ class MetaDataMD(MetaData):
self.filename = filename
def load(self):
self.entity = eval(open(self.filename).read())
for key, item in json.loads(open(self.filename).read()):
self.entity[key] = item
class MetadataStore(object):

View File

@@ -1,23 +1,24 @@
from saml2.extension.idpdisc import BINDING_DISCO
from pathutils import full_path
from pathutils import xmlsec_path
BASE = "http://localhost:8088"
CONFIG = {
"entityid" : "%s/disco.xml" % BASE,
"name" : "Rolands Discoserver",
"entityid": "%s/disco.xml" % BASE,
"name": "Rolands Discoserver",
"service": {
"ds": {
"endpoints" : {
"endpoints": {
"disco_service": [
("%s/disco" % BASE, BINDING_DISCO),
]
},
},
},
"debug" : 1,
"xmlsec_binary" : None,
"debug": 1,
"xmlsec_binary": xmlsec_path,
"metadata": {
"local": [full_path("servera.xml")],
},

View File

@@ -8,6 +8,7 @@ from saml2.saml import NAMEID_FORMAT_TRANSIENT
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from pathutils import full_path
from pathutils import xmlsec_path
BASE = "http://lingon.catalogix.se:8087"
@@ -49,7 +50,7 @@ CONFIG = {
"key_file": full_path("test.key"),
"cert_file": full_path("test.pem"),
"ca_certs": full_path("cacerts.txt"),
"xmlsec_binary": None,
"xmlsec_binary": xmlsec_path,
"metadata": {
"local": [full_path("idp_all.xml"), full_path("vo_metadata.xml")],
},

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@ from saml2.mdstore import MetadataStore
from saml2.mdstore import destinations
from saml2.mdstore import name
from saml2 import md
from saml2 import md, sigver
from saml2 import BINDING_SOAP
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST
@@ -69,6 +69,9 @@ METADATACONF = {
},
"6": {
"local": [full_path("metasp.xml")]
},
"8": {
"mdfile": [full_path("swamid.md")]
}
}
@@ -236,5 +239,15 @@ def test_sp_metadata():
assert _eq([n["friendly_name"] for n in req["required"]],
['surName', 'givenName', 'mail'])
def test_metadata_file():
sec_config.xmlsec_binary = sigver.get_xmlsec_binary(["/opt/local/bin"])
mds = MetadataStore(ONTS.values(), ATTRCONV, sec_config,
disable_ssl_certificate_validation=True)
mds.imp(METADATACONF["8"])
print len(mds.keys())
assert len(mds.keys()) == 560
if __name__ == "__main__":
test_swami_1()
test_metadata_file()

View File

@@ -5,43 +5,51 @@ from pathutils import dotname
__author__ = 'rolandh'
def _eq(l1,l2):
def _eq(l1, l2):
return set(l1) == set(l2)
def test_verify():
ds = DiscoveryServer(config_file=dotname("disco_conf"))
assert ds
assert ds.verify_sp_in_metadata("urn:mace:example.com:saml:roland:sp")
def test_construct_0():
sp = Saml2Client(config_file=dotname("servera_conf"))
url = sp.create_discovery_service_request("http://example.com/saml/disco",
"https://example.com/saml/sp.xml")
assert url == "http://example.com/saml/disco?entityID=https%3A%2F%2Fexample.com%2Fsaml%2Fsp.xml"
assert url == "http://example.com/saml/disco?entityID=https%3A%2F%2Fexample.com%2Fsaml%2Fsp.xml"
def test_construct_1():
sp = Saml2Client(config_file=dotname("servera_conf"))
url = sp.create_discovery_service_request("http://example.com/saml/disco",
"https://example.com/saml/sp.xml")
assert url == "http://example.com/saml/disco?entityID=https%3A%2F%2Fexample.com%2Fsaml%2Fsp.xml"
assert url == "http://example.com/saml/disco?entityID=https%3A%2F%2Fexample.com%2Fsaml%2Fsp.xml"
def test_construct_deconstruct_request():
sp = Saml2Client(config_file=dotname("servera_conf"))
url = sp.create_discovery_service_request("http://example.com/saml/disco",
"https://example.com/saml/sp.xml",
is_passive=True,
returnIDParam="foo",
return_url="https://example.com/saml/sp/disc")
url = sp.create_discovery_service_request(
"http://example.com/saml/disco",
"https://example.com/saml/sp.xml",
is_passive=True,
returnIDParam="foo",
return_url="https://example.com/saml/sp/disc")
print url
ds = DiscoveryServer(config_file=dotname("disco_conf"))
dsr = ds.parse_discovery_service_request(url)
# policy is added by the parsing and verifying method
assert _eq(dsr.keys(),["return_url", "entityID", "returnIDParam",
"isPassive", "policy"])
assert _eq(dsr.keys(), ["return", "entityID", "returnIDParam",
"isPassive", "policy"])
def test_construct_deconstruct_response():
sp = Saml2Client(config_file=dotname("servera_conf"))
@@ -52,10 +60,14 @@ def test_construct_deconstruct_response():
return_url="https://example.com/saml/sp/disc")
ds = DiscoveryServer(config_file=dotname("disco_conf"))
dsr = ds.parse_discovery_service_request(url)
args = dict([(key, dsr[key]) for key in ["returnIDParam", "return_url"]])
args = dict([(key, dsr[key]) for key in ["returnIDParam", "return"]])
url = ds.create_discovery_service_response(
entity_id="https://example.com/saml/idp.xml",
**args)
entity_id="https://example.com/saml/idp.xml",
**args)
idp_id = sp.parse_discovery_service_response(url, returnIDParam="foo")
assert idp_id == "https://example.com/saml/idp.xml"
if __name__ == "__main__":
test_construct_deconstruct_response()

View File

@@ -1,16 +1,22 @@
#!/usr/bin/env python
import sys
from saml2.httpbase import HTTPBase
from saml2 import saml
from saml2 import md
from saml2.extension import mdui
from saml2.extension import idpdisc
from saml2.attribute_converter import ac_factory
from saml2.extension import dri
from saml2.extension import idpdisc
from saml2.extension import mdattr
from saml2.extension import mdrpi
from saml2.extension import mdui
from saml2.extension import shibmd
from saml2.extension import ui
import xmldsig
import xmlenc
import argparse
from saml2.mdstore import MetaDataFile, MetaDataExtern
__author__ = 'rolandh'
@@ -20,45 +26,49 @@ A script that imports and verifies metadata and then dumps it in a basic
dictionary format.
"""
MDIMPORT = {
"swamid": {
"url": "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2",
"cert":"kalmar2.pem",
"type": "external"
},
"incommon": {
"file": "InCommon-metadata.xml",
"type": "local"
},
"test": {
"file": "mdtest.xml",
"type": "local"
}
}
ONTS = {
saml.NAMESPACE: saml,
mdui.NAMESPACE: mdui,
mdattr.NAMESPACE: mdattr,
mdrpi.NAMESPACE: mdrpi,
dri.NAMESPACE: dri,
ui.NAMESPACE: ui,
idpdisc.NAMESPACE: idpdisc,
md.NAMESPACE: md,
xmldsig.NAMESPACE: xmldsig,
xmlenc.NAMESPACE: xmlenc
xmlenc.NAMESPACE: xmlenc,
shibmd.NAMESPACE: shibmd
}
item = MDIMPORT[sys.argv[1]]
parser = argparse.ArgumentParser()
parser.add_argument('-t', dest='type')
parser.add_argument('-u', dest='url')
parser.add_argument('-c', dest='cert')
parser.add_argument('-a', dest='attrsmap')
parser.add_argument('-o', dest='output')
parser.add_argument(dest="item")
args = parser.parse_args()
metad = None
if item["type"] == "local":
metad = MetaDataFile(sys.argv[1], ONTS.values(), item["file"])
elif item["type"] == "external":
metad = MetaDataExtern(sys.argv[1], ONTS.values(),
item["url"], "/opt/local/bin/xmlsec1", item["cert"])
if args.type == "local":
metad = MetaDataFile(ONTS.values(), args.item, args.item)
elif args.type == "external":
ATTRCONV = ac_factory(args.attrsmap)
httpc = HTTPBase()
metad = MetaDataExtern(ONTS.values(), ATTRCONV, args.url,
"/opt/local/bin/xmlsec1",
args.cert, httpc)
if metad:
metad.load()
print metad.dumps()
txt = metad.dumps()
if args.output:
f = open(args.output, "w")
f.write(txt)
f.close()
else:
print txt

68
tools/mdexport_test.py Executable file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python
import sys
from saml2 import saml
from saml2 import md
from saml2.extension import mdui
from saml2.extension import idpdisc
from saml2.extension import dri
from saml2.extension import mdattr
from saml2.extension import ui
from saml2.extension import shibmd
import xmldsig
import xmlenc
from saml2.mdstore import MetaDataFile, MetaDataExtern
__author__ = 'rolandh'
"""
A script that imports and verifies metadata and then dumps it in a basic
dictionary format.
"""
ONTS = {
saml.NAMESPACE: saml,
mdui.NAMESPACE: mdui,
mdattr.NAMESPACE: mdattr,
dri.NAMESPACE: dri,
ui.NAMESPACE: ui,
idpdisc.NAMESPACE: idpdisc,
md.NAMESPACE: md,
xmldsig.NAMESPACE: xmldsig,
xmlenc.NAMESPACE: xmlenc,
shibmd.NAMESPACE: shibmd
}
MDIMPORT = {
"swamid": {
"url": "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2",
"cert": "kalmar2.pem",
"type": "external"
},
"incommon": {
"file": "InCommon-metadata.xml",
"type": "local"
},
"test": {
"file": "mdtest.xml",
"type": "local"
}
}
item = MDIMPORT[sys.argv[1]]
metad = None
if item["type"] == "local":
metad = MetaDataFile(sys.argv[1], ONTS.values(), item["file"])
elif item["type"] == "external":
metad = MetaDataExtern(sys.argv[1], ONTS.values(),
item["url"], "/opt/local/bin/xmlsec1", item["cert"])
if metad:
metad.load()
print metad.dumps()

View File

@@ -1,10 +1,11 @@
#!/usr/bin/env python
import sys
import time
from saml2.attribute_converter import ac_factory
from saml2.mdstore import MetaDataMD, MetaDataFile
__author__ = 'rolandh'
from saml2.mdie import from_dict
import xmldsig
import xmlenc
from saml2 import md
@@ -27,7 +28,20 @@ ONTS = {
xmldsig.NAMESPACE: xmldsig,
}
_dict = eval(open(sys.argv[1]).read())
res = from_dict(_dict, ONTS)
start = time.time()
for i in range(1, 10):
mdmd = MetaDataMD(ONTS, ac_factory("../tests/attributemaps"), "swamid2.md")
mdmd.load()
print res
_ = mdmd.keys()
print time.time() - start
start = time.time()
for i in range(1, 10):
mdf = MetaDataFile(ONTS.values(), ac_factory("../tests/attributemaps"),
"../tests/swamid-2.0.xml")
mdf.load()
_ = mdf.keys()
print time.time() - start