Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Andreas Richter
2014-04-16 10:39:02 -04:00
73 changed files with 2001 additions and 1397 deletions

4
TODO
View File

@@ -1,3 +1,3 @@
1. Write documentations. 1. Write documentation.
2. Write unittests for signature related utility methods. 2. Write unit tests for signature related utility methods.
3. Complete saml2 message class. 3. Complete saml2 message class.

View File

@@ -28,7 +28,7 @@ instance, the friendly name is used as the key.
Setup Setup
----- -----
I you look in the example/sp directory of the distribution you will see If you look in the example/sp directory of the distribution you will see
the necessary files: the necessary files:
application.py application.py
@@ -64,7 +64,7 @@ it line by line::
"service": ["sp"], "service": ["sp"],
Tells the software what type of services the software are suppost to Tells the software what type of services the software is supposed to
supply. It is used to check for the supply. It is used to check for the
completeness of the configuration and also when constructing metadata from completeness of the configuration and also when constructing metadata from
the configuration. More about that later. Allowed values are: "sp" the configuration. More about that later. Allowed values are: "sp"
@@ -119,13 +119,13 @@ building metadata. ::
#telephone_number #telephone_number
}] }]
Another piece of information that only is matters if you build and distribute Another piece of information that only matters if you build and distribute
metadata. metadata.
So, now to that part. In order to allow the IdP to talk to you you may have So, now to that part. In order to allow the IdP to talk to you you may have
to provide the one running the IdP with a metadata file. to provide the one running the IdP with a metadata file.
If you have a SP configuration file similar to the one I've walked you If you have a SP configuration file similar to the one I've walked you
through here, but with your information. You can make the metadata file through here, but with your information, you can make the metadata file
by running the make_metadata script you can find in the tools directory. by running the make_metadata script you can find in the tools directory.
Change directory to where you have the configuration file and do :: Change directory to where you have the configuration file and do ::
@@ -138,7 +138,7 @@ Repoze configuration
-------------------- --------------------
I'm not going through the INI file format here. You should read I'm not going through the INI file format here. You should read
`Middleware Responsibilities <http://static.repoze.org/whodocs/narr.html>`_ `Middleware Responsibilities <http://docs.repoze.org/who/2.0/middleware.html>`_
to get a good introduction to the concept. to get a good introduction to the concept.
The configuration of the pysaml2 part in the applications middleware are The configuration of the pysaml2 part in the applications middleware are
@@ -178,16 +178,16 @@ Which means that the plugin is used in all phases.
The application The application
--------------- ---------------
Is as said before extremly simple. The only thing that is connected to The app is, as said before, extremely simple. The only thing that is connected to
the PySaml2 configuration are at the bottom, namely where the server are. the PySaml2 configuration is at the bottom, namely where the server is.
You have to ascertain that this coincides with what is specified in the You have to ascertain that this coincides with what is specified in the
PySaml2 configuration. Apart from that there really are no thing in PySaml2 configuration. Apart from that there really is nothing in
application.py that demands that you use PySaml2 as middleware. If you application.py that demands that you use PySaml2 as middleware. If you
switched to using the LDAP or CAS plugins nothing would change in the switched to using the LDAP or CAS plugins nothing would change in the
application. In the application configuration yes! But not in the application. application. In the application configuration yes! But not in the application.
And that is really how it should be done. And that is really how it should be done.
There is one assumption and that is that the middleware plugin that gathers There is one assumption, and that is that the middleware plugin that gathers
information about the user places the extra information in as value on the information about the user places the extra information in as a value on the
"user" property in the dictionary found under the key "repoze.who.identity" "user" property in the dictionary found under the key "repoze.who.identity"
in the environment. in the environment.

View File

@@ -3,15 +3,15 @@
Configuration of pySAML2 entities Configuration of pySAML2 entities
================================= =================================
Whether you plan to run a pySAML2 Service Provider, Identity provider or an Whether you plan to run a pySAML2 Service Provider, Identity Provider or an
attribute authority you have to configure it. The format of the configuration attribute authority you have to configure it. The format of the configuration
file is the same disregarding which type of service you plan to run. file is the same regardless of which type of service you plan to run.
What differs is some of the directives. What differs are some of the directives.
Below you will find a list of all the used directives in alphabetic order. Below you will find a list of all the used directives in alphabetical order.
The configuration is written as a python module which contains a named The configuration is written as a python module which contains a named
dictionary ("CONFIG") that contains the configuration directives. dictionary ("CONFIG") that contains the configuration directives.
The basic structure of the configuration file is therefor like this:: The basic structure of the configuration file is therefore like this::
from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_REDIRECT
@@ -90,9 +90,9 @@ The attribute map module contains a MAP dictionary with three items. The
The *to* and *fro* sub-dictionaries then contain the mapping between the names. The *to* and *fro* sub-dictionaries then contain the mapping between the names.
As you see the format is again a python dictionary where the key is the As you see the format is again a python dictionary where the key is the
name to convert from and the value is the name to convert to. name to convert from, and the value is the name to convert to.
Since *to* in most cases are the inverse of the *fro* file, the Since *to* in most cases is the inverse of the *fro* file, the
software allowes you to only specify one of them and it will software allowes you to only specify one of them and it will
automatically create the other. automatically create the other.
@@ -111,7 +111,7 @@ contact_person
This is only used by *make_metadata.py* when it constructs the metadata for This is only used by *make_metadata.py* when it constructs the metadata for
the service described by the configuration file. the service described by the configuration file.
This is where you described who can be contacted if questions arises This is where you describe who can be contacted if questions arise
about the service or if support is needed. The possible types are according to about the service or if support is needed. The possible types are according to
the standard **technical**, **support**, **administrative**, **billing** the standard **technical**, **support**, **administrative**, **billing**
and **other**.:: and **other**.::
@@ -148,7 +148,7 @@ Format::
The globally unique identifier of the entity. The globally unique identifier of the entity.
.. note:: There is a recommendation that the entityid should point to a real .. note:: It is recommended that the entityid should point to a real
webpage where the metadata for the entity can be found. webpage where the metadata for the entity can be found.
key_file key_file
@@ -160,13 +160,13 @@ Format::
*key_file* is the name of a PEM formatted file that contains the private key *key_file* is the name of a PEM formatted file that contains the private key
of the service. This is presently used both to encrypt/sign assertions and as of the service. This is presently used both to encrypt/sign assertions and as
client key in a HTTPS session. the client key in an HTTPS session.
metadata metadata
^^^^^^^^ ^^^^^^^^
Contains a list of places where metadata can be found. This can be either Contains a list of places where metadata can be found. This can be either
a file accessible on the server the service runs on or somewhere on the net.:: a file accessible on the server the service runs on, or somewhere on the net.::
"metadata" : { "metadata" : {
"local": [ "local": [
@@ -180,8 +180,8 @@ a file accessible on the server the service runs on or somewhere on the net.::
}, },
The above configuration means that the service should read two local The above configuration means that the service should read two local
metadata files and on top of that load one from the net. To verify the metadata files, and on top of that load one from the net. To verify the
authenticity of the file downloaded from the net the local copy of the authenticity of the file downloaded from the net, the local copy of the
public key should be used. public key should be used.
This public key must be acquired by some out-of-band method. This public key must be acquired by some out-of-band method.
@@ -205,7 +205,7 @@ Where you describe the organization responsible for the service.::
service service
^^^^^^^ ^^^^^^^
Which services the server will provide, those are combinations of "idp","sp" Which services the server will provide; those are combinations of "idp", "sp"
and "aa". and "aa".
So if a server is a Service Provider (SP) then the configuration So if a server is a Service Provider (SP) then the configuration
could look something like this:: could look something like this::
@@ -228,13 +228,13 @@ could look something like this::
There are two options common to all services: 'name' and 'endpoints'. There are two options common to all services: 'name' and 'endpoints'.
The remaining options are specific to one or the other of the service types. The remaining options are specific to one or the other of the service types.
Which one is specified along side the name of the option Which one is specified along side the name of the option.
timeslack timeslack
^^^^^^^^^ ^^^^^^^^^
If your computer and another computer that you are communicating with are not If your computer and another computer that you are communicating with are not
in synch regarding the computer clock. Then you here can state how big a in synch regarding the computer clock, then here you can state how big a
difference you are prepared to accept. difference you are prepared to accept.
.. note:: This will indiscriminately effect all time comparisons. .. note:: This will indiscriminately effect all time comparisons.
@@ -275,7 +275,7 @@ policy
If the server is an IdP and/or an AA then there might be reasons to do things If the server is an IdP and/or an AA then there might be reasons to do things
differently depending on who is asking; this is where that is specified. differently depending on who is asking; this is where that is specified.
The keys are 'default' and SP entity identifiers, default is used whenever The keys are 'default' and SP entity identifiers. Default is used whenever
there is no entry for a specific SP. The reasoning is also that if there is there is no entry for a specific SP. The reasoning is also that if there is
no default and only SP entity identifiers as keys, then the server will only no default and only SP entity identifiers as keys, then the server will only
except connections from the specified SPs. except connections from the specified SPs.
@@ -301,12 +301,12 @@ An example might be::
} }
*lifetime* *lifetime*
is the maximum amount of time before the information should be This is the maximum amount of time before the information should be
regarded as stale. In an Assertion this is represented in the NotOnOrAfter regarded as stale. In an Assertion this is represented in the NotOnOrAfter
attribute. attribute.
*attribute_restrictions* *attribute_restrictions*
By default there is no restrictions as to which attributes should be By default there is no restrictions as to which attributes should be
return. Instead all the attributes and values that is gathered by the return. Instead all the attributes and values that are gathered by the
database backends will be returned if nothing else is stated. database backends will be returned if nothing else is stated.
In the example above the SP with the entity identifier In the example above the SP with the entity identifier
"urn:mace:umu.se:saml:roland:sp" "urn:mace:umu.se:saml:roland:sp"
@@ -332,7 +332,7 @@ regular expressions.::
} }
} }
Here only mail addresses that ends with ".umu.se" will be returned. Here only mail addresses that end with ".umu.se" will be returned.
sp sp
^^ ^^
@@ -345,7 +345,7 @@ authn_requests_signed
Indicates if the Authentication Requests sent by this SP should be signed Indicates if the Authentication Requests sent by this SP should be signed
by default. This can be overriden by application code for a specific call. by default. This can be overriden by application code for a specific call.
This set the AuthnRequestsSigned attribute of the SPSSODescriptor node. This sets the AuthnRequestsSigned attribute of the SPSSODescriptor node
of the metadata so the IdP will know this SP preference. of the metadata so the IdP will know this SP preference.
Valid values are "true" or "false". Default value is "false". Valid values are "true" or "false". Default value is "false".
@@ -362,8 +362,8 @@ Example::
idp idp
""" """
Defines the set of IdPs that this SP is allowed to use. If not all the IdPs in Defines the set of IdPs that this SP is allowed to use; if unset, all listed
the metadata is allowed, then the value is expected to be a list with entity IdPs may be used. If set, then the value is expected to be a list with entity
identifiers for the allowed IdPs. identifiers for the allowed IdPs.
A typical configuration, when the allowed set of IdPs are limited, would look A typical configuration, when the allowed set of IdPs are limited, would look
something like this:: something like this::
@@ -376,8 +376,6 @@ something like this::
In this case the SP has only one IdP it can use. In this case the SP has only one IdP it can use.
If all IdPs present in the metadata loaded this directive must be left out.
optional_attributes optional_attributes
""""""""""""""""""" """""""""""""""""""
@@ -415,7 +413,7 @@ want_assertions_signed
"""""""""""""""""""""" """"""""""""""""""""""
Indicates if this SP wants the IdP to send the assertions signed. This Indicates if this SP wants the IdP to send the assertions signed. This
set the WantAssertionsSigned attribute of the SPSSODescriptor node. sets the WantAssertionsSigned attribute of the SPSSODescriptor node
of the metadata so the IdP will know this SP preference. of the metadata so the IdP will know this SP preference.
Valid values are "true" or "false". Default value is "true". Valid values are "true" or "false". Default value is "true".
@@ -440,7 +438,7 @@ endpoints
""""""""" """""""""
Where the endpoints for the services provided are. Where the endpoints for the services provided are.
This directive has as value a dictionary with one of the following keys: This directive has as value a dictionary with one or more of the following keys:
* artifact_resolution_service (aa, idp and sp) * artifact_resolution_service (aa, idp and sp)
* assertion_consumer_service (sp) * assertion_consumer_service (sp)
@@ -474,7 +472,7 @@ Indicates if this entity will sign the Logout Requests originated from it.
This can be overriden by application code for a specific call. This can be overriden by application code for a specific call.
Valid values are "true" or "false". Default value is "false" Valid values are "true" or "false". Default value is "false".
Example:: Example::
@@ -491,7 +489,7 @@ The name of a database where the map between a local identifier and
a distributed identifier is kept. By default this is a shelve database. a distributed identifier is kept. By default this is a shelve database.
So if you just specify name, then a shelve database with that name So if you just specify name, then a shelve database with that name
is created. On the other hand if you specify a tuple then the first is created. On the other hand if you specify a tuple then the first
element in the tuple specifise which type of database you want to use element in the tuple specifies which type of database you want to use
and the second element is the address of the database. and the second element is the address of the database.
Example:: Example::
@@ -519,7 +517,7 @@ Gives information about common identifiers for virtual_organizations::
}, },
Keys in this dictionary are the identifiers for the virtual organizations. Keys in this dictionary are the identifiers for the virtual organizations.
The arguments per organization is 'nameid_format' and 'common_identifier'. The arguments per organization are 'nameid_format' and 'common_identifier'.
Useful if all the IdPs and AAs that are involved in a virtual organization Useful if all the IdPs and AAs that are involved in a virtual organization
have common attribute values for users that are part of the VO. have common attribute values for users that are part of the VO.
@@ -562,8 +560,8 @@ We start with a simple but fairly complete Service provider configuration::
} }
This is the typical setup for a SP. This is the typical setup for a SP.
A metadata file to load is *always* needed, but it can of course be A metadata file to load is *always* needed, but it can of course
containing anything from 1 up to many entity descriptions. contain anything from 1 up to many entity descriptions.
------ ------

View File

@@ -12,10 +12,10 @@ If you have not done it yet, read the :ref:`install`
Well, now you have it installed and you want to do something. Well, now you have it installed and you want to do something.
And I'm sorry to tell you this; but there isn't really a lot you can do with And I'm sorry to tell you this; but there isn't really a lot you can do with
this code on it's own. this code on its own.
Sure you can send a AuthenticationRequest to an IdentityProvider or a Sure you can send a AuthenticationRequest to an IdentityProvider or a
AttributeQuery to an AttributeAuthority but in order to get what they AttributeQuery to an AttributeAuthority, but in order to get what they
return you have to sit behind a Web server. Well that is not really true since return you have to sit behind a Web server. Well that is not really true since
the AttributeQuery would be over SOAP and you would get the result over the the AttributeQuery would be over SOAP and you would get the result over the
connection you have to the AttributeAuthority. connection you have to the AttributeAuthority.
@@ -29,7 +29,7 @@ But it can be used in a non-WSGI environment too.
So you will find descriptions of both cases here. So you will find descriptions of both cases here.
The configuration is the same disregarding whether you are using PySAML2 in a The configuration is the same regardless of whether you are using PySAML2 in a
WSGI or non-WSGI environment. WSGI or non-WSGI environment.
.. toctree:: .. toctree::

View File

@@ -13,7 +13,7 @@ pysaml2
PySAML2 is a pure python implementation of SAML2. It contains all necessary pieces for building a SAML2 service PySAML2 is a pure python implementation of SAML2. It contains all necessary pieces for building a SAML2 service
provider or an identity provider. The distribution contains examples of both. Originally written to work in a WSGI provider or an identity provider. The distribution contains examples of both. Originally written to work in a WSGI
environment there are extensions that allow you to use it with other frameworks. environment, there are extensions that allow you to use it with other frameworks.
Contents: Contents:

View File

@@ -53,5 +53,5 @@ The tests are based on the pypy test environment, so::
py.test py.test
is what you should use. If you don't have py.test, get it it's part of pypy! is what you should use. If you don't have py.test, get it it's part of pypy!
It's really good ! It's really good!

View File

@@ -1,30 +1,40 @@
This is a very simple setup just to check that all your gear are in order. This is a very simple setup just to check that all your gear are in order.
The setup consists of one IdP and one SP. The setup consists of one IdP and one SP, in idp2/ and sp-wsgi/ respectively.
The IdP authenticates users by using a htpasswd plugin and gets the identity information
from the ini-plugin.
All this is in the idp/who.ini configuration file, the file used for authentication To run the setup do:
is idp/passwd and the ini file is idp/idp_user.ini.
The passwords in passwd in clear text: ./all.sh start
roland:friend and then use your favourite webbrowser to look at "http://localhost:8087/"
ozzie:two
derek:three To shut it down do:
ryan:four
ischiro:five ./all.sh stop
The IdP authenticates users using a dictionary built in to idp2/idp.py;
look for the dictionary called PASSWD inside that file.
Other metadata about the accounts (names, email addresses, etc) are
stored in idp2/idp_user.py. (Note, not all accounts have all such data
defined.)
The username:password pairs in PASSWD:
haho0032:qwerty
roland:dianakra
babs:howes
upper:crust
The SP doesn't do anything but show you the information that the IdP sent. The SP doesn't do anything but show you the information that the IdP sent.
Note, the listeners are all configured to bind to localhost (127.0.0.1) only.
If you want to be able to connect to them externally, grep "HOST = '127.0.0.1'"
example/*/*.py and replace 127.0.0.1 with 0.0.0.0 or a specific IP.
To make it easy, for me :-), both the IdP and the SP uses the same keys. To make it easy, for me :-), both the IdP and the SP uses the same keys.
To generate new keys, run create_key.sh and follow its instructions.
To run the setup do There are alternate IdP and SP configs in idp2_repoze/ and sp-repoze/ that
are still in flux; do not use them unless you know what you are doing.
./all.sh start
and then use your favourit webbrowser to look at "http://localhost:8087/whoami"
./all stop
will of course stop your IdP and SP.

View File

@@ -2,19 +2,19 @@
startme() { startme() {
cd sp-wsgi cd sp-wsgi
if [ ! -f conf.py ] ; then if [ ! -f sp_conf.py ] ; then
cp conf.py.example conf.py cp sp_conf.py.example sp_conf.py
fi fi
../../tools/make_metadata.py conf > sp.xml ../../tools/make_metadata.py sp_conf > sp.xml
cd ../idp2 cd ../idp2
if [ ! -f idp_conf.py ] ; then if [ ! -f idp_conf.py ] ; then
cp idp_conf.py.example conf.py cp idp_conf.py.example idp_conf.py
fi fi
../../tools/make_metadata.py idp_conf > idp.xml ../../tools/make_metadata.py idp_conf > idp.xml
cd ../sp-wsgi cd ../sp-wsgi
./sp.py conf & ./sp.py sp_conf &
cd ../idp2 cd ../idp2
./idp.py idp_conf & ./idp.py idp_conf &

View File

@@ -1,5 +1,25 @@
openssl genrsa -des3 -out server.key 1024 #!/bin/bash
cat <<EOF
Generating a new test key and certificate. To change the defaults offered
by openssl, edit your openssl.cnf, such as /etc/ssl/openssl.cnf
EOF
openssl genrsa -out server.key 1024
chmod 600 server.key
openssl req -new -key server.key -out server.csr openssl req -new -key server.key -out server.csr
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
cat <<EOH
Now to enable these new keys, do:
cp server.key idp2/pki/mykey.pem
cp server.crt idp2/pki/mycert.pem
cp server.key sp-wsgi/pki/mykey.pem
cp server.crt sp-wsgi/pki/mycert.pem
EOH

View File

@@ -871,7 +871,7 @@ def application(environ, start_response):
captures in the WSGI environment as `myapp.url_args` so that captures in the WSGI environment as `myapp.url_args` so that
the functions from above can access the url placeholders. the functions from above can access the url placeholders.
If nothing matches call the `not_found` function. If nothing matches, call the `not_found` function.
:param environ: The HTTP application environment :param environ: The HTTP application environment
:param start_response: The application to run when the handling of the :param start_response: The application to run when the handling of the
@@ -976,10 +976,11 @@ if __name__ == '__main__':
module_directory=_rot + 'modules', module_directory=_rot + 'modules',
input_encoding='utf-8', output_encoding='utf-8') input_encoding='utf-8', output_encoding='utf-8')
HOST = '127.0.0.1'
PORT = 8088 PORT = 8088
SRV = make_server('', PORT, application) SRV = make_server(HOST, PORT, application)
print "IdP listening on port: %s" % PORT print "IdP listening on %s:%s" % (HOST, PORT)
SRV.serve_forever() SRV.serve_forever()
else: else:
_rot = args.mako_root _rot = args.mako_root

View File

@@ -116,7 +116,7 @@ CONFIG = {
"email_address": "support@example.com" "email_address": "support@example.com"
}, },
], ],
# This database holds the map between a subjects local identifier and # This database holds the map between a subject's local identifier and
# the identifier returned to a SP # the identifier returned to a SP
"xmlsec_binary": xmlsec_path, "xmlsec_binary": xmlsec_path,
#"attribute_map_dir": "../attributemaps", #"attribute_map_dir": "../attributemaps",

View File

@@ -869,10 +869,10 @@ def application(environ, start_response):
""" """
The main WSGI application. Dispatch the current request to The main WSGI application. Dispatch the current request to
the functions from above and store the regular expression the functions from above and store the regular expression
captures in the WSGI environment as `myapp.url_args` so that captures in the WSGI environment as `myapp.url_args` so that
the functions from above can access the url placeholders. the functions from above can access the url placeholders.
If nothing matches call the `not_found` function. If nothing matches, call the `not_found` function.
:param environ: The HTTP application environment :param environ: The HTTP application environment
:param start_response: The application to run when the handling of the :param start_response: The application to run when the handling of the
@@ -977,10 +977,11 @@ if __name__ == '__main__':
module_directory=_rot + 'modules', module_directory=_rot + 'modules',
input_encoding='utf-8', output_encoding='utf-8') input_encoding='utf-8', output_encoding='utf-8')
HOST = '127.0.0.1'
PORT = 8088 PORT = 8088
SRV = make_server('', PORT, application) SRV = make_server(HOST, PORT, application)
print "IdP listening on port: %s" % PORT print "IdP listening on %s:%s" % (HOST, PORT)
SRV.serve_forever() SRV.serve_forever()
else: else:
_rot = args.mako_root _rot = args.mako_root

View File

@@ -116,7 +116,7 @@ CONFIG = {
"email_address": "support@example.com" "email_address": "support@example.com"
}, },
], ],
# This database holds the map between a subjects local identifier and # This database holds the map between a subject's local identifier and
# the identifier returned to a SP # the identifier returned to a SP
"xmlsec_binary": xmlsec_path, "xmlsec_binary": xmlsec_path,
#"attribute_map_dir": "../attributemaps", #"attribute_map_dir": "../attributemaps",

View File

@@ -96,7 +96,7 @@ def whoami(environ, start_response, user):
if not nameid: if not nameid:
return not_authn(environ, start_response) return not_authn(environ, start_response)
if ava: if ava:
response = ["<h2>Your identity are supposed to be</h2>"] response = ["<h2>Your identity is supposed to be</h2>"]
response.extend(dict_to_table(ava)) response.extend(dict_to_table(ava))
else: else:
response = [ response = [
@@ -222,10 +222,10 @@ def application(environ, start_response):
""" """
The main WSGI application. Dispatch the current request to The main WSGI application. Dispatch the current request to
the functions from above and store the regular expression the functions from above and store the regular expression
captures in the WSGI environment as `myapp.url_args` so that captures in the WSGI environment as `myapp.url_args` so that
the functions from above can access the url placeholders. the functions from above can access the url placeholders.
If nothing matches call the `not_found` function. If nothing matches, call the `not_found` function.
:param environ: The HTTP application environment :param environ: The HTTP application environment
:param start_response: The application to run when the handling of the :param start_response: The application to run when the handling of the
@@ -268,6 +268,7 @@ app_with_auth = make_middleware_with_config(application, {"here": "."},
log_file="repoze_who.log") log_file="repoze_who.log")
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
HOST = '127.0.0.1'
PORT = 8087 PORT = 8087
# allow uwsgi or gunicorn mount # allow uwsgi or gunicorn mount
@@ -291,6 +292,6 @@ if __name__ == '__main__':
args = parser.parse_args() args = parser.parse_args()
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
srv = make_server('', PORT, app_with_auth) srv = make_server(HOST, PORT, app_with_auth)
print "SP listening on port: %s" % PORT print "SP listening on %s:%s" % (HOST, PORT)
srv.serve_forever() srv.serve_forever()

View File

@@ -1,5 +1,6 @@
from saml2.assertion import Policy from saml2.assertion import Policy
HOST = '127.0.0.1'
PORT = 8087 PORT = 8087
HTTPS = False HTTPS = False
@@ -11,6 +12,6 @@ POLICY = Policy(
) )
# HTTPS cert information # HTTPS cert information
SERVER_CERT = "pki/ssl.crt" SERVER_CERT = "pki/mycert.pem"
SERVER_KEY = "pki/ssl.pem" SERVER_KEY = "pki/mykey.pem"
CERT_CHAIN = "" CERT_CHAIN = ""

View File

@@ -93,7 +93,7 @@ def dict_to_table(ava, lev=0, width=1):
def handle_static(environ, start_response, path): def handle_static(environ, start_response, path):
""" """
Creates a response for a static file. There might be a longer 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. then just /static/... - if so strip the path leading up to static.
:param environ: wsgi enviroment :param environ: wsgi enviroment
:param start_response: wsgi start response :param start_response: wsgi start response
@@ -534,7 +534,7 @@ class SSO(object):
self.cache.relay_state[_rstate] = came_from self.cache.relay_state[_rstate] = came_from
ht_args = _cli.apply_binding(_binding, "%s" % req, destination, ht_args = _cli.apply_binding(_binding, "%s" % req, destination,
relay_state=_rstate) relay_state=_rstate)
_sid = req.id _sid = req_id
logger.debug("ht_args: %s" % ht_args) logger.debug("ht_args: %s" % ht_args)
except Exception, exc: except Exception, exc:
logger.exception(exc) logger.exception(exc)
@@ -645,7 +645,7 @@ def application(environ, start_response):
The main WSGI application. Dispatch the current request to The main WSGI application. Dispatch the current request to
the functions from above. the functions from above.
If nothing matches call the `not_found` function. If nothing matches, call the `not_found` function.
:param environ: The HTTP application environment :param environ: The HTTP application environment
:param start_response: The application to run when the handling of the :param start_response: The application to run when the handling of the
@@ -683,13 +683,14 @@ def application(environ, start_response):
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
HOST = service_conf.HOST
PORT = service_conf.PORT PORT = service_conf.PORT
# ------- HTTPS ------- # ------- HTTPS -------
# These should point to relevant files # These should point to relevant files
SERVER_CERT = service_conf.SERVER_CERT SERVER_CERT = service_conf.SERVER_CERT
SERVER_KEY = service_conf.SERVER_KEY SERVER_KEY = service_conf.SERVER_KEY
# This is of course the certificate chain for the CA that signed # This is of course the certificate chain for the CA that signed
# you cert and all the way up to the top # your cert and all the way up to the top
CERT_CHAIN = service_conf.CERT_CHAIN CERT_CHAIN = service_conf.CERT_CHAIN
if __name__ == '__main__': if __name__ == '__main__':
@@ -727,13 +728,13 @@ if __name__ == '__main__':
add_urls() add_urls()
SRV = wsgiserver.CherryPyWSGIServer(('0.0.0.0', PORT), application) SRV = wsgiserver.CherryPyWSGIServer((HOST, PORT), application)
if service_conf.HTTPS: if service_conf.HTTPS:
SRV.ssl_adapter = ssl_pyopenssl.pyOpenSSLAdapter(SERVER_CERT, SRV.ssl_adapter = ssl_pyopenssl.pyOpenSSLAdapter(SERVER_CERT,
SERVER_KEY, CERT_CHAIN) SERVER_KEY, CERT_CHAIN)
logger.info("Server starting") logger.info("Server starting")
print "SP listening on port: %s" % PORT print "SP listening on %s:%s" % (HOST, PORT)
try: try:
SRV.start() SRV.start()
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@@ -46,7 +46,8 @@ install_requires = [
'pycrypto', # 'Crypto' 'pycrypto', # 'Crypto'
'pytz', 'pytz',
'pyOpenSSL', 'pyOpenSSL',
'python-dateutil' 'python-dateutil',
'argparse'
] ]
tests_require = [ tests_require = [

View File

@@ -78,7 +78,8 @@ def _match(attr, ava):
return None return None
def filter_on_attributes(ava, required=None, optional=None, acs=None): def filter_on_attributes(ava, required=None, optional=None, acs=None,
fail_on_unfulfilled_requirements=True):
""" Filter """ Filter
:param ava: An attribute value assertion as a dictionary :param ava: An attribute value assertion as a dictionary
@@ -86,6 +87,8 @@ def filter_on_attributes(ava, required=None, optional=None, acs=None):
required required
:param optional: list of RequestedAttribute instances defined to be :param optional: list of RequestedAttribute instances defined to be
optional optional
:param fail_on_unfulfilled_requirements: If required attributes
are missing fail or fail not depending on this parameter.
:return: The modified attribute value assertion :return: The modified attribute value assertion
""" """
res = {} res = {}
@@ -116,7 +119,7 @@ def filter_on_attributes(ava, required=None, optional=None, acs=None):
values = [] values = []
res[_fn] = _filter_values(ava[_fn], values, True) res[_fn] = _filter_values(ava[_fn], values, True)
continue continue
else: elif fail_on_unfulfilled_requirements:
desc = "Required attribute missing: '%s' (%s)" % (attr["name"], desc = "Required attribute missing: '%s' (%s)" % (attr["name"],
_name) _name)
raise MissingValue(desc) raise MissingValue(desc)
@@ -434,6 +437,16 @@ class Policy(object):
return self.get("attribute_restrictions", sp_entity_id) return self.get("attribute_restrictions", sp_entity_id)
def get_fail_on_missing_requested(self, sp_entity_id):
""" Return the whether the IdP should should fail if the SPs
requested attributes could not be found.
:param sp_entity_id: The SP entity ID
:return: The restrictions
"""
return self.get("fail_on_missing_requested", sp_entity_id, True)
def entity_category_attributes(self, ec): def entity_category_attributes(self, ec):
if not self._restrictions: if not self._restrictions:
return None return None
@@ -492,7 +505,9 @@ class Policy(object):
if required or optional: if required or optional:
logger.debug("required: %s, optional: %s" % (required, optional)) logger.debug("required: %s, optional: %s" % (required, optional))
ava = filter_on_attributes(ava, required, optional, self.acs) ava = filter_on_attributes(
ava, required, optional, self.acs,
self.get_fail_on_missing_requested(sp_entity_id))
return ava return ava
@@ -543,6 +558,103 @@ class EntityCategories(object):
pass pass
def _authn_context_class_ref(authn_class, authn_auth=None):
"""
Construct the authn context with a authn context class reference
:param authn_class: The authn context class reference
:param authn_auth: Authenticating Authority
:return: An AuthnContext instance
"""
cntx_class = factory(saml.AuthnContextClassRef, text=authn_class)
if authn_auth:
return factory(saml.AuthnContext,
authn_context_class_ref=cntx_class,
authenticating_authority=factory(
saml.AuthenticatingAuthority, text=authn_auth))
else:
return factory(saml.AuthnContext,
authn_context_class_ref=cntx_class)
def _authn_context_decl(decl, authn_auth=None):
"""
Construct the authn context with a authn context declaration
:param decl: The authn context declaration
:param authn_auth: Authenticating Authority
:return: An AuthnContext instance
"""
return factory(saml.AuthnContext,
authn_context_decl=decl,
authenticating_authority=factory(
saml.AuthenticatingAuthority, text=authn_auth))
def _authn_context_decl_ref(decl_ref, authn_auth=None):
"""
Construct the authn context with a authn context declaration reference
:param decl_ref: The authn context declaration reference
:param authn_auth: Authenticating Authority
:return: An AuthnContext instance
"""
return factory(saml.AuthnContext,
authn_context_decl_ref=decl_ref,
authenticating_authority=factory(
saml.AuthenticatingAuthority, text=authn_auth))
def authn_statement(authn_class=None, authn_auth=None,
authn_decl=None, authn_decl_ref=None, authn_instant="",
subject_locality=""):
"""
Construct the AuthnStatement
:param authn_class: Authentication Context Class reference
:param authn_auth: Authenticating Authority
:param authn_decl: Authentication Context Declaration
:param authn_decl_ref: Authentication Context Declaration reference
:param authn_instant: When the Authentication was performed.
Assumed to be seconds since the Epoch.
:param subject_locality: Specifies the DNS domain name and IP address
for the system from which the assertion subject was apparently
authenticated.
:return: An AuthnContext instance
"""
if authn_instant:
_instant = instant(time_stamp=authn_instant)
else:
_instant = instant()
if authn_class:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
authn_context=_authn_context_class_ref(
authn_class, authn_auth))
elif authn_decl:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
authn_context=_authn_context_decl(authn_decl, authn_auth))
elif authn_decl_ref:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
authn_context=_authn_context_decl_ref(authn_decl_ref,
authn_auth))
else:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid())
if subject_locality:
res.subject_locality = saml.SubjectLocality(text=subject_locality)
return res
class Assertion(dict): class Assertion(dict):
""" Handles assertions about subjects """ """ Handles assertions about subjects """
@@ -550,101 +662,6 @@ class Assertion(dict):
dict.__init__(self, dic) dict.__init__(self, dic)
self.acs = [] self.acs = []
@staticmethod
def _authn_context_decl(decl, authn_auth=None):
"""
Construct the authn context with a authn context declaration
:param decl: The authn context declaration
:param authn_auth: Authenticating Authority
:return: An AuthnContext instance
"""
return factory(saml.AuthnContext,
authn_context_decl=decl,
authenticating_authority=factory(
saml.AuthenticatingAuthority, text=authn_auth))
def _authn_context_decl_ref(self, decl_ref, authn_auth=None):
"""
Construct the authn context with a authn context declaration reference
:param decl_ref: The authn context declaration reference
:param authn_auth: Authenticating Authority
:return: An AuthnContext instance
"""
return factory(saml.AuthnContext,
authn_context_decl_ref=decl_ref,
authenticating_authority=factory(
saml.AuthenticatingAuthority, text=authn_auth))
@staticmethod
def _authn_context_class_ref(authn_class, authn_auth=None):
"""
Construct the authn context with a authn context class reference
:param authn_class: The authn context class reference
:param authn_auth: Authenticating Authority
:return: An AuthnContext instance
"""
cntx_class = factory(saml.AuthnContextClassRef, text=authn_class)
if authn_auth:
return factory(saml.AuthnContext,
authn_context_class_ref=cntx_class,
authenticating_authority=factory(
saml.AuthenticatingAuthority, text=authn_auth))
else:
return factory(saml.AuthnContext,
authn_context_class_ref=cntx_class)
def _authn_statement(self, authn_class=None, authn_auth=None,
authn_decl=None, authn_decl_ref=None, authn_instant="",
subject_locality=""):
"""
Construct the AuthnStatement
:param authn_class: Authentication Context Class reference
:param authn_auth: Authenticating Authority
:param authn_decl: Authentication Context Declaration
:param authn_decl_ref: Authentication Context Declaration reference
:param authn_instant: When the Authentication was performed.
Assumed to be seconds since the Epoch.
:param subject_locality: Specifies the DNS domain name and IP address
for the system from which the assertion subject was apparently
authenticated.
:return: An AuthnContext instance
"""
if authn_instant:
_instant = instant(time_stamp=authn_instant)
else:
_instant = instant()
if authn_class:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
authn_context=self._authn_context_class_ref(
authn_class, authn_auth))
elif authn_decl:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
authn_context=self._authn_context_decl(authn_decl, authn_auth))
elif authn_decl_ref:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid(),
authn_context=self._authn_context_decl_ref(authn_decl_ref,
authn_auth))
else:
res = factory(
saml.AuthnStatement,
authn_instant=_instant,
session_index=sid())
if subject_locality:
res.subject_locality = saml.SubjectLocality(text=subject_locality)
return res
def construct(self, sp_entity_id, in_response_to, consumer_url, def construct(self, sp_entity_id, in_response_to, consumer_url,
name_id, attrconvs, policy, issuer, authn_class=None, name_id, attrconvs, policy, issuer, authn_class=None,
authn_auth=None, authn_decl=None, encrypt=None, authn_auth=None, authn_decl=None, encrypt=None,
@@ -695,10 +712,10 @@ class Assertion(dict):
conds = policy.conditions(sp_entity_id) conds = policy.conditions(sp_entity_id)
if authn_auth or authn_class or authn_decl or authn_decl_ref: if authn_auth or authn_class or authn_decl or authn_decl_ref:
_authn_statement = self._authn_statement(authn_class, authn_auth, _authn_statement = authn_statement(authn_class, authn_auth,
authn_decl, authn_decl_ref, authn_decl, authn_decl_ref,
authn_instant, authn_instant,
subject_locality) subject_locality)
else: else:
_authn_statement = None _authn_statement = None

View File

@@ -1,20 +1,18 @@
# See http://technet.microsoft.com/en-us/library/cc733065(v=ws.10).aspx CLAIMS = 'http://schemas.xmlsoap.org/claims/'
# and http://technet.microsoft.com/en-us/library/ee913589(v=ws.10).aspx
# for information regarding the default claim types supported by
# Microsoft ADFS v1.x.
MAP = { MAP = {
"identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
"fro": { 'fro': {
'http://schemas.xmlsoap.org/claims/commonname': 'commonName', CLAIMS+'commonname': 'commonName',
'http://schemas.xmlsoap.org/claims/emailaddress': 'emailAddress', CLAIMS+'emailaddress': 'emailAddress',
'http://schemas.xmlsoap.org/claims/group': 'group', CLAIMS+'group': 'group',
'http://schemas.xmlsoap.org/claims/upn': 'upn', CLAIMS+'upn': 'upn',
}, },
"to": { 'to': {
'commonName': 'http://schemas.xmlsoap.org/claims/commonname', 'commonName': CLAIMS+'commonname',
'emailAddress': 'http://schemas.xmlsoap.org/claims/emailaddress', 'emailAddress': CLAIMS+'emailaddress',
'group': 'http://schemas.xmlsoap.org/claims/group', 'group': CLAIMS+'group',
'upn': 'http://schemas.xmlsoap.org/claims/upn', 'upn': CLAIMS+'upn',
} }
} }

View File

@@ -1,47 +1,49 @@
# See http://technet.microsoft.com/en-us/library/ee913589(v=ws.10).aspx CLAIMS = 'http://schemas.xmlsoap.org/claims/'
# for information regarding the default claim types supported by COM_WS_CLAIMS = 'http://schemas.xmlsoap.com/ws/2005/05/identity/claims/'
# Microsoft ADFS v2.0. MS_CLAIMS = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/'
ORG_WS_CLAIMS = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/'
MAP = { MAP = {
"identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
"fro": { 'fro': {
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'emailAddress', CLAIMS+'commonname': 'commonName',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname': 'givenName', CLAIMS+'group': 'group',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name': 'name', COM_WS_CLAIMS+'denyonlysid': 'denyOnlySid',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn': 'upn', MS_CLAIMS+'authenticationmethod': 'authenticationMethod',
'http://schemas.xmlsoap.org/claims/commonname': 'commonName', MS_CLAIMS+'denyonlyprimarygroupsid': 'denyOnlyPrimaryGroupSid',
'http://schemas.xmlsoap.org/claims/group': 'group', MS_CLAIMS+'denyonlyprimarysid': 'denyOnlyPrimarySid',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/role': 'role', MS_CLAIMS+'groupsid': 'groupSid',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname': 'surname', MS_CLAIMS+'primarygroupsid': 'primaryGroupSid',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier': 'privatePersonalId', MS_CLAIMS+'primarysid': 'primarySid',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': 'nameId', MS_CLAIMS+'role': 'role',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod': 'authenticationMethod', MS_CLAIMS+'windowsaccountname': 'windowsAccountName',
'http://schemas.xmlsoap.com/ws/2005/05/identity/claims/denyonlysid': 'denyOnlySid', ORG_WS_CLAIMS+'emailaddress': 'emailAddress',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid': 'denyOnlyPrimarySid', ORG_WS_CLAIMS+'givenname': 'givenName',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid': 'denyOnlyPrimaryGroupSid', ORG_WS_CLAIMS+'name': 'name',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid': 'groupSid', ORG_WS_CLAIMS+'nameidentifier': 'nameId',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid': 'primaryGroupSid', ORG_WS_CLAIMS+'privatepersonalidentifier': 'privatePersonalId',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid': 'primarySid', ORG_WS_CLAIMS+'surname': 'surname',
'http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname': 'windowsAccountName', ORG_WS_CLAIMS+'upn': 'upn',
}, },
"to": { 'to': {
'emailAddress': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', 'authenticationMethod': MS_CLAIMS+'authenticationmethod',
'givenName': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname', 'commonName': CLAIMS+'commonname',
'name': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name', 'denyOnlyPrimaryGroupSid': MS_CLAIMS+'denyonlyprimarygroupsid',
'upn': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn', 'denyOnlyPrimarySid': MS_CLAIMS+'denyonlyprimarysid',
'commonName': 'http://schemas.xmlsoap.org/claims/commonname', 'denyOnlySid': COM_WS_CLAIMS+'denyonlysid',
'group': 'http://schemas.xmlsoap.org/claims/group', 'emailAddress': ORG_WS_CLAIMS+'emailaddress',
'role': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role', 'givenName': ORG_WS_CLAIMS+'givenname',
'surname': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname', 'group': CLAIMS+'group',
'privatePersonalId': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier', 'groupSid': MS_CLAIMS+'groupsid',
'nameId': 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier', 'name': ORG_WS_CLAIMS+'name',
'authenticationMethod': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod', 'nameId': ORG_WS_CLAIMS+'nameidentifier',
'denyOnlySid': 'http://schemas.xmlsoap.com/ws/2005/05/identity/claims/denyonlysid', 'primaryGroupSid': MS_CLAIMS+'primarygroupsid',
'denyOnlyPrimarySid': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid', 'primarySid': MS_CLAIMS+'primarysid',
'denyOnlyPrimaryGroupSid': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid', 'privatePersonalId': ORG_WS_CLAIMS+'privatepersonalidentifier',
'groupSid': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid', 'role': MS_CLAIMS+'role',
'primaryGroupSid': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid', 'surname': ORG_WS_CLAIMS+'surname',
'primarySid': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid', 'upn': ORG_WS_CLAIMS+'upn',
'windowsAccountName': 'http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname', 'windowsAccountName': MS_CLAIMS+'windowsaccountname',
} }
} }

View File

@@ -1,326 +1,328 @@
DEF = 'urn:mace:dir:attribute-def:'
MAP = { MAP = {
"identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
"fro": { 'fro': {
'urn:mace:dir:attribute-def:aRecord': 'aRecord', DEF+'aRecord': 'aRecord',
'urn:mace:dir:attribute-def:aliasedEntryName': 'aliasedEntryName', DEF+'aliasedEntryName': 'aliasedEntryName',
'urn:mace:dir:attribute-def:aliasedObjectName': 'aliasedObjectName', DEF+'aliasedObjectName': 'aliasedObjectName',
'urn:mace:dir:attribute-def:associatedDomain': 'associatedDomain', DEF+'associatedDomain': 'associatedDomain',
'urn:mace:dir:attribute-def:associatedName': 'associatedName', DEF+'associatedName': 'associatedName',
'urn:mace:dir:attribute-def:audio': 'audio', DEF+'audio': 'audio',
'urn:mace:dir:attribute-def:authorityRevocationList': 'authorityRevocationList', DEF+'authorityRevocationList': 'authorityRevocationList',
'urn:mace:dir:attribute-def:buildingName': 'buildingName', DEF+'buildingName': 'buildingName',
'urn:mace:dir:attribute-def:businessCategory': 'businessCategory', DEF+'businessCategory': 'businessCategory',
'urn:mace:dir:attribute-def:c': 'c', DEF+'c': 'c',
'urn:mace:dir:attribute-def:cACertificate': 'cACertificate', DEF+'cACertificate': 'cACertificate',
'urn:mace:dir:attribute-def:cNAMERecord': 'cNAMERecord', DEF+'cNAMERecord': 'cNAMERecord',
'urn:mace:dir:attribute-def:carLicense': 'carLicense', DEF+'carLicense': 'carLicense',
'urn:mace:dir:attribute-def:certificateRevocationList': 'certificateRevocationList', DEF+'certificateRevocationList': 'certificateRevocationList',
'urn:mace:dir:attribute-def:cn': 'cn', DEF+'cn': 'cn',
'urn:mace:dir:attribute-def:co': 'co', DEF+'co': 'co',
'urn:mace:dir:attribute-def:commonName': 'commonName', DEF+'commonName': 'commonName',
'urn:mace:dir:attribute-def:countryName': 'countryName', DEF+'countryName': 'countryName',
'urn:mace:dir:attribute-def:crossCertificatePair': 'crossCertificatePair', DEF+'crossCertificatePair': 'crossCertificatePair',
'urn:mace:dir:attribute-def:dITRedirect': 'dITRedirect', DEF+'dITRedirect': 'dITRedirect',
'urn:mace:dir:attribute-def:dSAQuality': 'dSAQuality', DEF+'dSAQuality': 'dSAQuality',
'urn:mace:dir:attribute-def:dc': 'dc', DEF+'dc': 'dc',
'urn:mace:dir:attribute-def:deltaRevocationList': 'deltaRevocationList', DEF+'deltaRevocationList': 'deltaRevocationList',
'urn:mace:dir:attribute-def:departmentNumber': 'departmentNumber', DEF+'departmentNumber': 'departmentNumber',
'urn:mace:dir:attribute-def:description': 'description', DEF+'description': 'description',
'urn:mace:dir:attribute-def:destinationIndicator': 'destinationIndicator', DEF+'destinationIndicator': 'destinationIndicator',
'urn:mace:dir:attribute-def:displayName': 'displayName', DEF+'displayName': 'displayName',
'urn:mace:dir:attribute-def:distinguishedName': 'distinguishedName', DEF+'distinguishedName': 'distinguishedName',
'urn:mace:dir:attribute-def:dmdName': 'dmdName', DEF+'dmdName': 'dmdName',
'urn:mace:dir:attribute-def:dnQualifier': 'dnQualifier', DEF+'dnQualifier': 'dnQualifier',
'urn:mace:dir:attribute-def:documentAuthor': 'documentAuthor', DEF+'documentAuthor': 'documentAuthor',
'urn:mace:dir:attribute-def:documentIdentifier': 'documentIdentifier', DEF+'documentIdentifier': 'documentIdentifier',
'urn:mace:dir:attribute-def:documentLocation': 'documentLocation', DEF+'documentLocation': 'documentLocation',
'urn:mace:dir:attribute-def:documentPublisher': 'documentPublisher', DEF+'documentPublisher': 'documentPublisher',
'urn:mace:dir:attribute-def:documentTitle': 'documentTitle', DEF+'documentTitle': 'documentTitle',
'urn:mace:dir:attribute-def:documentVersion': 'documentVersion', DEF+'documentVersion': 'documentVersion',
'urn:mace:dir:attribute-def:domainComponent': 'domainComponent', DEF+'domainComponent': 'domainComponent',
'urn:mace:dir:attribute-def:drink': 'drink', DEF+'drink': 'drink',
'urn:mace:dir:attribute-def:eduOrgHomePageURI': 'eduOrgHomePageURI', DEF+'eduOrgHomePageURI': 'eduOrgHomePageURI',
'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI': 'eduOrgIdentityAuthNPolicyURI', DEF+'eduOrgIdentityAuthNPolicyURI': 'eduOrgIdentityAuthNPolicyURI',
'urn:mace:dir:attribute-def:eduOrgLegalName': 'eduOrgLegalName', DEF+'eduOrgLegalName': 'eduOrgLegalName',
'urn:mace:dir:attribute-def:eduOrgSuperiorURI': 'eduOrgSuperiorURI', DEF+'eduOrgSuperiorURI': 'eduOrgSuperiorURI',
'urn:mace:dir:attribute-def:eduOrgWhitePagesURI': 'eduOrgWhitePagesURI', DEF+'eduOrgWhitePagesURI': 'eduOrgWhitePagesURI',
'urn:mace:dir:attribute-def:eduPersonAffiliation': 'eduPersonAffiliation', DEF+'eduPersonAffiliation': 'eduPersonAffiliation',
'urn:mace:dir:attribute-def:eduPersonEntitlement': 'eduPersonEntitlement', DEF+'eduPersonEntitlement': 'eduPersonEntitlement',
'urn:mace:dir:attribute-def:eduPersonNickname': 'eduPersonNickname', DEF+'eduPersonNickname': 'eduPersonNickname',
'urn:mace:dir:attribute-def:eduPersonOrgDN': 'eduPersonOrgDN', DEF+'eduPersonOrgDN': 'eduPersonOrgDN',
'urn:mace:dir:attribute-def:eduPersonOrgUnitDN': 'eduPersonOrgUnitDN', DEF+'eduPersonOrgUnitDN': 'eduPersonOrgUnitDN',
'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation': 'eduPersonPrimaryAffiliation', DEF+'eduPersonPrimaryAffiliation': 'eduPersonPrimaryAffiliation',
'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN': 'eduPersonPrimaryOrgUnitDN', DEF+'eduPersonPrimaryOrgUnitDN': 'eduPersonPrimaryOrgUnitDN',
'urn:mace:dir:attribute-def:eduPersonPrincipalName': 'eduPersonPrincipalName', DEF+'eduPersonPrincipalName': 'eduPersonPrincipalName',
'urn:mace:dir:attribute-def:eduPersonScopedAffiliation': 'eduPersonScopedAffiliation', DEF+'eduPersonScopedAffiliation': 'eduPersonScopedAffiliation',
'urn:mace:dir:attribute-def:eduPersonTargetedID': 'eduPersonTargetedID', DEF+'eduPersonTargetedID': 'eduPersonTargetedID',
'urn:mace:dir:attribute-def:email': 'email', DEF+'email': 'email',
'urn:mace:dir:attribute-def:emailAddress': 'emailAddress', DEF+'emailAddress': 'emailAddress',
'urn:mace:dir:attribute-def:employeeNumber': 'employeeNumber', DEF+'employeeNumber': 'employeeNumber',
'urn:mace:dir:attribute-def:employeeType': 'employeeType', DEF+'employeeType': 'employeeType',
'urn:mace:dir:attribute-def:enhancedSearchGuide': 'enhancedSearchGuide', DEF+'enhancedSearchGuide': 'enhancedSearchGuide',
'urn:mace:dir:attribute-def:facsimileTelephoneNumber': 'facsimileTelephoneNumber', DEF+'facsimileTelephoneNumber': 'facsimileTelephoneNumber',
'urn:mace:dir:attribute-def:favouriteDrink': 'favouriteDrink', DEF+'favouriteDrink': 'favouriteDrink',
'urn:mace:dir:attribute-def:fax': 'fax', DEF+'fax': 'fax',
'urn:mace:dir:attribute-def:federationFeideSchemaVersion': 'federationFeideSchemaVersion', DEF+'federationFeideSchemaVersion': 'federationFeideSchemaVersion',
'urn:mace:dir:attribute-def:friendlyCountryName': 'friendlyCountryName', DEF+'friendlyCountryName': 'friendlyCountryName',
'urn:mace:dir:attribute-def:generationQualifier': 'generationQualifier', DEF+'generationQualifier': 'generationQualifier',
'urn:mace:dir:attribute-def:givenName': 'givenName', DEF+'givenName': 'givenName',
'urn:mace:dir:attribute-def:gn': 'gn', DEF+'gn': 'gn',
'urn:mace:dir:attribute-def:homePhone': 'homePhone', DEF+'homePhone': 'homePhone',
'urn:mace:dir:attribute-def:homePostalAddress': 'homePostalAddress', DEF+'homePostalAddress': 'homePostalAddress',
'urn:mace:dir:attribute-def:homeTelephoneNumber': 'homeTelephoneNumber', DEF+'homeTelephoneNumber': 'homeTelephoneNumber',
'urn:mace:dir:attribute-def:host': 'host', DEF+'host': 'host',
'urn:mace:dir:attribute-def:houseIdentifier': 'houseIdentifier', DEF+'houseIdentifier': 'houseIdentifier',
'urn:mace:dir:attribute-def:info': 'info', DEF+'info': 'info',
'urn:mace:dir:attribute-def:initials': 'initials', DEF+'initials': 'initials',
'urn:mace:dir:attribute-def:internationaliSDNNumber': 'internationaliSDNNumber', DEF+'internationaliSDNNumber': 'internationaliSDNNumber',
'urn:mace:dir:attribute-def:janetMailbox': 'janetMailbox', DEF+'janetMailbox': 'janetMailbox',
'urn:mace:dir:attribute-def:jpegPhoto': 'jpegPhoto', DEF+'jpegPhoto': 'jpegPhoto',
'urn:mace:dir:attribute-def:knowledgeInformation': 'knowledgeInformation', DEF+'knowledgeInformation': 'knowledgeInformation',
'urn:mace:dir:attribute-def:l': 'l', DEF+'l': 'l',
'urn:mace:dir:attribute-def:labeledURI': 'labeledURI', DEF+'labeledURI': 'labeledURI',
'urn:mace:dir:attribute-def:localityName': 'localityName', DEF+'localityName': 'localityName',
'urn:mace:dir:attribute-def:mDRecord': 'mDRecord', DEF+'mDRecord': 'mDRecord',
'urn:mace:dir:attribute-def:mXRecord': 'mXRecord', DEF+'mXRecord': 'mXRecord',
'urn:mace:dir:attribute-def:mail': 'mail', DEF+'mail': 'mail',
'urn:mace:dir:attribute-def:mailPreferenceOption': 'mailPreferenceOption', DEF+'mailPreferenceOption': 'mailPreferenceOption',
'urn:mace:dir:attribute-def:manager': 'manager', DEF+'manager': 'manager',
'urn:mace:dir:attribute-def:member': 'member', DEF+'member': 'member',
'urn:mace:dir:attribute-def:mobile': 'mobile', DEF+'mobile': 'mobile',
'urn:mace:dir:attribute-def:mobileTelephoneNumber': 'mobileTelephoneNumber', DEF+'mobileTelephoneNumber': 'mobileTelephoneNumber',
'urn:mace:dir:attribute-def:nSRecord': 'nSRecord', DEF+'nSRecord': 'nSRecord',
'urn:mace:dir:attribute-def:name': 'name', DEF+'name': 'name',
'urn:mace:dir:attribute-def:norEduOrgAcronym': 'norEduOrgAcronym', DEF+'norEduOrgAcronym': 'norEduOrgAcronym',
'urn:mace:dir:attribute-def:norEduOrgNIN': 'norEduOrgNIN', DEF+'norEduOrgNIN': 'norEduOrgNIN',
'urn:mace:dir:attribute-def:norEduOrgSchemaVersion': 'norEduOrgSchemaVersion', DEF+'norEduOrgSchemaVersion': 'norEduOrgSchemaVersion',
'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier': 'norEduOrgUniqueIdentifier', DEF+'norEduOrgUniqueIdentifier': 'norEduOrgUniqueIdentifier',
'urn:mace:dir:attribute-def:norEduOrgUniqueNumber': 'norEduOrgUniqueNumber', DEF+'norEduOrgUniqueNumber': 'norEduOrgUniqueNumber',
'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier': 'norEduOrgUnitUniqueIdentifier', DEF+'norEduOrgUnitUniqueIdentifier': 'norEduOrgUnitUniqueIdentifier',
'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber': 'norEduOrgUnitUniqueNumber', DEF+'norEduOrgUnitUniqueNumber': 'norEduOrgUnitUniqueNumber',
'urn:mace:dir:attribute-def:norEduPersonBirthDate': 'norEduPersonBirthDate', DEF+'norEduPersonBirthDate': 'norEduPersonBirthDate',
'urn:mace:dir:attribute-def:norEduPersonLIN': 'norEduPersonLIN', DEF+'norEduPersonLIN': 'norEduPersonLIN',
'urn:mace:dir:attribute-def:norEduPersonNIN': 'norEduPersonNIN', DEF+'norEduPersonNIN': 'norEduPersonNIN',
'urn:mace:dir:attribute-def:o': 'o', DEF+'o': 'o',
'urn:mace:dir:attribute-def:objectClass': 'objectClass', DEF+'objectClass': 'objectClass',
'urn:mace:dir:attribute-def:organizationName': 'organizationName', DEF+'organizationName': 'organizationName',
'urn:mace:dir:attribute-def:organizationalStatus': 'organizationalStatus', DEF+'organizationalStatus': 'organizationalStatus',
'urn:mace:dir:attribute-def:organizationalUnitName': 'organizationalUnitName', DEF+'organizationalUnitName': 'organizationalUnitName',
'urn:mace:dir:attribute-def:otherMailbox': 'otherMailbox', DEF+'otherMailbox': 'otherMailbox',
'urn:mace:dir:attribute-def:ou': 'ou', DEF+'ou': 'ou',
'urn:mace:dir:attribute-def:owner': 'owner', DEF+'owner': 'owner',
'urn:mace:dir:attribute-def:pager': 'pager', DEF+'pager': 'pager',
'urn:mace:dir:attribute-def:pagerTelephoneNumber': 'pagerTelephoneNumber', DEF+'pagerTelephoneNumber': 'pagerTelephoneNumber',
'urn:mace:dir:attribute-def:personalSignature': 'personalSignature', DEF+'personalSignature': 'personalSignature',
'urn:mace:dir:attribute-def:personalTitle': 'personalTitle', DEF+'personalTitle': 'personalTitle',
'urn:mace:dir:attribute-def:photo': 'photo', DEF+'photo': 'photo',
'urn:mace:dir:attribute-def:physicalDeliveryOfficeName': 'physicalDeliveryOfficeName', DEF+'physicalDeliveryOfficeName': 'physicalDeliveryOfficeName',
'urn:mace:dir:attribute-def:pkcs9email': 'pkcs9email', DEF+'pkcs9email': 'pkcs9email',
'urn:mace:dir:attribute-def:postOfficeBox': 'postOfficeBox', DEF+'postOfficeBox': 'postOfficeBox',
'urn:mace:dir:attribute-def:postalAddress': 'postalAddress', DEF+'postalAddress': 'postalAddress',
'urn:mace:dir:attribute-def:postalCode': 'postalCode', DEF+'postalCode': 'postalCode',
'urn:mace:dir:attribute-def:preferredDeliveryMethod': 'preferredDeliveryMethod', DEF+'preferredDeliveryMethod': 'preferredDeliveryMethod',
'urn:mace:dir:attribute-def:preferredLanguage': 'preferredLanguage', DEF+'preferredLanguage': 'preferredLanguage',
'urn:mace:dir:attribute-def:presentationAddress': 'presentationAddress', DEF+'presentationAddress': 'presentationAddress',
'urn:mace:dir:attribute-def:protocolInformation': 'protocolInformation', DEF+'protocolInformation': 'protocolInformation',
'urn:mace:dir:attribute-def:pseudonym': 'pseudonym', DEF+'pseudonym': 'pseudonym',
'urn:mace:dir:attribute-def:registeredAddress': 'registeredAddress', DEF+'registeredAddress': 'registeredAddress',
'urn:mace:dir:attribute-def:rfc822Mailbox': 'rfc822Mailbox', DEF+'rfc822Mailbox': 'rfc822Mailbox',
'urn:mace:dir:attribute-def:roleOccupant': 'roleOccupant', DEF+'roleOccupant': 'roleOccupant',
'urn:mace:dir:attribute-def:roomNumber': 'roomNumber', DEF+'roomNumber': 'roomNumber',
'urn:mace:dir:attribute-def:sOARecord': 'sOARecord', DEF+'sOARecord': 'sOARecord',
'urn:mace:dir:attribute-def:searchGuide': 'searchGuide', DEF+'searchGuide': 'searchGuide',
'urn:mace:dir:attribute-def:secretary': 'secretary', DEF+'secretary': 'secretary',
'urn:mace:dir:attribute-def:seeAlso': 'seeAlso', DEF+'seeAlso': 'seeAlso',
'urn:mace:dir:attribute-def:serialNumber': 'serialNumber', DEF+'serialNumber': 'serialNumber',
'urn:mace:dir:attribute-def:singleLevelQuality': 'singleLevelQuality', DEF+'singleLevelQuality': 'singleLevelQuality',
'urn:mace:dir:attribute-def:sn': 'sn', DEF+'sn': 'sn',
'urn:mace:dir:attribute-def:st': 'st', DEF+'st': 'st',
'urn:mace:dir:attribute-def:stateOrProvinceName': 'stateOrProvinceName', DEF+'stateOrProvinceName': 'stateOrProvinceName',
'urn:mace:dir:attribute-def:street': 'street', DEF+'street': 'street',
'urn:mace:dir:attribute-def:streetAddress': 'streetAddress', DEF+'streetAddress': 'streetAddress',
'urn:mace:dir:attribute-def:subtreeMaximumQuality': 'subtreeMaximumQuality', DEF+'subtreeMaximumQuality': 'subtreeMaximumQuality',
'urn:mace:dir:attribute-def:subtreeMinimumQuality': 'subtreeMinimumQuality', DEF+'subtreeMinimumQuality': 'subtreeMinimumQuality',
'urn:mace:dir:attribute-def:supportedAlgorithms': 'supportedAlgorithms', DEF+'supportedAlgorithms': 'supportedAlgorithms',
'urn:mace:dir:attribute-def:supportedApplicationContext': 'supportedApplicationContext', DEF+'supportedApplicationContext': 'supportedApplicationContext',
'urn:mace:dir:attribute-def:surname': 'surname', DEF+'surname': 'surname',
'urn:mace:dir:attribute-def:telephoneNumber': 'telephoneNumber', DEF+'telephoneNumber': 'telephoneNumber',
'urn:mace:dir:attribute-def:teletexTerminalIdentifier': 'teletexTerminalIdentifier', DEF+'teletexTerminalIdentifier': 'teletexTerminalIdentifier',
'urn:mace:dir:attribute-def:telexNumber': 'telexNumber', DEF+'telexNumber': 'telexNumber',
'urn:mace:dir:attribute-def:textEncodedORAddress': 'textEncodedORAddress', DEF+'textEncodedORAddress': 'textEncodedORAddress',
'urn:mace:dir:attribute-def:title': 'title', DEF+'title': 'title',
'urn:mace:dir:attribute-def:uid': 'uid', DEF+'uid': 'uid',
'urn:mace:dir:attribute-def:uniqueIdentifier': 'uniqueIdentifier', DEF+'uniqueIdentifier': 'uniqueIdentifier',
'urn:mace:dir:attribute-def:uniqueMember': 'uniqueMember', DEF+'uniqueMember': 'uniqueMember',
'urn:mace:dir:attribute-def:userCertificate': 'userCertificate', DEF+'userCertificate': 'userCertificate',
'urn:mace:dir:attribute-def:userClass': 'userClass', DEF+'userClass': 'userClass',
'urn:mace:dir:attribute-def:userPKCS12': 'userPKCS12', DEF+'userPKCS12': 'userPKCS12',
'urn:mace:dir:attribute-def:userPassword': 'userPassword', DEF+'userPassword': 'userPassword',
'urn:mace:dir:attribute-def:userSMIMECertificate': 'userSMIMECertificate', DEF+'userSMIMECertificate': 'userSMIMECertificate',
'urn:mace:dir:attribute-def:userid': 'userid', DEF+'userid': 'userid',
'urn:mace:dir:attribute-def:x121Address': 'x121Address', DEF+'x121Address': 'x121Address',
'urn:mace:dir:attribute-def:x500UniqueIdentifier': 'x500UniqueIdentifier', DEF+'x500UniqueIdentifier': 'x500UniqueIdentifier',
}, },
"to": { 'to': {
'aRecord': 'urn:mace:dir:attribute-def:aRecord', 'aRecord': DEF+'aRecord',
'aliasedEntryName': 'urn:mace:dir:attribute-def:aliasedEntryName', 'aliasedEntryName': DEF+'aliasedEntryName',
'aliasedObjectName': 'urn:mace:dir:attribute-def:aliasedObjectName', 'aliasedObjectName': DEF+'aliasedObjectName',
'associatedDomain': 'urn:mace:dir:attribute-def:associatedDomain', 'associatedDomain': DEF+'associatedDomain',
'associatedName': 'urn:mace:dir:attribute-def:associatedName', 'associatedName': DEF+'associatedName',
'audio': 'urn:mace:dir:attribute-def:audio', 'audio': DEF+'audio',
'authorityRevocationList': 'urn:mace:dir:attribute-def:authorityRevocationList', 'authorityRevocationList': DEF+'authorityRevocationList',
'buildingName': 'urn:mace:dir:attribute-def:buildingName', 'buildingName': DEF+'buildingName',
'businessCategory': 'urn:mace:dir:attribute-def:businessCategory', 'businessCategory': DEF+'businessCategory',
'c': 'urn:mace:dir:attribute-def:c', 'c': DEF+'c',
'cACertificate': 'urn:mace:dir:attribute-def:cACertificate', 'cACertificate': DEF+'cACertificate',
'cNAMERecord': 'urn:mace:dir:attribute-def:cNAMERecord', 'cNAMERecord': DEF+'cNAMERecord',
'carLicense': 'urn:mace:dir:attribute-def:carLicense', 'carLicense': DEF+'carLicense',
'certificateRevocationList': 'urn:mace:dir:attribute-def:certificateRevocationList', 'certificateRevocationList': DEF+'certificateRevocationList',
'cn': 'urn:mace:dir:attribute-def:cn', 'cn': DEF+'cn',
'co': 'urn:mace:dir:attribute-def:co', 'co': DEF+'co',
'commonName': 'urn:mace:dir:attribute-def:commonName', 'commonName': DEF+'commonName',
'countryName': 'urn:mace:dir:attribute-def:countryName', 'countryName': DEF+'countryName',
'crossCertificatePair': 'urn:mace:dir:attribute-def:crossCertificatePair', 'crossCertificatePair': DEF+'crossCertificatePair',
'dITRedirect': 'urn:mace:dir:attribute-def:dITRedirect', 'dITRedirect': DEF+'dITRedirect',
'dSAQuality': 'urn:mace:dir:attribute-def:dSAQuality', 'dSAQuality': DEF+'dSAQuality',
'dc': 'urn:mace:dir:attribute-def:dc', 'dc': DEF+'dc',
'deltaRevocationList': 'urn:mace:dir:attribute-def:deltaRevocationList', 'deltaRevocationList': DEF+'deltaRevocationList',
'departmentNumber': 'urn:mace:dir:attribute-def:departmentNumber', 'departmentNumber': DEF+'departmentNumber',
'description': 'urn:mace:dir:attribute-def:description', 'description': DEF+'description',
'destinationIndicator': 'urn:mace:dir:attribute-def:destinationIndicator', 'destinationIndicator': DEF+'destinationIndicator',
'displayName': 'urn:mace:dir:attribute-def:displayName', 'displayName': DEF+'displayName',
'distinguishedName': 'urn:mace:dir:attribute-def:distinguishedName', 'distinguishedName': DEF+'distinguishedName',
'dmdName': 'urn:mace:dir:attribute-def:dmdName', 'dmdName': DEF+'dmdName',
'dnQualifier': 'urn:mace:dir:attribute-def:dnQualifier', 'dnQualifier': DEF+'dnQualifier',
'documentAuthor': 'urn:mace:dir:attribute-def:documentAuthor', 'documentAuthor': DEF+'documentAuthor',
'documentIdentifier': 'urn:mace:dir:attribute-def:documentIdentifier', 'documentIdentifier': DEF+'documentIdentifier',
'documentLocation': 'urn:mace:dir:attribute-def:documentLocation', 'documentLocation': DEF+'documentLocation',
'documentPublisher': 'urn:mace:dir:attribute-def:documentPublisher', 'documentPublisher': DEF+'documentPublisher',
'documentTitle': 'urn:mace:dir:attribute-def:documentTitle', 'documentTitle': DEF+'documentTitle',
'documentVersion': 'urn:mace:dir:attribute-def:documentVersion', 'documentVersion': DEF+'documentVersion',
'domainComponent': 'urn:mace:dir:attribute-def:domainComponent', 'domainComponent': DEF+'domainComponent',
'drink': 'urn:mace:dir:attribute-def:drink', 'drink': DEF+'drink',
'eduOrgHomePageURI': 'urn:mace:dir:attribute-def:eduOrgHomePageURI', 'eduOrgHomePageURI': DEF+'eduOrgHomePageURI',
'eduOrgIdentityAuthNPolicyURI': 'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI', 'eduOrgIdentityAuthNPolicyURI': DEF+'eduOrgIdentityAuthNPolicyURI',
'eduOrgLegalName': 'urn:mace:dir:attribute-def:eduOrgLegalName', 'eduOrgLegalName': DEF+'eduOrgLegalName',
'eduOrgSuperiorURI': 'urn:mace:dir:attribute-def:eduOrgSuperiorURI', 'eduOrgSuperiorURI': DEF+'eduOrgSuperiorURI',
'eduOrgWhitePagesURI': 'urn:mace:dir:attribute-def:eduOrgWhitePagesURI', 'eduOrgWhitePagesURI': DEF+'eduOrgWhitePagesURI',
'eduPersonAffiliation': 'urn:mace:dir:attribute-def:eduPersonAffiliation', 'eduPersonAffiliation': DEF+'eduPersonAffiliation',
'eduPersonEntitlement': 'urn:mace:dir:attribute-def:eduPersonEntitlement', 'eduPersonEntitlement': DEF+'eduPersonEntitlement',
'eduPersonNickname': 'urn:mace:dir:attribute-def:eduPersonNickname', 'eduPersonNickname': DEF+'eduPersonNickname',
'eduPersonOrgDN': 'urn:mace:dir:attribute-def:eduPersonOrgDN', 'eduPersonOrgDN': DEF+'eduPersonOrgDN',
'eduPersonOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonOrgUnitDN', 'eduPersonOrgUnitDN': DEF+'eduPersonOrgUnitDN',
'eduPersonPrimaryAffiliation': 'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation', 'eduPersonPrimaryAffiliation': DEF+'eduPersonPrimaryAffiliation',
'eduPersonPrimaryOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN', 'eduPersonPrimaryOrgUnitDN': DEF+'eduPersonPrimaryOrgUnitDN',
'eduPersonPrincipalName': 'urn:mace:dir:attribute-def:eduPersonPrincipalName', 'eduPersonPrincipalName': DEF+'eduPersonPrincipalName',
'eduPersonScopedAffiliation': 'urn:mace:dir:attribute-def:eduPersonScopedAffiliation', 'eduPersonScopedAffiliation': DEF+'eduPersonScopedAffiliation',
'eduPersonTargetedID': 'urn:mace:dir:attribute-def:eduPersonTargetedID', 'eduPersonTargetedID': DEF+'eduPersonTargetedID',
'email': 'urn:mace:dir:attribute-def:email', 'email': DEF+'email',
'emailAddress': 'urn:mace:dir:attribute-def:emailAddress', 'emailAddress': DEF+'emailAddress',
'employeeNumber': 'urn:mace:dir:attribute-def:employeeNumber', 'employeeNumber': DEF+'employeeNumber',
'employeeType': 'urn:mace:dir:attribute-def:employeeType', 'employeeType': DEF+'employeeType',
'enhancedSearchGuide': 'urn:mace:dir:attribute-def:enhancedSearchGuide', 'enhancedSearchGuide': DEF+'enhancedSearchGuide',
'facsimileTelephoneNumber': 'urn:mace:dir:attribute-def:facsimileTelephoneNumber', 'facsimileTelephoneNumber': DEF+'facsimileTelephoneNumber',
'favouriteDrink': 'urn:mace:dir:attribute-def:favouriteDrink', 'favouriteDrink': DEF+'favouriteDrink',
'fax': 'urn:mace:dir:attribute-def:fax', 'fax': DEF+'fax',
'federationFeideSchemaVersion': 'urn:mace:dir:attribute-def:federationFeideSchemaVersion', 'federationFeideSchemaVersion': DEF+'federationFeideSchemaVersion',
'friendlyCountryName': 'urn:mace:dir:attribute-def:friendlyCountryName', 'friendlyCountryName': DEF+'friendlyCountryName',
'generationQualifier': 'urn:mace:dir:attribute-def:generationQualifier', 'generationQualifier': DEF+'generationQualifier',
'givenName': 'urn:mace:dir:attribute-def:givenName', 'givenName': DEF+'givenName',
'gn': 'urn:mace:dir:attribute-def:gn', 'gn': DEF+'gn',
'homePhone': 'urn:mace:dir:attribute-def:homePhone', 'homePhone': DEF+'homePhone',
'homePostalAddress': 'urn:mace:dir:attribute-def:homePostalAddress', 'homePostalAddress': DEF+'homePostalAddress',
'homeTelephoneNumber': 'urn:mace:dir:attribute-def:homeTelephoneNumber', 'homeTelephoneNumber': DEF+'homeTelephoneNumber',
'host': 'urn:mace:dir:attribute-def:host', 'host': DEF+'host',
'houseIdentifier': 'urn:mace:dir:attribute-def:houseIdentifier', 'houseIdentifier': DEF+'houseIdentifier',
'info': 'urn:mace:dir:attribute-def:info', 'info': DEF+'info',
'initials': 'urn:mace:dir:attribute-def:initials', 'initials': DEF+'initials',
'internationaliSDNNumber': 'urn:mace:dir:attribute-def:internationaliSDNNumber', 'internationaliSDNNumber': DEF+'internationaliSDNNumber',
'janetMailbox': 'urn:mace:dir:attribute-def:janetMailbox', 'janetMailbox': DEF+'janetMailbox',
'jpegPhoto': 'urn:mace:dir:attribute-def:jpegPhoto', 'jpegPhoto': DEF+'jpegPhoto',
'knowledgeInformation': 'urn:mace:dir:attribute-def:knowledgeInformation', 'knowledgeInformation': DEF+'knowledgeInformation',
'l': 'urn:mace:dir:attribute-def:l', 'l': DEF+'l',
'labeledURI': 'urn:mace:dir:attribute-def:labeledURI', 'labeledURI': DEF+'labeledURI',
'localityName': 'urn:mace:dir:attribute-def:localityName', 'localityName': DEF+'localityName',
'mDRecord': 'urn:mace:dir:attribute-def:mDRecord', 'mDRecord': DEF+'mDRecord',
'mXRecord': 'urn:mace:dir:attribute-def:mXRecord', 'mXRecord': DEF+'mXRecord',
'mail': 'urn:mace:dir:attribute-def:mail', 'mail': DEF+'mail',
'mailPreferenceOption': 'urn:mace:dir:attribute-def:mailPreferenceOption', 'mailPreferenceOption': DEF+'mailPreferenceOption',
'manager': 'urn:mace:dir:attribute-def:manager', 'manager': DEF+'manager',
'member': 'urn:mace:dir:attribute-def:member', 'member': DEF+'member',
'mobile': 'urn:mace:dir:attribute-def:mobile', 'mobile': DEF+'mobile',
'mobileTelephoneNumber': 'urn:mace:dir:attribute-def:mobileTelephoneNumber', 'mobileTelephoneNumber': DEF+'mobileTelephoneNumber',
'nSRecord': 'urn:mace:dir:attribute-def:nSRecord', 'nSRecord': DEF+'nSRecord',
'name': 'urn:mace:dir:attribute-def:name', 'name': DEF+'name',
'norEduOrgAcronym': 'urn:mace:dir:attribute-def:norEduOrgAcronym', 'norEduOrgAcronym': DEF+'norEduOrgAcronym',
'norEduOrgNIN': 'urn:mace:dir:attribute-def:norEduOrgNIN', 'norEduOrgNIN': DEF+'norEduOrgNIN',
'norEduOrgSchemaVersion': 'urn:mace:dir:attribute-def:norEduOrgSchemaVersion', 'norEduOrgSchemaVersion': DEF+'norEduOrgSchemaVersion',
'norEduOrgUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier', 'norEduOrgUniqueIdentifier': DEF+'norEduOrgUniqueIdentifier',
'norEduOrgUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUniqueNumber', 'norEduOrgUniqueNumber': DEF+'norEduOrgUniqueNumber',
'norEduOrgUnitUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier', 'norEduOrgUnitUniqueIdentifier': DEF+'norEduOrgUnitUniqueIdentifier',
'norEduOrgUnitUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber', 'norEduOrgUnitUniqueNumber': DEF+'norEduOrgUnitUniqueNumber',
'norEduPersonBirthDate': 'urn:mace:dir:attribute-def:norEduPersonBirthDate', 'norEduPersonBirthDate': DEF+'norEduPersonBirthDate',
'norEduPersonLIN': 'urn:mace:dir:attribute-def:norEduPersonLIN', 'norEduPersonLIN': DEF+'norEduPersonLIN',
'norEduPersonNIN': 'urn:mace:dir:attribute-def:norEduPersonNIN', 'norEduPersonNIN': DEF+'norEduPersonNIN',
'o': 'urn:mace:dir:attribute-def:o', 'o': DEF+'o',
'objectClass': 'urn:mace:dir:attribute-def:objectClass', 'objectClass': DEF+'objectClass',
'organizationName': 'urn:mace:dir:attribute-def:organizationName', 'organizationName': DEF+'organizationName',
'organizationalStatus': 'urn:mace:dir:attribute-def:organizationalStatus', 'organizationalStatus': DEF+'organizationalStatus',
'organizationalUnitName': 'urn:mace:dir:attribute-def:organizationalUnitName', 'organizationalUnitName': DEF+'organizationalUnitName',
'otherMailbox': 'urn:mace:dir:attribute-def:otherMailbox', 'otherMailbox': DEF+'otherMailbox',
'ou': 'urn:mace:dir:attribute-def:ou', 'ou': DEF+'ou',
'owner': 'urn:mace:dir:attribute-def:owner', 'owner': DEF+'owner',
'pager': 'urn:mace:dir:attribute-def:pager', 'pager': DEF+'pager',
'pagerTelephoneNumber': 'urn:mace:dir:attribute-def:pagerTelephoneNumber', 'pagerTelephoneNumber': DEF+'pagerTelephoneNumber',
'personalSignature': 'urn:mace:dir:attribute-def:personalSignature', 'personalSignature': DEF+'personalSignature',
'personalTitle': 'urn:mace:dir:attribute-def:personalTitle', 'personalTitle': DEF+'personalTitle',
'photo': 'urn:mace:dir:attribute-def:photo', 'photo': DEF+'photo',
'physicalDeliveryOfficeName': 'urn:mace:dir:attribute-def:physicalDeliveryOfficeName', 'physicalDeliveryOfficeName': DEF+'physicalDeliveryOfficeName',
'pkcs9email': 'urn:mace:dir:attribute-def:pkcs9email', 'pkcs9email': DEF+'pkcs9email',
'postOfficeBox': 'urn:mace:dir:attribute-def:postOfficeBox', 'postOfficeBox': DEF+'postOfficeBox',
'postalAddress': 'urn:mace:dir:attribute-def:postalAddress', 'postalAddress': DEF+'postalAddress',
'postalCode': 'urn:mace:dir:attribute-def:postalCode', 'postalCode': DEF+'postalCode',
'preferredDeliveryMethod': 'urn:mace:dir:attribute-def:preferredDeliveryMethod', 'preferredDeliveryMethod': DEF+'preferredDeliveryMethod',
'preferredLanguage': 'urn:mace:dir:attribute-def:preferredLanguage', 'preferredLanguage': DEF+'preferredLanguage',
'presentationAddress': 'urn:mace:dir:attribute-def:presentationAddress', 'presentationAddress': DEF+'presentationAddress',
'protocolInformation': 'urn:mace:dir:attribute-def:protocolInformation', 'protocolInformation': DEF+'protocolInformation',
'pseudonym': 'urn:mace:dir:attribute-def:pseudonym', 'pseudonym': DEF+'pseudonym',
'registeredAddress': 'urn:mace:dir:attribute-def:registeredAddress', 'registeredAddress': DEF+'registeredAddress',
'rfc822Mailbox': 'urn:mace:dir:attribute-def:rfc822Mailbox', 'rfc822Mailbox': DEF+'rfc822Mailbox',
'roleOccupant': 'urn:mace:dir:attribute-def:roleOccupant', 'roleOccupant': DEF+'roleOccupant',
'roomNumber': 'urn:mace:dir:attribute-def:roomNumber', 'roomNumber': DEF+'roomNumber',
'sOARecord': 'urn:mace:dir:attribute-def:sOARecord', 'sOARecord': DEF+'sOARecord',
'searchGuide': 'urn:mace:dir:attribute-def:searchGuide', 'searchGuide': DEF+'searchGuide',
'secretary': 'urn:mace:dir:attribute-def:secretary', 'secretary': DEF+'secretary',
'seeAlso': 'urn:mace:dir:attribute-def:seeAlso', 'seeAlso': DEF+'seeAlso',
'serialNumber': 'urn:mace:dir:attribute-def:serialNumber', 'serialNumber': DEF+'serialNumber',
'singleLevelQuality': 'urn:mace:dir:attribute-def:singleLevelQuality', 'singleLevelQuality': DEF+'singleLevelQuality',
'sn': 'urn:mace:dir:attribute-def:sn', 'sn': DEF+'sn',
'st': 'urn:mace:dir:attribute-def:st', 'st': DEF+'st',
'stateOrProvinceName': 'urn:mace:dir:attribute-def:stateOrProvinceName', 'stateOrProvinceName': DEF+'stateOrProvinceName',
'street': 'urn:mace:dir:attribute-def:street', 'street': DEF+'street',
'streetAddress': 'urn:mace:dir:attribute-def:streetAddress', 'streetAddress': DEF+'streetAddress',
'subtreeMaximumQuality': 'urn:mace:dir:attribute-def:subtreeMaximumQuality', 'subtreeMaximumQuality': DEF+'subtreeMaximumQuality',
'subtreeMinimumQuality': 'urn:mace:dir:attribute-def:subtreeMinimumQuality', 'subtreeMinimumQuality': DEF+'subtreeMinimumQuality',
'supportedAlgorithms': 'urn:mace:dir:attribute-def:supportedAlgorithms', 'supportedAlgorithms': DEF+'supportedAlgorithms',
'supportedApplicationContext': 'urn:mace:dir:attribute-def:supportedApplicationContext', 'supportedApplicationContext': DEF+'supportedApplicationContext',
'surname': 'urn:mace:dir:attribute-def:surname', 'surname': DEF+'surname',
'telephoneNumber': 'urn:mace:dir:attribute-def:telephoneNumber', 'telephoneNumber': DEF+'telephoneNumber',
'teletexTerminalIdentifier': 'urn:mace:dir:attribute-def:teletexTerminalIdentifier', 'teletexTerminalIdentifier': DEF+'teletexTerminalIdentifier',
'telexNumber': 'urn:mace:dir:attribute-def:telexNumber', 'telexNumber': DEF+'telexNumber',
'textEncodedORAddress': 'urn:mace:dir:attribute-def:textEncodedORAddress', 'textEncodedORAddress': DEF+'textEncodedORAddress',
'title': 'urn:mace:dir:attribute-def:title', 'title': DEF+'title',
'uid': 'urn:mace:dir:attribute-def:uid', 'uid': DEF+'uid',
'uniqueIdentifier': 'urn:mace:dir:attribute-def:uniqueIdentifier', 'uniqueIdentifier': DEF+'uniqueIdentifier',
'uniqueMember': 'urn:mace:dir:attribute-def:uniqueMember', 'uniqueMember': DEF+'uniqueMember',
'userCertificate': 'urn:mace:dir:attribute-def:userCertificate', 'userCertificate': DEF+'userCertificate',
'userClass': 'urn:mace:dir:attribute-def:userClass', 'userClass': DEF+'userClass',
'userPKCS12': 'urn:mace:dir:attribute-def:userPKCS12', 'userPKCS12': DEF+'userPKCS12',
'userPassword': 'urn:mace:dir:attribute-def:userPassword', 'userPassword': DEF+'userPassword',
'userSMIMECertificate': 'urn:mace:dir:attribute-def:userSMIMECertificate', 'userSMIMECertificate': DEF+'userSMIMECertificate',
'userid': 'urn:mace:dir:attribute-def:userid', 'userid': DEF+'userid',
'x121Address': 'urn:mace:dir:attribute-def:x121Address', 'x121Address': DEF+'x121Address',
'x500UniqueIdentifier': 'urn:mace:dir:attribute-def:x500UniqueIdentifier', 'x500UniqueIdentifier': DEF+'x500UniqueIdentifier',
} }
} }

View File

@@ -1,104 +1,52 @@
__author__ = 'rolandh' EDUCOURSE_OID = 'urn:oid:1.3.6.1.4.1.5923.1.6.1.'
EDUPERSON_OID = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.'
EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." NETSCAPE_LDAP = 'urn:oid:2.16.840.1.113730.3.1.'
X500ATTR_OID = "urn:oid:2.5.4." NOREDUPERSON_OID = 'urn:oid:1.3.6.1.4.1.2428.90.1.'
NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." PKCS_9 = 'urn:oid:1.2.840.113549.1.9.1.'
NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.1." SCHAC = 'urn:oid:1.3.6.1.4.1.25178.1.2.'
SIS = 'urn:oid:1.2.752.194.10.2.'
UCL_DIR_PILOT = 'urn:oid:0.9.2342.19200300.100.1.' UCL_DIR_PILOT = 'urn:oid:0.9.2342.19200300.100.1.'
PKCS_9 = "urn:oid:1.2.840.113549.1.9.1." UMICH = 'urn:oid:1.3.6.1.4.1.250.1.57.'
UMICH = "urn:oid:1.3.6.1.4.1.250.1.57." X500ATTR_OID = 'urn:oid:2.5.4.'
SCHAC = "urn:oid:1.3.6.1.4.1.25178.1.2."
#urn:oid:1.3.6.1.4.1.1466.115.121.1.26
MAP = { MAP = {
"identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", 'identifier': 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri',
"fro": { 'fro': {
EDUCOURSE_OID+'1': 'eduCourseOffering',
EDUCOURSE_OID+'2': 'eduCourseMember',
EDUPERSON_OID+'1': 'eduPersonAffiliation',
EDUPERSON_OID+'2': 'eduPersonNickname', EDUPERSON_OID+'2': 'eduPersonNickname',
EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', EDUPERSON_OID+'3': 'eduPersonOrgDN',
EDUPERSON_OID+'11': 'eduPersonAssurance',
EDUPERSON_OID+'10': 'eduPersonTargetedID',
EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', EDUPERSON_OID+'4': 'eduPersonOrgUnitDN',
EDUPERSON_OID+'5': 'eduPersonPrimaryAffiliation',
EDUPERSON_OID+'6': 'eduPersonPrincipalName',
EDUPERSON_OID+'7': 'eduPersonEntitlement',
EDUPERSON_OID+'8': 'eduPersonPrimaryOrgUnitDN',
EDUPERSON_OID+'9': 'eduPersonScopedAffiliation',
EDUPERSON_OID+'10': 'eduPersonTargetedID',
EDUPERSON_OID+'11': 'eduPersonAssurance',
NETSCAPE_LDAP+'1': 'carLicense',
NETSCAPE_LDAP+'2': 'departmentNumber',
NETSCAPE_LDAP+'3': 'employeeNumber',
NETSCAPE_LDAP+'4': 'employeeType',
NETSCAPE_LDAP+'39': 'preferredLanguage',
NETSCAPE_LDAP+'40': 'userSMIMECertificate',
NETSCAPE_LDAP+'216': 'userPKCS12',
NETSCAPE_LDAP+'241': 'displayName',
NOREDUPERSON_OID+'1': 'norEduOrgUniqueNumber',
NOREDUPERSON_OID+'2': 'norEduOrgUnitUniqueNumber',
NOREDUPERSON_OID+'3': 'norEduPersonBirthDate',
NOREDUPERSON_OID+'4': 'norEduPersonLIN',
NOREDUPERSON_OID+'5': 'norEduPersonNIN',
NOREDUPERSON_OID+'6': 'norEduOrgAcronym', NOREDUPERSON_OID+'6': 'norEduOrgAcronym',
NOREDUPERSON_OID+'7': 'norEduOrgUniqueIdentifier', NOREDUPERSON_OID+'7': 'norEduOrgUniqueIdentifier',
NOREDUPERSON_OID+'4': 'norEduPersonLIN',
EDUPERSON_OID+'1': 'eduPersonAffiliation',
NOREDUPERSON_OID+'2': 'norEduOrgUnitUniqueNumber',
NETSCAPE_LDAP+'40': 'userSMIMECertificate',
NOREDUPERSON_OID+'1': 'norEduOrgUniqueNumber',
NETSCAPE_LDAP+'241': 'displayName',
UCL_DIR_PILOT+'37': 'associatedDomain',
EDUPERSON_OID+'6': 'eduPersonPrincipalName',
NOREDUPERSON_OID+'8': 'norEduOrgUnitUniqueIdentifier', NOREDUPERSON_OID+'8': 'norEduOrgUnitUniqueIdentifier',
NOREDUPERSON_OID+'9': 'federationFeideSchemaVersion', NOREDUPERSON_OID+'9': 'federationFeideSchemaVersion',
X500ATTR_OID+'53': 'deltaRevocationList', NOREDUPERSON_OID+'10': 'norEduPersonLegalName',
X500ATTR_OID+'52': 'supportedAlgorithms', NOREDUPERSON_OID+'11': 'norEduOrgSchemaVersion',
X500ATTR_OID+'51': 'houseIdentifier', NOREDUPERSON_OID+'12': 'norEduOrgNIN',
X500ATTR_OID+'50': 'uniqueMember',
X500ATTR_OID+'19': 'physicalDeliveryOfficeName',
X500ATTR_OID+'18': 'postOfficeBox',
X500ATTR_OID+'17': 'postalCode',
X500ATTR_OID+'16': 'postalAddress',
X500ATTR_OID+'15': 'businessCategory',
X500ATTR_OID+'14': 'searchGuide',
EDUPERSON_OID+'5': 'eduPersonPrimaryAffiliation',
X500ATTR_OID+'12': 'title',
X500ATTR_OID+'11': 'ou',
X500ATTR_OID+'10': 'o',
X500ATTR_OID+'37': 'cACertificate',
X500ATTR_OID+'36': 'userCertificate',
X500ATTR_OID+'31': 'member',
X500ATTR_OID+'30': 'supportedApplicationContext',
X500ATTR_OID+'33': 'roleOccupant',
X500ATTR_OID+'32': 'owner',
NETSCAPE_LDAP+'1': 'carLicense',
PKCS_9+'1': 'email', PKCS_9+'1': 'email',
NETSCAPE_LDAP+'3': 'employeeNumber',
NETSCAPE_LDAP+'2': 'departmentNumber',
X500ATTR_OID+'39': 'certificateRevocationList',
X500ATTR_OID+'38': 'authorityRevocationList',
NETSCAPE_LDAP+'216': 'userPKCS12',
EDUPERSON_OID+'8': 'eduPersonPrimaryOrgUnitDN',
X500ATTR_OID+'9': 'street',
X500ATTR_OID+'8': 'st',
NETSCAPE_LDAP+'39': 'preferredLanguage',
EDUPERSON_OID+'7': 'eduPersonEntitlement',
X500ATTR_OID+'2': 'knowledgeInformation',
X500ATTR_OID+'7': 'l',
X500ATTR_OID+'6': 'c',
X500ATTR_OID+'5': 'serialNumber',
X500ATTR_OID+'4': 'sn',
X500ATTR_OID+'3': 'cn',
UCL_DIR_PILOT+'60': 'jpegPhoto',
X500ATTR_OID+'65': 'pseudonym',
NOREDUPERSON_OID+'5': 'norEduPersonNIN',
UCL_DIR_PILOT+'3': 'mail',
UCL_DIR_PILOT+'25': 'dc',
X500ATTR_OID+'40': 'crossCertificatePair',
X500ATTR_OID+'42': 'givenName',
X500ATTR_OID+'43': 'initials',
X500ATTR_OID+'44': 'generationQualifier',
X500ATTR_OID+'45': 'x500UniqueIdentifier',
X500ATTR_OID+'46': 'dnQualifier',
X500ATTR_OID+'47': 'enhancedSearchGuide',
X500ATTR_OID+'48': 'protocolInformation',
X500ATTR_OID+'54': 'dmdName',
NETSCAPE_LDAP+'4': 'employeeType',
X500ATTR_OID+'22': 'teletexTerminalIdentifier',
X500ATTR_OID+'23': 'facsimileTelephoneNumber',
X500ATTR_OID+'20': 'telephoneNumber',
X500ATTR_OID+'21': 'telexNumber',
X500ATTR_OID+'26': 'registeredAddress',
X500ATTR_OID+'27': 'destinationIndicator',
X500ATTR_OID+'24': 'x121Address',
X500ATTR_OID+'25': 'internationaliSDNNumber',
X500ATTR_OID+'28': 'preferredDeliveryMethod',
X500ATTR_OID+'29': 'presentationAddress',
EDUPERSON_OID+'3': 'eduPersonOrgDN',
NOREDUPERSON_OID+'3': 'norEduPersonBirthDate',
UMICH+'57': 'labeledURI',
UCL_DIR_PILOT+'1': 'uid',
UCL_DIR_PILOT+'43': 'co',
SCHAC+'1': 'schacMotherTongue', SCHAC+'1': 'schacMotherTongue',
SCHAC+'2': 'schacGender', SCHAC+'2': 'schacGender',
SCHAC+'3': 'schacDateOfBirth', SCHAC+'3': 'schacDateOfBirth',
@@ -119,132 +67,177 @@ MAP = {
SCHAC+'19': 'schacUserStatus', SCHAC+'19': 'schacUserStatus',
SCHAC+'20': 'schacProjectMembership', SCHAC+'20': 'schacProjectMembership',
SCHAC+'21': 'schacProjectSpecificRole', SCHAC+'21': 'schacProjectSpecificRole',
}, SIS+'1': 'sisLegalGuardianFor',
"to": { SIS+'2': 'sisSchoolGrade',
'cn': X500ATTR_OID+'3', UCL_DIR_PILOT+'1': 'uid',
'commonName': X500ATTR_OID+'3', UCL_DIR_PILOT+'3': 'mail',
'roleOccupant': X500ATTR_OID+'33', UCL_DIR_PILOT+'25': 'dc',
'gn': X500ATTR_OID+'42', UCL_DIR_PILOT+'37': 'associatedDomain',
'norEduPersonNIN': NOREDUPERSON_OID+'5', UCL_DIR_PILOT+'43': 'co',
'title': X500ATTR_OID+'12', UCL_DIR_PILOT+'60': 'jpegPhoto',
'facsimileTelephoneNumber': X500ATTR_OID+'23', UMICH+'57': 'labeledURI',
'mail': UCL_DIR_PILOT+'3', X500ATTR_OID+'2': 'knowledgeInformation',
'postOfficeBox': X500ATTR_OID+'18', X500ATTR_OID+'3': 'cn',
'fax': X500ATTR_OID+'23', X500ATTR_OID+'4': 'sn',
'telephoneNumber': X500ATTR_OID+'20', X500ATTR_OID+'5': 'serialNumber',
'norEduPersonBirthDate': NOREDUPERSON_OID+'3', X500ATTR_OID+'6': 'c',
'rfc822Mailbox': UCL_DIR_PILOT+'3', X500ATTR_OID+'7': 'l',
'dc': UCL_DIR_PILOT+'25', X500ATTR_OID+'8': 'st',
'countryName': X500ATTR_OID+'6', X500ATTR_OID+'9': 'street',
'emailAddress': PKCS_9+'1', X500ATTR_OID+'10': 'o',
'employeeNumber': NETSCAPE_LDAP+'3', X500ATTR_OID+'11': 'ou',
'organizationName': X500ATTR_OID+'10', X500ATTR_OID+'12': 'title',
'eduPersonAssurance': EDUPERSON_OID+'11', X500ATTR_OID+'14': 'searchGuide',
'norEduOrgAcronym': NOREDUPERSON_OID+'6', X500ATTR_OID+'15': 'businessCategory',
'registeredAddress': X500ATTR_OID+'26', X500ATTR_OID+'16': 'postalAddress',
'physicalDeliveryOfficeName': X500ATTR_OID+'19', X500ATTR_OID+'17': 'postalCode',
X500ATTR_OID+'18': 'postOfficeBox',
X500ATTR_OID+'19': 'physicalDeliveryOfficeName',
X500ATTR_OID+'20': 'telephoneNumber',
X500ATTR_OID+'21': 'telexNumber',
X500ATTR_OID+'22': 'teletexTerminalIdentifier',
X500ATTR_OID+'23': 'facsimileTelephoneNumber',
X500ATTR_OID+'24': 'x121Address',
X500ATTR_OID+'25': 'internationaliSDNNumber',
X500ATTR_OID+'26': 'registeredAddress',
X500ATTR_OID+'27': 'destinationIndicator',
X500ATTR_OID+'28': 'preferredDeliveryMethod',
X500ATTR_OID+'29': 'presentationAddress',
X500ATTR_OID+'30': 'supportedApplicationContext',
X500ATTR_OID+'31': 'member',
X500ATTR_OID+'32': 'owner',
X500ATTR_OID+'33': 'roleOccupant',
X500ATTR_OID+'36': 'userCertificate',
X500ATTR_OID+'37': 'cACertificate',
X500ATTR_OID+'38': 'authorityRevocationList',
X500ATTR_OID+'39': 'certificateRevocationList',
X500ATTR_OID+'40': 'crossCertificatePair',
X500ATTR_OID+'42': 'givenName',
X500ATTR_OID+'43': 'initials',
X500ATTR_OID+'44': 'generationQualifier',
X500ATTR_OID+'45': 'x500UniqueIdentifier',
X500ATTR_OID+'46': 'dnQualifier',
X500ATTR_OID+'47': 'enhancedSearchGuide',
X500ATTR_OID+'48': 'protocolInformation',
X500ATTR_OID+'50': 'uniqueMember',
X500ATTR_OID+'51': 'houseIdentifier',
X500ATTR_OID+'52': 'supportedAlgorithms',
X500ATTR_OID+'53': 'deltaRevocationList',
X500ATTR_OID+'54': 'dmdName',
X500ATTR_OID+'65': 'pseudonym',
},
'to': {
'associatedDomain': UCL_DIR_PILOT+'37', 'associatedDomain': UCL_DIR_PILOT+'37',
'l': X500ATTR_OID+'7',
'stateOrProvinceName': X500ATTR_OID+'8',
'federationFeideSchemaVersion': NOREDUPERSON_OID+'9',
'pkcs9email': PKCS_9+'1',
'givenName': X500ATTR_OID+'42',
'givenname': X500ATTR_OID+'42',
'x500UniqueIdentifier': X500ATTR_OID+'45',
'eduPersonNickname': EDUPERSON_OID+'2',
'houseIdentifier': X500ATTR_OID+'51',
'street': X500ATTR_OID+'9',
'supportedAlgorithms': X500ATTR_OID+'52',
'preferredLanguage': NETSCAPE_LDAP+'39',
'postalAddress': X500ATTR_OID+'16',
'email': PKCS_9+'1',
'norEduOrgUnitUniqueIdentifier': NOREDUPERSON_OID+'8',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'8',
'c': X500ATTR_OID+'6',
'teletexTerminalIdentifier': X500ATTR_OID+'22',
'o': X500ATTR_OID+'10',
'cACertificate': X500ATTR_OID+'37',
'telexNumber': X500ATTR_OID+'21',
'ou': X500ATTR_OID+'11',
'initials': X500ATTR_OID+'43',
'eduPersonOrgUnitDN': EDUPERSON_OID+'4',
'deltaRevocationList': X500ATTR_OID+'53',
'norEduPersonLIN': NOREDUPERSON_OID+'4',
'supportedApplicationContext': X500ATTR_OID+'30',
'eduPersonEntitlement': EDUPERSON_OID+'7',
'generationQualifier': X500ATTR_OID+'44',
'eduPersonAffiliation': EDUPERSON_OID+'1',
'edupersonaffiliation': EDUPERSON_OID+'1',
'eduPersonPrincipalName': EDUPERSON_OID+'6',
'edupersonprincipalname': EDUPERSON_OID+'6',
'eppn': EDUPERSON_OID+'6',
'localityName': X500ATTR_OID+'7',
'owner': X500ATTR_OID+'32',
'norEduOrgUnitUniqueNumber': NOREDUPERSON_OID+'2',
'searchGuide': X500ATTR_OID+'14',
'certificateRevocationList': X500ATTR_OID+'39',
'organizationalUnitName': X500ATTR_OID+'11',
'userCertificate': X500ATTR_OID+'36',
'preferredDeliveryMethod': X500ATTR_OID+'28',
'internationaliSDNNumber': X500ATTR_OID+'25',
'uniqueMember': X500ATTR_OID+'50',
'departmentNumber': NETSCAPE_LDAP+'2',
'enhancedSearchGuide': X500ATTR_OID+'47',
'userPKCS12': NETSCAPE_LDAP+'216',
'eduPersonTargetedID': EDUPERSON_OID+'10',
'norEduOrgUniqueNumber': NOREDUPERSON_OID+'1',
'x121Address': X500ATTR_OID+'24',
'destinationIndicator': X500ATTR_OID+'27',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'5',
'surname': X500ATTR_OID+'4',
'jpegPhoto': UCL_DIR_PILOT+'60',
'eduPersonScopedAffiliation': EDUPERSON_OID+'9',
'edupersonscopedaffiliation': EDUPERSON_OID+'9',
'protocolInformation': X500ATTR_OID+'48',
'knowledgeInformation': X500ATTR_OID+'2',
'employeeType': NETSCAPE_LDAP+'4',
'userSMIMECertificate': NETSCAPE_LDAP+'40',
'member': X500ATTR_OID+'31',
'streetAddress': X500ATTR_OID+'9',
'dmdName': X500ATTR_OID+'54',
'postalCode': X500ATTR_OID+'17',
'pseudonym': X500ATTR_OID+'65',
'dnQualifier': X500ATTR_OID+'46',
'crossCertificatePair': X500ATTR_OID+'40',
'eduPersonOrgDN': EDUPERSON_OID+'3',
'authorityRevocationList': X500ATTR_OID+'38', 'authorityRevocationList': X500ATTR_OID+'38',
'displayName': NETSCAPE_LDAP+'241',
'businessCategory': X500ATTR_OID+'15', 'businessCategory': X500ATTR_OID+'15',
'serialNumber': X500ATTR_OID+'5', 'c': X500ATTR_OID+'6',
'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7', 'cACertificate': X500ATTR_OID+'37',
'st': X500ATTR_OID+'8',
'carLicense': NETSCAPE_LDAP+'1', 'carLicense': NETSCAPE_LDAP+'1',
'presentationAddress': X500ATTR_OID+'29', 'certificateRevocationList': X500ATTR_OID+'39',
'sn': X500ATTR_OID+'4', 'cn': X500ATTR_OID+'3',
'domainComponent': UCL_DIR_PILOT+'25',
'labeledURI': UMICH+'57',
'uid': UCL_DIR_PILOT+'1',
'co': UCL_DIR_PILOT+'43', 'co': UCL_DIR_PILOT+'43',
'friendlyCountryName': UCL_DIR_PILOT+'43', 'crossCertificatePair': X500ATTR_OID+'40',
'schacMotherTongue':SCHAC+'1', 'dc': UCL_DIR_PILOT+'25',
'deltaRevocationList': X500ATTR_OID+'53',
'departmentNumber': NETSCAPE_LDAP+'2',
'destinationIndicator': X500ATTR_OID+'27',
'displayName': NETSCAPE_LDAP+'241',
'dmdName': X500ATTR_OID+'54',
'dnQualifier': X500ATTR_OID+'46',
'eduCourseMember': EDUCOURSE_OID+'2',
'eduCourseOffering': EDUCOURSE_OID+'1',
'eduPersonAffiliation': EDUPERSON_OID+'1',
'eduPersonAssurance': EDUPERSON_OID+'11',
'eduPersonEntitlement': EDUPERSON_OID+'7',
'eduPersonNickname': EDUPERSON_OID+'2',
'eduPersonOrgDN': EDUPERSON_OID+'3',
'eduPersonOrgUnitDN': EDUPERSON_OID+'4',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'5',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'8',
'eduPersonPrincipalName': EDUPERSON_OID+'6',
'eduPersonScopedAffiliation': EDUPERSON_OID+'9',
'eduPersonTargetedID': EDUPERSON_OID+'10',
'email': PKCS_9+'1',
'employeeNumber': NETSCAPE_LDAP+'3',
'employeeType': NETSCAPE_LDAP+'4',
'enhancedSearchGuide': X500ATTR_OID+'47',
'facsimileTelephoneNumber': X500ATTR_OID+'23',
'federationFeideSchemaVersion': NOREDUPERSON_OID+'9',
'generationQualifier': X500ATTR_OID+'44',
'givenName': X500ATTR_OID+'42',
'houseIdentifier': X500ATTR_OID+'51',
'initials': X500ATTR_OID+'43',
'internationaliSDNNumber': X500ATTR_OID+'25',
'jpegPhoto': UCL_DIR_PILOT+'60',
'knowledgeInformation': X500ATTR_OID+'2',
'l': X500ATTR_OID+'7',
'labeledURI': UMICH+'57',
'mail': UCL_DIR_PILOT+'3',
'member': X500ATTR_OID+'31',
'norEduOrgAcronym': NOREDUPERSON_OID+'6',
'norEduOrgNIN': NOREDUPERSON_OID+'12',
'norEduOrgSchemaVersion': NOREDUPERSON_OID+'11',
'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7',
'norEduOrgUniqueNumber': NOREDUPERSON_OID+'1',
'norEduOrgUnitUniqueIdentifier': NOREDUPERSON_OID+'8',
'norEduOrgUnitUniqueNumber': NOREDUPERSON_OID+'2',
'norEduPersonBirthDate': NOREDUPERSON_OID+'3',
'norEduPersonLIN': NOREDUPERSON_OID+'4',
'norEduPersonLegalName': NOREDUPERSON_OID+'10',
'norEduPersonNIN': NOREDUPERSON_OID+'5',
'o': X500ATTR_OID+'10',
'ou': X500ATTR_OID+'11',
'owner': X500ATTR_OID+'32',
'physicalDeliveryOfficeName': X500ATTR_OID+'19',
'postOfficeBox': X500ATTR_OID+'18',
'postalAddress': X500ATTR_OID+'16',
'postalCode': X500ATTR_OID+'17',
'preferredDeliveryMethod': X500ATTR_OID+'28',
'preferredLanguage': NETSCAPE_LDAP+'39',
'presentationAddress': X500ATTR_OID+'29',
'protocolInformation': X500ATTR_OID+'48',
'pseudonym': X500ATTR_OID+'65',
'registeredAddress': X500ATTR_OID+'26',
'roleOccupant': X500ATTR_OID+'33',
'schacCountryOfCitizenship': SCHAC+'5',
'schacCountryOfResidence': SCHAC+'11',
'schacDateOfBirth': SCHAC+'3',
'schacExpiryDate': SCHAC+'17',
'schacGender': SCHAC+'2', 'schacGender': SCHAC+'2',
'schacDateOfBirth':SCHAC+'3',
'schacPlaceOfBirth': SCHAC+'4',
'schacCountryOfCitizenship':SCHAC+'5',
'schacSn1': SCHAC+'6',
'schacSn2': SCHAC+'7',
'schacPersonalTitle':SCHAC+'8',
'schacHomeOrganization': SCHAC+'9', 'schacHomeOrganization': SCHAC+'9',
'schacHomeOrganizationType': SCHAC+'10', 'schacHomeOrganizationType': SCHAC+'10',
'schacCountryOfResidence': SCHAC+'11', 'schacMotherTongue': SCHAC+'1',
'schacUserPresenceID': SCHAC+'12',
'schacPersonalPosition': SCHAC+'13', 'schacPersonalPosition': SCHAC+'13',
'schacPersonalTitle': SCHAC+'8',
'schacPersonalUniqueCode': SCHAC+'14', 'schacPersonalUniqueCode': SCHAC+'14',
'schacPersonalUniqueID': SCHAC+'15', 'schacPersonalUniqueID': SCHAC+'15',
'schacExpiryDate': SCHAC+'17', 'schacPlaceOfBirth': SCHAC+'4',
'schacUserPrivateAttribute': SCHAC+'18',
'schacUserStatus': SCHAC+'19',
'schacProjectMembership': SCHAC+'20', 'schacProjectMembership': SCHAC+'20',
'schacProjectSpecificRole': SCHAC+'21', 'schacProjectSpecificRole': SCHAC+'21',
} 'schacSn1': SCHAC+'6',
'schacSn2': SCHAC+'7',
'schacUserPresenceID': SCHAC+'12',
'schacUserPrivateAttribute': SCHAC+'18',
'schacUserStatus': SCHAC+'19',
'searchGuide': X500ATTR_OID+'14',
'serialNumber': X500ATTR_OID+'5',
'sisLegalGuardianFor': SIS+'1',
'sisSchoolGrade': SIS+'2',
'sn': X500ATTR_OID+'4',
'st': X500ATTR_OID+'8',
'street': X500ATTR_OID+'9',
'supportedAlgorithms': X500ATTR_OID+'52',
'supportedApplicationContext': X500ATTR_OID+'30',
'telephoneNumber': X500ATTR_OID+'20',
'teletexTerminalIdentifier': X500ATTR_OID+'22',
'telexNumber': X500ATTR_OID+'21',
'title': X500ATTR_OID+'12',
'uid': UCL_DIR_PILOT+'1',
'uniqueMember': X500ATTR_OID+'50',
'userCertificate': X500ATTR_OID+'36',
'userPKCS12': NETSCAPE_LDAP+'216',
'userSMIMECertificate': NETSCAPE_LDAP+'40',
'x121Address': X500ATTR_OID+'24',
'x500UniqueIdentifier': X500ATTR_OID+'45',
}
} }

View File

@@ -1,73 +1,82 @@
EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." EDUPERSON_OID = 'urn:oid:1.3.6.1.4.1.5923.1.1.1.'
X500ATTR = "urn:oid:2.5.4." NETSCAPE_LDAP = 'urn:oid:2.16.840.1.113730.3.1.'
NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." NOREDUPERSON_OID = 'urn:oid:1.3.6.1.4.1.2428.90.1.'
NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.1." PKCS_9 = 'urn:oid:1.2.840.113549.1.9.'
UCL_DIR_PILOT = "urn:oid:0.9.2342.19200300.100.1." UCL_DIR_PILOT = 'urn:oid:0.9.2342.19200300.100.1.'
PKCS_9 = "urn:oid:1.2.840.113549.1.9." UMICH = 'urn:oid:1.3.6.1.4.1.250.1.57.'
UMICH = "urn:oid:1.3.6.1.4.1.250.1.57." X500ATTR = 'urn:oid:2.5.4.'
MAP = { MAP = {
"identifier": "urn:mace:shibboleth:1.0:attributeNamespace:uri", "identifier": "urn:mace:shibboleth:1.0:attributeNamespace:uri",
"fro": { 'fro': {
EDUPERSON_OID+'1': 'eduPersonAffiliation',
EDUPERSON_OID+'2': 'eduPersonNickname', EDUPERSON_OID+'2': 'eduPersonNickname',
EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', EDUPERSON_OID+'3': 'eduPersonOrgDN',
EDUPERSON_OID+'11': 'eduPersonAssurance',
EDUPERSON_OID+'10': 'eduPersonTargetedID',
EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', EDUPERSON_OID+'4': 'eduPersonOrgUnitDN',
EDUPERSON_OID+'5': 'eduPersonPrimaryAffiliation',
EDUPERSON_OID+'6': 'eduPersonPrincipalName',
EDUPERSON_OID+'7': 'eduPersonEntitlement',
EDUPERSON_OID+'8': 'eduPersonPrimaryOrgUnitDN',
EDUPERSON_OID+'9': 'eduPersonScopedAffiliation',
EDUPERSON_OID+'10': 'eduPersonTargetedID',
EDUPERSON_OID+'11': 'eduPersonAssurance',
NETSCAPE_LDAP+'1': 'carLicense',
NETSCAPE_LDAP+'2': 'departmentNumber',
NETSCAPE_LDAP+'3': 'employeeNumber',
NETSCAPE_LDAP+'4': 'employeeType',
NETSCAPE_LDAP+'39': 'preferredLanguage',
NETSCAPE_LDAP+'40': 'userSMIMECertificate',
NETSCAPE_LDAP+'216': 'userPKCS12',
NETSCAPE_LDAP+'241': 'displayName',
NOREDUPERSON_OID+'1': 'norEduOrgUniqueNumber',
NOREDUPERSON_OID+'2': 'norEduOrgUnitUniqueNumber',
NOREDUPERSON_OID+'3': 'norEduPersonBirthDate',
NOREDUPERSON_OID+'4': 'norEduPersonLIN',
NOREDUPERSON_OID+'5': 'norEduPersonNIN',
NOREDUPERSON_OID+'6': 'norEduOrgAcronym', NOREDUPERSON_OID+'6': 'norEduOrgAcronym',
NOREDUPERSON_OID+'7': 'norEduOrgUniqueIdentifier', NOREDUPERSON_OID+'7': 'norEduOrgUniqueIdentifier',
NOREDUPERSON_OID+'4': 'norEduPersonLIN',
EDUPERSON_OID+'1': 'eduPersonAffiliation',
NOREDUPERSON_OID+'2': 'norEduOrgUnitUniqueNumber',
NETSCAPE_LDAP+'40': 'userSMIMECertificate',
NOREDUPERSON_OID+'1': 'norEduOrgUniqueNumber',
NETSCAPE_LDAP+'241': 'displayName',
UCL_DIR_PILOT+'37': 'associatedDomain',
EDUPERSON_OID+'6': 'eduPersonPrincipalName',
NOREDUPERSON_OID+'8': 'norEduOrgUnitUniqueIdentifier', NOREDUPERSON_OID+'8': 'norEduOrgUnitUniqueIdentifier',
NOREDUPERSON_OID+'9': 'federationFeideSchemaVersion', NOREDUPERSON_OID+'9': 'federationFeideSchemaVersion',
X500ATTR+'53': 'deltaRevocationList',
X500ATTR+'52': 'supportedAlgorithms',
X500ATTR+'51': 'houseIdentifier',
X500ATTR+'50': 'uniqueMember',
X500ATTR+'19': 'physicalDeliveryOfficeName',
X500ATTR+'18': 'postOfficeBox',
X500ATTR+'17': 'postalCode',
X500ATTR+'16': 'postalAddress',
X500ATTR+'15': 'businessCategory',
X500ATTR+'14': 'searchGuide',
EDUPERSON_OID+'5': 'eduPersonPrimaryAffiliation',
X500ATTR+'12': 'title',
X500ATTR+'11': 'ou',
X500ATTR+'10': 'o',
X500ATTR+'37': 'cACertificate',
X500ATTR+'36': 'userCertificate',
X500ATTR+'31': 'member',
X500ATTR+'30': 'supportedApplicationContext',
X500ATTR+'33': 'roleOccupant',
X500ATTR+'32': 'owner',
NETSCAPE_LDAP+'1': 'carLicense',
PKCS_9+'1': 'email', PKCS_9+'1': 'email',
NETSCAPE_LDAP+'3': 'employeeNumber',
NETSCAPE_LDAP+'2': 'departmentNumber',
X500ATTR+'39': 'certificateRevocationList',
X500ATTR+'38': 'authorityRevocationList',
NETSCAPE_LDAP+'216': 'userPKCS12',
EDUPERSON_OID+'8': 'eduPersonPrimaryOrgUnitDN',
X500ATTR+'9': 'street',
X500ATTR+'8': 'st',
NETSCAPE_LDAP+'39': 'preferredLanguage',
EDUPERSON_OID+'7': 'eduPersonEntitlement',
X500ATTR+'2': 'knowledgeInformation',
X500ATTR+'7': 'l',
X500ATTR+'6': 'c',
X500ATTR+'5': 'serialNumber',
X500ATTR+'4': 'sn',
UCL_DIR_PILOT+'60': 'jpegPhoto',
X500ATTR+'65': 'pseudonym',
NOREDUPERSON_OID+'5': 'norEduPersonNIN',
UCL_DIR_PILOT+'3': 'mail', UCL_DIR_PILOT+'3': 'mail',
UCL_DIR_PILOT+'25': 'dc', UCL_DIR_PILOT+'25': 'dc',
UCL_DIR_PILOT+'37': 'associatedDomain',
UCL_DIR_PILOT+'60': 'jpegPhoto',
X500ATTR+'2': 'knowledgeInformation',
X500ATTR+'4': 'sn',
X500ATTR+'5': 'serialNumber',
X500ATTR+'6': 'c',
X500ATTR+'7': 'l',
X500ATTR+'8': 'st',
X500ATTR+'9': 'street',
X500ATTR+'10': 'o',
X500ATTR+'11': 'ou',
X500ATTR+'12': 'title',
X500ATTR+'14': 'searchGuide',
X500ATTR+'15': 'businessCategory',
X500ATTR+'16': 'postalAddress',
X500ATTR+'17': 'postalCode',
X500ATTR+'18': 'postOfficeBox',
X500ATTR+'19': 'physicalDeliveryOfficeName',
X500ATTR+'20': 'telephoneNumber',
X500ATTR+'21': 'telexNumber',
X500ATTR+'22': 'teletexTerminalIdentifier',
X500ATTR+'23': 'facsimileTelephoneNumber',
X500ATTR+'24': 'x121Address',
X500ATTR+'25': 'internationaliSDNNumber',
X500ATTR+'26': 'registeredAddress',
X500ATTR+'27': 'destinationIndicator',
X500ATTR+'28': 'preferredDeliveryMethod',
X500ATTR+'29': 'presentationAddress',
X500ATTR+'30': 'supportedApplicationContext',
X500ATTR+'31': 'member',
X500ATTR+'32': 'owner',
X500ATTR+'33': 'roleOccupant',
X500ATTR+'36': 'userCertificate',
X500ATTR+'37': 'cACertificate',
X500ATTR+'38': 'authorityRevocationList',
X500ATTR+'39': 'certificateRevocationList',
X500ATTR+'40': 'crossCertificatePair', X500ATTR+'40': 'crossCertificatePair',
X500ATTR+'42': 'givenName', X500ATTR+'42': 'givenName',
X500ATTR+'43': 'initials', X500ATTR+'43': 'initials',
@@ -76,115 +85,107 @@ MAP = {
X500ATTR+'46': 'dnQualifier', X500ATTR+'46': 'dnQualifier',
X500ATTR+'47': 'enhancedSearchGuide', X500ATTR+'47': 'enhancedSearchGuide',
X500ATTR+'48': 'protocolInformation', X500ATTR+'48': 'protocolInformation',
X500ATTR+'50': 'uniqueMember',
X500ATTR+'51': 'houseIdentifier',
X500ATTR+'52': 'supportedAlgorithms',
X500ATTR+'53': 'deltaRevocationList',
X500ATTR+'54': 'dmdName', X500ATTR+'54': 'dmdName',
NETSCAPE_LDAP+'4': 'employeeType', X500ATTR+'65': 'pseudonym',
X500ATTR+'22': 'teletexTerminalIdentifier',
X500ATTR+'23': 'facsimileTelephoneNumber',
X500ATTR+'20': 'telephoneNumber',
X500ATTR+'21': 'telexNumber',
X500ATTR+'26': 'registeredAddress',
X500ATTR+'27': 'destinationIndicator',
X500ATTR+'24': 'x121Address',
X500ATTR+'25': 'internationaliSDNNumber',
X500ATTR+'28': 'preferredDeliveryMethod',
X500ATTR+'29': 'presentationAddress',
EDUPERSON_OID+'3': 'eduPersonOrgDN',
NOREDUPERSON_OID+'3': 'norEduPersonBirthDate',
}, },
"to":{ 'to': {
'roleOccupant': X500ATTR+'33', 'associatedDomain': UCL_DIR_PILOT+'37',
'gn': X500ATTR+'42', 'authorityRevocationList': X500ATTR+'38',
'norEduPersonNIN': NOREDUPERSON_OID+'5', 'businessCategory': X500ATTR+'15',
'title': X500ATTR+'12', 'c': X500ATTR+'6',
'facsimileTelephoneNumber': X500ATTR+'23', 'cACertificate': X500ATTR+'37',
'mail': UCL_DIR_PILOT+'3', 'carLicense': NETSCAPE_LDAP+'1',
'postOfficeBox': X500ATTR+'18', 'certificateRevocationList': X500ATTR+'39',
'fax': X500ATTR+'23',
'telephoneNumber': X500ATTR+'20',
'norEduPersonBirthDate': NOREDUPERSON_OID+'3',
'rfc822Mailbox': UCL_DIR_PILOT+'3',
'dc': UCL_DIR_PILOT+'25',
'countryName': X500ATTR+'6', 'countryName': X500ATTR+'6',
'crossCertificatePair': X500ATTR+'40',
'dc': UCL_DIR_PILOT+'25',
'deltaRevocationList': X500ATTR+'53',
'departmentNumber': NETSCAPE_LDAP+'2',
'destinationIndicator': X500ATTR+'27',
'displayName': NETSCAPE_LDAP+'241',
'dmdName': X500ATTR+'54',
'dnQualifier': X500ATTR+'46',
'domainComponent': UCL_DIR_PILOT+'25',
'eduPersonAffiliation': EDUPERSON_OID+'1',
'eduPersonAssurance': EDUPERSON_OID+'11',
'eduPersonEntitlement': EDUPERSON_OID+'7',
'eduPersonNickname': EDUPERSON_OID+'2',
'eduPersonOrgDN': EDUPERSON_OID+'3',
'eduPersonOrgUnitDN': EDUPERSON_OID+'4',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'5',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'8',
'eduPersonPrincipalName': EDUPERSON_OID+'6',
'eduPersonScopedAffiliation': EDUPERSON_OID+'9',
'eduPersonTargetedID': EDUPERSON_OID+'10',
'email': PKCS_9+'1',
'emailAddress': PKCS_9+'1', 'emailAddress': PKCS_9+'1',
'employeeNumber': NETSCAPE_LDAP+'3', 'employeeNumber': NETSCAPE_LDAP+'3',
'organizationName': X500ATTR+'10',
'eduPersonAssurance': EDUPERSON_OID+'11',
'norEduOrgAcronym': NOREDUPERSON_OID+'6',
'registeredAddress': X500ATTR+'26',
'physicalDeliveryOfficeName': X500ATTR+'19',
'associatedDomain': UCL_DIR_PILOT+'37',
'l': X500ATTR+'7',
'stateOrProvinceName': X500ATTR+'8',
'federationFeideSchemaVersion': NOREDUPERSON_OID+'9',
'pkcs9email': PKCS_9+'1',
'givenName': X500ATTR+'42',
'x500UniqueIdentifier': X500ATTR+'45',
'eduPersonNickname': EDUPERSON_OID+'2',
'houseIdentifier': X500ATTR+'51',
'street': X500ATTR+'9',
'supportedAlgorithms': X500ATTR+'52',
'preferredLanguage': NETSCAPE_LDAP+'39',
'postalAddress': X500ATTR+'16',
'email': PKCS_9+'1',
'norEduOrgUnitUniqueIdentifier': NOREDUPERSON_OID+'8',
'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'8',
'c': X500ATTR+'6',
'teletexTerminalIdentifier': X500ATTR+'22',
'o': X500ATTR+'10',
'cACertificate': X500ATTR+'37',
'telexNumber': X500ATTR+'21',
'ou': X500ATTR+'11',
'initials': X500ATTR+'43',
'eduPersonOrgUnitDN': EDUPERSON_OID+'4',
'deltaRevocationList': X500ATTR+'53',
'norEduPersonLIN': NOREDUPERSON_OID+'4',
'supportedApplicationContext': X500ATTR+'30',
'eduPersonEntitlement': EDUPERSON_OID+'7',
'generationQualifier': X500ATTR+'44',
'eduPersonAffiliation': EDUPERSON_OID+'1',
'eduPersonPrincipalName': EDUPERSON_OID+'6',
'localityName': X500ATTR+'7',
'owner': X500ATTR+'32',
'norEduOrgUnitUniqueNumber': NOREDUPERSON_OID+'2',
'searchGuide': X500ATTR+'14',
'certificateRevocationList': X500ATTR+'39',
'organizationalUnitName': X500ATTR+'11',
'userCertificate': X500ATTR+'36',
'preferredDeliveryMethod': X500ATTR+'28',
'internationaliSDNNumber': X500ATTR+'25',
'uniqueMember': X500ATTR+'50',
'departmentNumber': NETSCAPE_LDAP+'2',
'enhancedSearchGuide': X500ATTR+'47',
'userPKCS12': NETSCAPE_LDAP+'216',
'eduPersonTargetedID': EDUPERSON_OID+'10',
'norEduOrgUniqueNumber': NOREDUPERSON_OID+'1',
'x121Address': X500ATTR+'24',
'destinationIndicator': X500ATTR+'27',
'eduPersonPrimaryAffiliation': EDUPERSON_OID+'5',
'surname': X500ATTR+'4',
'jpegPhoto': UCL_DIR_PILOT+'60',
'eduPersonScopedAffiliation': EDUPERSON_OID+'9',
'protocolInformation': X500ATTR+'48',
'knowledgeInformation': X500ATTR+'2',
'employeeType': NETSCAPE_LDAP+'4', 'employeeType': NETSCAPE_LDAP+'4',
'userSMIMECertificate': NETSCAPE_LDAP+'40', 'enhancedSearchGuide': X500ATTR+'47',
'facsimileTelephoneNumber': X500ATTR+'23',
'fax': X500ATTR+'23',
'federationFeideSchemaVersion': NOREDUPERSON_OID+'9',
'generationQualifier': X500ATTR+'44',
'givenName': X500ATTR+'42',
'gn': X500ATTR+'42',
'houseIdentifier': X500ATTR+'51',
'initials': X500ATTR+'43',
'internationaliSDNNumber': X500ATTR+'25',
'jpegPhoto': UCL_DIR_PILOT+'60',
'knowledgeInformation': X500ATTR+'2',
'l': X500ATTR+'7',
'localityName': X500ATTR+'7',
'mail': UCL_DIR_PILOT+'3',
'member': X500ATTR+'31', 'member': X500ATTR+'31',
'streetAddress': X500ATTR+'9', 'norEduOrgAcronym': NOREDUPERSON_OID+'6',
'dmdName': X500ATTR+'54',
'postalCode': X500ATTR+'17',
'pseudonym': X500ATTR+'65',
'dnQualifier': X500ATTR+'46',
'crossCertificatePair': X500ATTR+'40',
'eduPersonOrgDN': EDUPERSON_OID+'3',
'authorityRevocationList': X500ATTR+'38',
'displayName': NETSCAPE_LDAP+'241',
'businessCategory': X500ATTR+'15',
'serialNumber': X500ATTR+'5',
'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7', 'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7',
'st': X500ATTR+'8', 'norEduOrgUniqueNumber': NOREDUPERSON_OID+'1',
'carLicense': NETSCAPE_LDAP+'1', 'norEduOrgUnitUniqueIdentifier': NOREDUPERSON_OID+'8',
'norEduOrgUnitUniqueNumber': NOREDUPERSON_OID+'2',
'norEduPersonBirthDate': NOREDUPERSON_OID+'3',
'norEduPersonLIN': NOREDUPERSON_OID+'4',
'norEduPersonNIN': NOREDUPERSON_OID+'5',
'o': X500ATTR+'10',
'organizationName': X500ATTR+'10',
'organizationalUnitName': X500ATTR+'11',
'ou': X500ATTR+'11',
'owner': X500ATTR+'32',
'physicalDeliveryOfficeName': X500ATTR+'19',
'pkcs9email': PKCS_9+'1',
'postOfficeBox': X500ATTR+'18',
'postalAddress': X500ATTR+'16',
'postalCode': X500ATTR+'17',
'preferredDeliveryMethod': X500ATTR+'28',
'preferredLanguage': NETSCAPE_LDAP+'39',
'presentationAddress': X500ATTR+'29', 'presentationAddress': X500ATTR+'29',
'protocolInformation': X500ATTR+'48',
'pseudonym': X500ATTR+'65',
'registeredAddress': X500ATTR+'26',
'rfc822Mailbox': UCL_DIR_PILOT+'3',
'roleOccupant': X500ATTR+'33',
'searchGuide': X500ATTR+'14',
'serialNumber': X500ATTR+'5',
'sn': X500ATTR+'4', 'sn': X500ATTR+'4',
'domainComponent': UCL_DIR_PILOT+'25', 'st': X500ATTR+'8',
'stateOrProvinceName': X500ATTR+'8',
'street': X500ATTR+'9',
'streetAddress': X500ATTR+'9',
'supportedAlgorithms': X500ATTR+'52',
'supportedApplicationContext': X500ATTR+'30',
'surname': X500ATTR+'4',
'telephoneNumber': X500ATTR+'20',
'teletexTerminalIdentifier': X500ATTR+'22',
'telexNumber': X500ATTR+'21',
'title': X500ATTR+'12',
'uniqueMember': X500ATTR+'50',
'userCertificate': X500ATTR+'36',
'userPKCS12': NETSCAPE_LDAP+'216',
'userSMIMECertificate': NETSCAPE_LDAP+'40',
'x121Address': X500ATTR+'24',
'x500UniqueIdentifier': X500ATTR+'45',
} }
} }

View File

@@ -269,7 +269,8 @@ class Config(object):
acs = ac_factory() acs = ac_factory()
if not acs: if not acs:
raise ConfigurationError("No attribute converters, something is wrong!!") raise ConfigurationError(
"No attribute converters, something is wrong!!")
_acs = self.getattr("attribute_converters", typ) _acs = self.getattr("attribute_converters", typ)
if _acs: if _acs:

View File

@@ -8,13 +8,15 @@ from saml2.soap import parse_soap_enveloped_saml_artifact_resolve
from saml2.soap import class_instances_from_soap_enveloped_saml_thingies from saml2.soap import class_instances_from_soap_enveloped_saml_thingies
from saml2.soap import open_soap_envelope from saml2.soap import open_soap_envelope
from saml2 import samlp, SamlBase, SAMLError from saml2 import samlp
from saml2 import SamlBase
from saml2 import SAMLError
from saml2 import saml from saml2 import saml
from saml2 import response from saml2 import response as saml_response
from saml2 import BINDING_URI from saml2 import BINDING_URI
from saml2 import BINDING_HTTP_ARTIFACT from saml2 import BINDING_HTTP_ARTIFACT
from saml2 import BINDING_PAOS from saml2 import BINDING_PAOS
from saml2 import request from saml2 import request as saml_request
from saml2 import soap from saml2 import soap
from saml2 import element_to_extension_element from saml2 import element_to_extension_element
from saml2 import extension_elements_to_elements from saml2 import extension_elements_to_elements
@@ -296,7 +298,16 @@ class Entity(HTTPBase):
return info return info
def unravel(self, txt, binding, msgtype="response"): @staticmethod
def unravel(txt, binding, msgtype="response"):
"""
Will unpack the received text. Depending on the context the original
response may have been transformed before transmission.
:param txt:
:param binding:
:param msgtype:
:return:
"""
#logger.debug("unravel '%s'" % txt) #logger.debug("unravel '%s'" % txt)
if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST, if binding not in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST,
BINDING_SOAP, BINDING_URI, BINDING_HTTP_ARTIFACT, BINDING_SOAP, BINDING_URI, BINDING_HTTP_ARTIFACT,
@@ -309,7 +320,8 @@ class Entity(HTTPBase):
elif binding == BINDING_HTTP_POST: elif binding == BINDING_HTTP_POST:
xmlstr = base64.b64decode(txt) xmlstr = base64.b64decode(txt)
elif binding == BINDING_SOAP: elif binding == BINDING_SOAP:
func = getattr(soap, "parse_soap_enveloped_saml_%s" % msgtype) func = getattr(soap,
"parse_soap_enveloped_saml_%s" % msgtype)
xmlstr = func(txt) xmlstr = func(txt)
elif binding == BINDING_HTTP_ARTIFACT: elif binding == BINDING_HTTP_ARTIFACT:
xmlstr = base64.b64decode(txt) xmlstr = base64.b64decode(txt)
@@ -320,7 +332,8 @@ class Entity(HTTPBase):
return xmlstr return xmlstr
def parse_soap_message(self, text): @staticmethod
def parse_soap_message(text):
""" """
:param text: The SOAP message :param text: The SOAP message
@@ -330,7 +343,8 @@ class Entity(HTTPBase):
ecp, ecp,
samlp]) samlp])
def unpack_soap_message(self, text): @staticmethod
def unpack_soap_message(text):
""" """
Picks out the parts of the SOAP message, body and headers apart Picks out the parts of the SOAP message, body and headers apart
:param text: The SOAP message :param text: The SOAP message
@@ -438,7 +452,8 @@ class Entity(HTTPBase):
msg.extension_elements = extensions msg.extension_elements = extensions
def _response(self, in_response_to, consumer_url=None, status=None, def _response(self, in_response_to, consumer_url=None, status=None,
issuer=None, sign=False, to_sign=None, encrypt_assertion=False, encrypt_cert=None, **kwargs): issuer=None, sign=False, to_sign=None,
encrypt_assertion=False, encrypt_cert=None, **kwargs):
""" Create a Response. """ Create a Response.
:param in_response_to: The session identifier of the request :param in_response_to: The session identifier of the request
@@ -471,10 +486,13 @@ class Entity(HTTPBase):
if encrypt_assertion: if encrypt_assertion:
sign_class = [(class_name(response), response.id)] sign_class = [(class_name(response), response.id)]
if sign: if sign:
response.signature = pre_signature_part(response.id, self.sec.my_cert, 1) response.signature = pre_signature_part(response.id,
self.sec.my_cert, 1)
cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary) cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary)
_, cert_file = make_temp("%s" % encrypt_cert, decode=False) _, cert_file = make_temp("%s" % encrypt_cert, decode=False)
response = cbxs.encrypt_assertion(response, cert_file, pre_encryption_part())#template(response.assertion.id)) response = cbxs.encrypt_assertion(response, cert_file,
pre_encryption_part())
# template(response.assertion.id))
if sign: if sign:
return signed_instance_factory(response, self.sec, sign_class) return signed_instance_factory(response, self.sec, sign_class)
else: else:
@@ -520,13 +538,14 @@ class Entity(HTTPBase):
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
def srv2typ(self, service): @staticmethod
def srv2typ(service):
for typ in ["aa", "pdp", "aq"]: for typ in ["aa", "pdp", "aq"]:
if service in ENDPOINTS[typ]: if service in ENDPOINTS[typ]:
if typ == "aa": if typ == "aa":
return "attribute_authority" return "attribute_authority"
elif typ == "aq": elif typ == "aq":
return "authn_authority" return "authn_authority"
else: else:
return typ return typ
@@ -570,12 +589,14 @@ class Entity(HTTPBase):
origdoc = xmlstr origdoc = xmlstr
xmlstr = self.unravel(xmlstr, binding, request_cls.msgtype) xmlstr = self.unravel(xmlstr, binding, request_cls.msgtype)
must = self.config.getattr("want_authn_requests_signed", "idp") must = self.config.getattr("want_authn_requests_signed", "idp")
only_valid_cert = self.config.getattr("want_authn_requests_only_with_valid_cert", "idp") only_valid_cert = self.config.getattr(
"want_authn_requests_only_with_valid_cert", "idp")
if only_valid_cert is None: if only_valid_cert is None:
only_valid_cert = False only_valid_cert = False
if only_valid_cert: if only_valid_cert:
must = True must = True
_request = _request.loads(xmlstr, binding, origdoc=origdoc, must=must, only_valid_cert=only_valid_cert) _request = _request.loads(xmlstr, binding, origdoc=origdoc, must=must,
only_valid_cert=only_valid_cert)
_log_debug("Loaded request") _log_debug("Loaded request")
@@ -674,14 +695,14 @@ class Entity(HTTPBase):
return response return response
def create_artifact_resolve(self, artifact, destination, sid, consent=None, def create_artifact_resolve(self, artifact, destination, sessid,
extensions=None, sign=False): consent=None, extensions=None, sign=False):
""" """
Create a ArtifactResolve request Create a ArtifactResolve request
:param artifact: :param artifact:
:param destination: :param destination:
:param sid: session id :param sessid: session id
:param consent: :param consent:
:param extensions: :param extensions:
:param sign: :param sign:
@@ -690,7 +711,7 @@ class Entity(HTTPBase):
artifact = Artifact(text=artifact) artifact = Artifact(text=artifact)
return self._message(ArtifactResolve, destination, sid, return self._message(ArtifactResolve, destination, sessid,
consent, extensions, sign, artifact=artifact) consent, extensions, sign, artifact=artifact)
def create_artifact_response(self, request, artifact, bindings=None, def create_artifact_response(self, request, artifact, bindings=None,
@@ -763,7 +784,7 @@ class Entity(HTTPBase):
was not. was not.
""" """
return self._parse_request(xmlstr, request.ManageNameIDRequest, return self._parse_request(xmlstr, saml_request.ManageNameIDRequest,
"manage_name_id_service", binding) "manage_name_id_service", binding)
def create_manage_name_id_response(self, request, bindings=None, def create_manage_name_id_response(self, request, bindings=None,
@@ -781,17 +802,21 @@ class Entity(HTTPBase):
def parse_manage_name_id_request_response(self, string, def parse_manage_name_id_request_response(self, string,
binding=BINDING_SOAP): binding=BINDING_SOAP):
return self._parse_response(string, response.ManageNameIDResponse, return self._parse_response(string, saml_response.ManageNameIDResponse,
"manage_name_id_service", binding) "manage_name_id_service", binding,
asynchop=False)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
def _parse_response(self, xmlstr, response_cls, service, binding, outstanding_certs=None, **kwargs): def _parse_response(self, xmlstr, response_cls, service, binding,
outstanding_certs=None, **kwargs):
""" Deal with a Response """ Deal with a Response
:param xmlstr: The response as a xml string :param xmlstr: The response as a xml string
:param response_cls: What type of response it is :param response_cls: What type of response it is
:param binding: What type of binding this message came through. :param binding: What type of binding this message came through.
:param outstanding_certs: Certificates that belongs to me that the
IdP may have used to encrypt a response/assertion/..
:param kwargs: Extra key word arguments :param kwargs: Extra key word arguments
:return: None if the reply doesn't contain a valid SAML Response, :return: None if the reply doesn't contain a valid SAML Response,
otherwise the response. otherwise the response.
@@ -800,20 +825,20 @@ class Entity(HTTPBase):
response = None response = None
if self.config.accepted_time_diff: if self.config.accepted_time_diff:
kwargs["timeslack"] = self.config.accepted_time_diff timeslack = self.config.accepted_time_diff
if "asynchop" not in kwargs: if "asynchop" not in kwargs:
if binding in [BINDING_SOAP, BINDING_PAOS]: if binding in [BINDING_SOAP, BINDING_PAOS]:
kwargs["asynchop"] = False asynchop = False
else: else:
kwargs["asynchop"] = True asynchop = True
if xmlstr: if xmlstr:
if "return_addrs" not in kwargs: if "return_addrs" not in kwargs:
if binding in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]: if binding in [BINDING_HTTP_REDIRECT, BINDING_HTTP_POST]:
try: try:
# expected return address # expected return address
kwargs["return_addrs"] = self.config.endpoint( return_addrs = self.config.endpoint(
service, binding=binding) service, binding=binding)
except Exception: except Exception:
logger.info("Not supposed to handle this!") logger.info("Not supposed to handle this!")
@@ -827,12 +852,6 @@ class Entity(HTTPBase):
xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype) xmlstr = self.unravel(xmlstr, binding, response_cls.msgtype)
origxml = xmlstr origxml = xmlstr
if outstanding_certs is not None:
_response = samlp.any_response_from_string(xmlstr)
if len(_response.encrypted_assertion) > 0:
_, cert_file = make_temp("%s" % outstanding_certs[_response.in_response_to]["key"], decode=False)
cbxs = CryptoBackendXmlSec1(self.config.xmlsec_binary)
xmlstr = cbxs.decrypt(xmlstr, cert_file)
if not xmlstr: # Not a valid reponse if not xmlstr: # Not a valid reponse
return None return None
@@ -851,16 +870,14 @@ class Entity(HTTPBase):
logger.debug("XMLSTR: %s" % xmlstr) logger.debug("XMLSTR: %s" % xmlstr)
if hasattr(response.response, 'encrypted_assertion'):
for encrypted_assertion in response.response.encrypted_assertion:
if encrypted_assertion.extension_elements is not None:
assertion_list = extension_elements_to_elements(encrypted_assertion.extension_elements, [saml])
for assertion in assertion_list:
_assertion = saml.assertion_from_string(str(assertion))
response.response.assertion.append(_assertion)
if response: if response:
response = response.verify() if outstanding_certs:
_, key_file = make_temp(
"%s" % outstanding_certs[
response.in_response_to]["key"], decode=False)
else:
key_file = ""
response = response.verify(key_file)
if not response: if not response:
return None return None
@@ -887,7 +904,7 @@ class Entity(HTTPBase):
was not. was not.
""" """
return self._parse_request(xmlstr, request.LogoutRequest, return self._parse_request(xmlstr, saml_request.LogoutRequest,
"single_logout_service", binding) "single_logout_service", binding)
def use_artifact(self, message, endpoint_index=0): def use_artifact(self, message, endpoint_index=0):
@@ -964,7 +981,7 @@ class Entity(HTTPBase):
kwargs = {"entity_id": self.config.entityid, kwargs = {"entity_id": self.config.entityid,
"attribute_converters": self.config.attribute_converters} "attribute_converters": self.config.attribute_converters}
resp = self._parse_response(xmlstr, response.ArtifactResponse, resp = self._parse_response(xmlstr, saml_response.ArtifactResponse,
"artifact_resolve", BINDING_SOAP, "artifact_resolve", BINDING_SOAP,
**kwargs) **kwargs)
# should just be one # should just be one

View File

@@ -775,37 +775,58 @@ class MetadataStore(object):
return [m["text"] for m in ad["affiliate_member"]] return [m["text"] for m in ad["affiliate_member"]]
def entity_categories(self, entity_id): def entity_categories(self, entity_id):
ent = self.__getitem__(entity_id) """
res = [] Get a list of entity categories for an entity id.
try:
ext = ent["extensions"]
except KeyError:
pass
else:
for elem in ext["extension_elements"]:
if elem["__class__"] == ENTITYATTRIBUTES:
for attr in elem["attribute"]:
if attr["name"] == ENTITY_CATEGORY:
res.extend([v["text"] for v in
attr["attribute_value"]])
return res :param entity_id: Entity id
:return: Entity categories
:type entity_id: string
:rtype: [string]
"""
attributes = self.entity_attributes(entity_id)
return attributes.get(ENTITY_CATEGORY, [])
def supported_entity_categories(self, entity_id): def supported_entity_categories(self, entity_id):
ent = self.__getitem__(entity_id) """
res = [] Get a list of entity category support for an entity id.
try:
ext = ent["extensions"]
except KeyError:
pass
else:
for elem in ext["extension_elements"]:
if elem["__class__"] == ENTITYATTRIBUTES:
for attr in elem["attribute"]:
if attr["name"] == ENTITY_CATEGORY_SUPPORT:
res.extend([v["text"] for v in
attr["attribute_value"]])
:param entity_id: Entity id
:return: Entity category support
:type entity_id: string
:rtype: [string]
"""
attributes = self.entity_attributes(entity_id)
return attributes.get(ENTITY_CATEGORY_SUPPORT, [])
def entity_attributes(self, entity_id):
"""
Get all entity attributes for an entry in the metadata.
Example return data:
{'http://macedir.org/entity-category': ['something', 'something2'],
'http://example.org/saml-foo': ['bar']}
:param entity_id: Entity id
:return: dict with keys and value-lists from metadata
:type entity_id: string
:rtype: dict
"""
res = {}
try:
ext = self.__getitem__(entity_id)["extensions"]
except KeyError:
return res
for elem in ext["extension_elements"]:
if elem["__class__"] == ENTITYATTRIBUTES:
for attr in elem["attribute"]:
if attr["name"] not in res:
res[attr["name"]] = []
res[attr["name"]] += [v["text"] for v in attr[
"attribute_value"]]
return res return res
def bindings(self, entity_id, typ, service): def bindings(self, entity_id, typ, service):

View File

@@ -42,8 +42,8 @@ import xmldsig as ds
import xmlenc as xenc import xmlenc as xenc
from saml2 import samlp from saml2 import samlp
from saml2 import class_name
from saml2 import saml from saml2 import saml
from saml2 import extension_element_to_element
from saml2 import extension_elements_to_elements from saml2 import extension_elements_to_elements
from saml2 import SAMLError from saml2 import SAMLError
from saml2 import time_util from saml2 import time_util
@@ -387,7 +387,7 @@ class StatusResponse(object):
if self.asynchop: if self.asynchop:
if self.response.destination and \ if self.response.destination and \
self.response.destination not in self.return_addrs: self.response.destination not in self.return_addrs:
logger.error("%s not in %s" % (self.response.destination, logger.error("%s not in %s" % (self.response.destination,
self.return_addrs)) self.return_addrs))
return None return None
@@ -399,7 +399,7 @@ class StatusResponse(object):
def loads(self, xmldata, decode=True, origxml=None): def loads(self, xmldata, decode=True, origxml=None):
return self._loads(xmldata, decode, origxml) return self._loads(xmldata, decode, origxml)
def verify(self): def verify(self, key_file=""):
try: try:
return self._verify() return self._verify()
except AssertionError: except AssertionError:
@@ -473,6 +473,7 @@ class AuthnResponse(StatusResponse):
self.came_from = "" self.came_from = ""
self.ava = None self.ava = None
self.assertion = None self.assertion = None
self.assertions = []
self.session_not_on_or_after = 0 self.session_not_on_or_after = 0
self.allow_unsolicited = allow_unsolicited self.allow_unsolicited = allow_unsolicited
self.require_signature = want_assertions_signed self.require_signature = want_assertions_signed
@@ -739,8 +740,13 @@ class AuthnResponse(StatusResponse):
return self.name_id return self.name_id
def _assertion(self, assertion): def _assertion(self, assertion):
self.assertion = assertion """
Check the assertion
:param assertion:
:return: True/False depending on if the assertion is sane or not
"""
self.assertion = assertion
logger.debug("assertion context: %s" % (self.context,)) logger.debug("assertion context: %s" % (self.context,))
logger.debug("assertion keys: %s" % (assertion.keyswv())) logger.debug("assertion keys: %s" % (assertion.keyswv()))
logger.debug("outstanding_queries: %s" % (self.outstanding_queries,)) logger.debug("outstanding_queries: %s" % (self.outstanding_queries,))
@@ -773,54 +779,61 @@ class AuthnResponse(StatusResponse):
logger.exception("get subject") logger.exception("get subject")
raise raise
def _encrypted_assertion(self, xmlstr): def decrypt_assertions(self, encrypted_assertions, key_file=""):
if xmlstr.encrypted_data: res = []
assertion_str = self.sec.decrypt(xmlstr.encrypted_data.to_string()) for encrypted_assertion in encrypted_assertions:
if not assertion_str: if encrypted_assertion.extension_elements:
raise DecryptionFailed() assertions = extension_elements_to_elements(
assertion = saml.assertion_from_string(assertion_str) encrypted_assertion.extension_elements, [saml, samlp])
else: for assertion in assertions:
decrypt_xml = self.sec.decrypt(xmlstr) if assertion.signature:
if not self.sec.verify_signature(
"%s" % assertion, key_file,
node_name=class_name(assertion)):
logger.error(
"Failed to verify signature on '%s'" % assertion)
raise SignatureError()
res.append(assertion)
return res
logger.debug("Decryption successfull") def parse_assertion(self, key_file=""):
self.response = samlp.response_from_string(decrypt_xml)
logger.debug("Parsed decrypted assertion successfull")
enc = self.response.encrypted_assertion[0].extension_elements[0]
assertion = extension_element_to_element(
enc, saml.ELEMENT_FROM_STRING, namespace=saml.NAMESPACE)
logger.debug("Decrypted Assertion: %s" % assertion)
return self._assertion(assertion)
def parse_assertion(self):
if self.context == "AuthnQuery": if self.context == "AuthnQuery":
# can contain one or more assertions # can contain one or more assertions
pass pass
else: # This is a saml2int limitation else: # This is a saml2int limitation
try: try:
assert len(self.response.assertion) == 1 or \ assert len(self.response.assertion) == 1 or \
len(self.response.encrypted_assertion) == 1 len(self.response.encrypted_assertion) == 1
except AssertionError: except AssertionError:
raise Exception("No assertion part") raise Exception("No assertion part")
if self.response.encrypted_assertion:
logger.debug("***Encrypted assertion/-s***")
decr_text = self.sec.decrypt(self.xmlstr)
resp = samlp.response_from_string(decr_text)
res = self.decrypt_assertions(resp.encrypted_assertion, key_file)
if self.response.assertion:
self.response.assertion.extend(res)
else:
self.response.assertion = res
self.response.encrypted_assertion = []
if self.response.assertion: if self.response.assertion:
logger.debug("***Unencrypted response***") logger.debug("***Unencrypted assertion***")
for assertion in self.response.assertion: for assertion in self.response.assertion:
if not self._assertion(assertion): if not self._assertion(assertion):
return False return False
return True else:
else: self.assertions.append(assertion)
logger.debug("***Encrypted response***") self.assertion = self.assertions[0]
for assertion in self.response.encrypted_assertion:
if not self._encrypted_assertion(assertion):
return False
return True
def verify(self): return True
def verify(self, key_file=""):
""" Verify that the assertion is syntactically correct and """ Verify that the assertion is syntactically correct and
the signature is correct if present.""" the signature is correct if present.
:param key_file: If not the default key file should be used this is it.
"""
try: try:
self._verify() self._verify()
@@ -830,7 +843,7 @@ class AuthnResponse(StatusResponse):
if not isinstance(self.response, samlp.Response): if not isinstance(self.response, samlp.Response):
return self return self
if self.parse_assertion(): if self.parse_assertion(key_file):
return self return self
else: else:
logger.error("Could not parse the assertion") logger.error("Could not parse the assertion")
@@ -1056,7 +1069,7 @@ class AssertionIDResponse(object):
return self._postamble() return self._postamble()
def verify(self): def verify(self, key_file=""):
try: try:
valid_instance(self.response) valid_instance(self.response)
except NotValid, exc: except NotValid, exc:

View File

@@ -246,6 +246,8 @@ class AttributeValueBase(SamlBase):
# clear type # clear type
#self.clear_type() #self.clear_type()
self.set_text(tree.text) self.set_text(tree.text)
if XSI_NIL in self.extension_attributes:
del self.extension_attributes[XSI_NIL]
try: try:
typ = self.extension_attributes[XSI_TYPE] typ = self.extension_attributes[XSI_TYPE]
_verify_value_type(typ, getattr(self, "text")) _verify_value_type(typ, getattr(self, "text"))

View File

@@ -35,12 +35,13 @@ from Crypto.PublicKey import RSA
from saml2.cert import OpenSSLWrapper from saml2.cert import OpenSSLWrapper
from saml2.extension import pefim from saml2.extension import pefim
from saml2.saml import EncryptedAssertion from saml2.saml import EncryptedAssertion
from saml2.samlp import Response, response_from_string from saml2.samlp import Response
import xmldsig as ds import xmldsig as ds
import xmlenc as enc
from saml2 import samlp, SAMLError, extension_elements_to_elements from saml2 import samlp
from saml2 import SAMLError
from saml2 import extension_elements_to_elements
from saml2 import class_name from saml2 import class_name
from saml2 import saml from saml2 import saml
from saml2 import ExtensionElement from saml2 import ExtensionElement
@@ -74,7 +75,8 @@ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
RSA_1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5" RSA_1_5 = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
TRIPLE_DES_CBC = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc" TRIPLE_DES_CBC = "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"
XMLTAG = "<?xml version='1.0'?>" XMLTAG = "<?xml version='1.0'?>"
PREFIX = "<?xml version='1.0' encoding='UTF-8'?>" PREFIX1 = "<?xml version='1.0' encoding='UTF-8'?>"
PREFIX2 = '<?xml version="1.0" encoding="UTF-8"?>'
class SigverError(SAMLError): class SigverError(SAMLError):
@@ -125,8 +127,12 @@ def rm_xmltag(statement):
statement = statement[len(XMLTAG):] statement = statement[len(XMLTAG):]
if statement[0] == '\n': if statement[0] == '\n':
statement = statement[1:] statement = statement[1:]
elif statement.startswith(PREFIX): elif statement.startswith(PREFIX1):
statement = statement[len(PREFIX):] statement = statement[len(PREFIX1):]
if statement[0] == '\n':
statement = statement[1:]
elif statement.startswith(PREFIX2):
statement = statement[len(PREFIX2):]
if statement[0] == '\n': if statement[0] == '\n':
statement = statement[1:] statement = statement[1:]
@@ -693,7 +699,7 @@ class CryptoBackend():
def encrypt(self, text, recv_key, template, key_type): def encrypt(self, text, recv_key, template, key_type):
raise NotImplementedError() raise NotImplementedError()
def encrypt_assertion(self, statement, recv_key, key_type): def encrypt_assertion(self, statement, recv_key, key_type, xpath=""):
raise NotImplementedError() raise NotImplementedError()
def decrypt(self, enctext, key_file): def decrypt(self, enctext, key_file):
@@ -733,12 +739,25 @@ class CryptoBackendXmlSec1(CryptoBackend):
except IndexError: except IndexError:
return "" return ""
def encrypt(self, text, recv_key, template, key_type): def encrypt(self, text, recv_key, template, session_key_type, xpath=""):
"""
:param text: The text to be compiled
:param recv_key: Filename of a file where the key resides
:param template: Filename of a file with the pre-encryption part
:param session_key_type: Type and size of a new session key
"des-192" generates a new 192 bits DES key for DES3 encryption
:param xpath: What should be encrypted
:return:
"""
logger.debug("Encryption input len: %d" % len(text)) logger.debug("Encryption input len: %d" % len(text))
_, fil = make_temp("%s" % text, decode=False) _, fil = make_temp("%s" % text, decode=False)
com_list = [self.xmlsec, "--encrypt", "--pubkey-cert-pem", recv_key, com_list = [self.xmlsec, "--encrypt", "--pubkey-cert-pem", recv_key,
"--session-key", key_type, "--xml-data", fil] "--session-key", session_key_type, "--xml-data", fil]
if xpath:
com_list.extend(['--node-xpath', xpath])
(_stdout, _stderr, output) = self._run_xmlsec(com_list, [template], (_stdout, _stderr, output) = self._run_xmlsec(com_list, [template],
exception=DecryptError, exception=DecryptError,
@@ -767,7 +786,8 @@ class CryptoBackendXmlSec1(CryptoBackend):
"--session-key", key_type, "--xml-data", fil, "--session-key", key_type, "--xml-data", fil,
"--node-xpath", ASSERT_XPATH] "--node-xpath", ASSERT_XPATH]
(_stdout, _stderr, output) = self._run_xmlsec(com_list, [tmpl], exception=EncryptError, validate_output=False) (_stdout, _stderr, output) = self._run_xmlsec(
com_list, [tmpl], exception=EncryptError, validate_output=False)
os.unlink(fil) os.unlink(fil)
if not output: if not output:
@@ -1206,7 +1226,7 @@ class SecurityContext(object):
:param text: Text to encrypt :param text: Text to encrypt
:param recv_key: A file containing the receivers public key :param recv_key: A file containing the receivers public key
:param template: A file containing the XML document template :param template: A file containing the XMLSEC template
:param key_type: The type of session key to use :param key_type: The type of session key to use
:result: An encrypted XML text :result: An encrypted XML text
""" """
@@ -1262,8 +1282,7 @@ class SecurityContext(object):
return self.crypto.validate_signature(signedtext, cert_file=cert_file, return self.crypto.validate_signature(signedtext, cert_file=cert_file,
cert_type=cert_type, cert_type=cert_type,
node_name=node_name, node_name=node_name,
node_id=node_id, id_attr=id_attr, node_id=node_id, id_attr=id_attr)
)
def _check_signature(self, decoded_xml, item, node_name=NODE_NAME, def _check_signature(self, decoded_xml, item, node_name=NODE_NAME,
origdoc=None, id_attr="", must=False, origdoc=None, id_attr="", must=False,
@@ -1735,7 +1754,10 @@ def pre_encrypt_assertion(response):
assertion = response.assertion assertion = response.assertion
response.assertion = None response.assertion = None
response.encrypted_assertion = EncryptedAssertion() response.encrypted_assertion = EncryptedAssertion()
response.encrypted_assertion.add_extension_element(assertion) if isinstance(assertion, list):
response.encrypted_assertion.add_extension_elements(assertion)
else:
response.encrypted_assertion.add_extension_element(assertion)
# txt = "%s" % response # txt = "%s" % response
# _ass = "%s" % assertion # _ass = "%s" % assertion
# _ass = rm_xmltag(_ass) # _ass = rm_xmltag(_ass)

View File

@@ -81,7 +81,8 @@ def parse_duration(duration):
assert duration[index] == "P" assert duration[index] == "P"
index += 1 index += 1
dic = dict([(typ, 0) for (code, typ) in D_FORMAT]) dic = dict([(typ, 0) for (code, typ) in D_FORMAT if typ])
dlen = len(duration)
for code, typ in D_FORMAT: for code, typ in D_FORMAT:
#print duration[index:], code #print duration[index:], code
@@ -94,25 +95,36 @@ def parse_duration(duration):
raise Exception("Not allowed to end with 'T'") raise Exception("Not allowed to end with 'T'")
else: else:
raise Exception("Missing T") raise Exception("Missing T")
elif duration[index] == "T":
continue
else: else:
try: try:
mod = duration[index:].index(code) mod = duration[index:].index(code)
_val = duration[index:index + mod]
try: try:
dic[typ] = int(duration[index:index + mod]) dic[typ] = int(_val)
except ValueError: except ValueError:
if code == "S": # smallest value used may also have a decimal fraction
if mod + index + 1 == dlen:
try: try:
dic[typ] = float(duration[index:index + mod]) dic[typ] = float(_val)
except ValueError: except ValueError:
raise Exception("Not a float") if "," in _val:
_val = _val.replace(",", ".")
try:
dic[typ] = float(_val)
except ValueError:
raise Exception("Not a float")
else:
raise Exception("Not a float")
else: else:
raise Exception( raise ValueError(
"Fractions not allow on anything byt seconds") "Fraction not allowed on other than smallest value")
index = mod + index + 1 index = mod + index + 1
except ValueError: except ValueError:
dic[typ] = 0 dic[typ] = 0
if index == len(duration): if index == dlen:
break break
return sign, dic return sign, dic

View File

@@ -2,8 +2,8 @@ from pathutils import full_path
from pathutils import xmlsec_path from pathutils import xmlsec_path
CONFIG = { CONFIG = {
"entityid" : "urn:mace:example.com:saml:roland:sp", "entityid": "urn:mace:example.com:saml:roland:sp",
"name" : "urn:mace:example.com:saml:roland:sp", "name": "urn:mace:example.com:saml:roland:sp",
"description": "My own SP", "description": "My own SP",
"service": { "service": {
"sp": { "sp": {
@@ -25,14 +25,14 @@ CONFIG = {
"local": [full_path("idp.xml"), full_path("vo_metadata.xml")], "local": [full_path("idp.xml"), full_path("vo_metadata.xml")],
}, },
"virtual_organization": { "virtual_organization": {
"urn:mace:example.com:it:tek":{ "urn:mace:example.com:it:tek": {
"nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID",
"common_identifier": "umuselin", "common_identifier": "umuselin",
} }
}, },
"subject_data": "subject_data.db", "subject_data": "subject_data.db",
"accepted_time_diff": 60, "accepted_time_diff": 60,
"attribute_map_dir" : full_path("attributemaps"), "attribute_map_dir": full_path("attributemaps"),
"valid_for": 6, "valid_for": 6,
"organization": { "organization": {
"name": ("AB Exempel", "se"), "name": ("AB Exempel", "se"),
@@ -40,12 +40,13 @@ CONFIG = {
"url": "http://www.example.org", "url": "http://www.example.org",
}, },
"contact_person": [{ "contact_person": [{
"given_name": "Roland", "given_name": "Roland",
"sur_name": "Hedberg", "sur_name": "Hedberg",
"telephone_number": "+46 70 100 0000", "telephone_number": "+46 70 100 0000",
"email_address": ["tech@eample.com", "tech@example.org"], "email_address": ["tech@eample.com",
"contact_type": "technical" "tech@example.org"],
}, "contact_type": "technical"
},
], ],
"logger": { "logger": {
"rotating": { "rotating": {

View File

@@ -7,38 +7,43 @@ from saml2.time_util import f_quotient, modulo, parse_duration, add_duration
from saml2.time_util import str_to_time, instant, valid, in_a_while from saml2.time_util import str_to_time, instant, valid, in_a_while
from saml2.time_util import before, after, not_before, not_on_or_after from saml2.time_util import before, after, not_before, not_on_or_after
def test_f_quotient(): def test_f_quotient():
assert f_quotient(-1,3) == -1 assert f_quotient(-1, 3) == -1
assert f_quotient(0,3) == 0 assert f_quotient(0, 3) == 0
assert f_quotient(1,3) == 0 assert f_quotient(1, 3) == 0
assert f_quotient(2,3) == 0 assert f_quotient(2, 3) == 0
assert f_quotient(3,3) == 1 assert f_quotient(3, 3) == 1
assert f_quotient(3.123,3) == 1 assert f_quotient(3.123, 3) == 1
def test_modulo(): def test_modulo():
assert modulo(-1,3) == 2 assert modulo(-1, 3) == 2
assert modulo(0,3) == 0 assert modulo(0, 3) == 0
assert modulo(1,3) == 1 assert modulo(1, 3) == 1
assert modulo(2,3) == 2 assert modulo(2, 3) == 2
assert modulo(3,3) == 0 assert modulo(3, 3) == 0
x = 3.123 x = 3.123
assert modulo(3.123,3) == x - 3 assert modulo(3.123, 3) == x - 3
def test_f_quotient_2(): def test_f_quotient_2():
assert f_quotient(0, 1, 13) == -1 assert f_quotient(0, 1, 13) == -1
for i in range(1,13): for i in range(1, 13):
assert f_quotient(i, 1, 13) == 0 assert f_quotient(i, 1, 13) == 0
assert f_quotient(13, 1, 13) == 1 assert f_quotient(13, 1, 13) == 1
assert f_quotient(13.123, 1, 13) == 1 assert f_quotient(13.123, 1, 13) == 1
def test_modulo_2(): def test_modulo_2():
assert modulo(0, 1, 13) == 12 assert modulo(0, 1, 13) == 12
for i in range(1,13): for i in range(1, 13):
assert modulo(i, 1, 13) == i assert modulo(i, 1, 13) == i
assert modulo(13, 1, 13) == 1 assert modulo(13, 1, 13) == 1
#x = 0.123 #x = 0.123
#assert modulo(13+x, 1, 13) == 1+x #assert modulo(13+x, 1, 13) == 1+x
def test_parse_duration(): def test_parse_duration():
(sign, d) = parse_duration("P1Y3M5DT7H10M3.3S") (sign, d) = parse_duration("P1Y3M5DT7H10M3.3S")
assert sign == "+" assert sign == "+"
@@ -49,6 +54,45 @@ def test_parse_duration():
assert d['tm_year'] == 1 assert d['tm_year'] == 1
assert d['tm_min'] == 10 assert d['tm_min'] == 10
def test_parse_duration2():
(sign, d) = parse_duration("PT30M")
assert sign == "+"
assert d['tm_sec'] == 0
assert d['tm_mon'] == 0
assert d['tm_hour'] == 0
assert d['tm_mday'] == 0
assert d['tm_year'] == 0
assert d['tm_min'] == 30
PATTERNS = {
"P3Y6M4DT12H30M5S": {'tm_sec': 5, 'tm_hour': 12, 'tm_mday': 4,
'tm_year': 3, 'tm_mon': 6, 'tm_min': 30},
"P23DT23H": {'tm_sec': 0, 'tm_hour': 23, 'tm_mday': 23, 'tm_year': 0,
'tm_mon': 0, 'tm_min': 0},
"P4Y": {'tm_sec': 0, 'tm_hour': 0, 'tm_mday': 0, 'tm_year': 4,
'tm_mon': 0, 'tm_min': 0},
"P1M": {'tm_sec': 0, 'tm_hour': 0, 'tm_mday': 0, 'tm_year': 0,
'tm_mon': 1, 'tm_min': 0},
"PT1M": {'tm_sec': 0, 'tm_hour': 0, 'tm_mday': 0, 'tm_year': 0,
'tm_mon': 0, 'tm_min': 1},
"P0.5Y": {'tm_sec': 0, 'tm_hour': 0, 'tm_mday': 0, 'tm_year': 0.5,
'tm_mon': 0, 'tm_min': 0},
"P0,5Y": {'tm_sec': 0, 'tm_hour': 0, 'tm_mday': 0, 'tm_year': 0.5,
'tm_mon': 0, 'tm_min': 0},
"PT36H": {'tm_sec': 0, 'tm_hour': 36, 'tm_mday': 0, 'tm_year': 0,
'tm_mon': 0, 'tm_min': 0},
"P1DT12H": {'tm_sec': 0, 'tm_hour': 12, 'tm_mday': 1, 'tm_year': 0,
'tm_mon': 0, 'tm_min': 0}
}
def test_parse_duration_n():
for dur, _val in PATTERNS.items():
(sign, d) = parse_duration(dur)
assert d == _val
def test_add_duration_1(): def test_add_duration_1():
#2000-01-12T12:13:14Z P1Y3M5DT7H10M3S 2001-04-17T19:23:17Z #2000-01-12T12:13:14Z P1Y3M5DT7H10M3S 2001-04-17T19:23:17Z
t = add_duration(str_to_time("2000-01-12T12:13:14Z"), "P1Y3M5DT7H10M3S") t = add_duration(str_to_time("2000-01-12T12:13:14Z"), "P1Y3M5DT7H10M3S")
@@ -59,9 +103,10 @@ def test_add_duration_1():
assert t.tm_min == 23 assert t.tm_min == 23
assert t.tm_sec == 17 assert t.tm_sec == 17
def test_add_duration_2(): def test_add_duration_2():
#2000-01-12 PT33H 2000-01-13 #2000-01-12 PT33H 2000-01-13
t = add_duration(str_to_time("2000-01-12T00:00:00Z"),"PT33H") t = add_duration(str_to_time("2000-01-12T00:00:00Z"), "PT33H")
assert t.tm_year == 2000 assert t.tm_year == 2000
assert t.tm_mon == 1 assert t.tm_mon == 1
assert t.tm_mday == 14 assert t.tm_mday == 14
@@ -69,6 +114,7 @@ def test_add_duration_2():
assert t.tm_min == 0 assert t.tm_min == 0
assert t.tm_sec == 0 assert t.tm_sec == 0
def test_str_to_time(): def test_str_to_time():
t = calendar.timegm(str_to_time("2000-01-12T00:00:00Z")) t = calendar.timegm(str_to_time("2000-01-12T00:00:00Z"))
#TODO: Find all instances of time.mktime(.....) #TODO: Find all instances of time.mktime(.....)
@@ -78,22 +124,25 @@ def test_str_to_time():
# do this as an external method in the # do this as an external method in the
assert t == 947635200 assert t == 947635200
def test_instant(): def test_instant():
inst = str_to_time(instant()) inst = str_to_time(instant())
now = time.gmtime() now = time.gmtime()
assert now >= inst assert now >= inst
def test_valid(): def test_valid():
assert valid("2000-01-12T00:00:00Z") == False assert valid("2000-01-12T00:00:00Z") == False
current_year = datetime.datetime.today().year current_year = datetime.datetime.today().year
assert valid("%d-01-12T00:00:00Z" % (current_year + 1)) == True assert valid("%d-01-12T00:00:00Z" % (current_year + 1)) == True
this_instance = instant() this_instance = instant()
time.sleep(1) time.sleep(1)
assert valid(this_instance) == False # unless on a very fast machine :-) assert valid(this_instance) == False # unless on a very fast machine :-)
soon = in_a_while(seconds=10) soon = in_a_while(seconds=10)
assert valid(soon) == True assert valid(soon) == True
def test_timeout(): def test_timeout():
soon = in_a_while(seconds=1) soon = in_a_while(seconds=1)
time.sleep(2) time.sleep(2)
@@ -122,3 +171,7 @@ def test_not_on_or_after():
current_year = datetime.datetime.today().year current_year = datetime.datetime.today().year
assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year + 1)) == True assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year + 1)) == True
assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year - 1)) == False assert not_on_or_after("%d-01-01T00:00:00Z" % (current_year - 1)) == False
if __name__ == "__main__":
test_parse_duration_n()

View File

@@ -151,7 +151,7 @@ def test_audience():
assert aud_restr.audience.text == "urn:foo:bar" assert aud_restr.audience.text == "urn:foo:bar"
def test_conditions(): def test_conditions():
conditions = utils.factory( saml.Conditions, conditions = utils.factory(saml.Conditions,
not_before="2009-10-30T07:58:10.852Z", not_before="2009-10-30T07:58:10.852Z",
not_on_or_after="2009-10-30T08:03:10.852Z", not_on_or_after="2009-10-30T08:03:10.852Z",
audience_restriction=[utils.factory(saml.AudienceRestriction, audience_restriction=[utils.factory(saml.AudienceRestriction,

View File

@@ -18,9 +18,11 @@ from saml2.validate import valid_anytype
from py.test import raises from py.test import raises
def _eq(l1,l2):
def _eq(l1, l2):
return set(l1) == set(l2) return set(l1) == set(l2)
def test_duration(): def test_duration():
assert valid_duration("P1Y2M3DT10H30M") assert valid_duration("P1Y2M3DT10H30M")
assert valid_duration("P1Y2M3DT10H30M1.567S") assert valid_duration("P1Y2M3DT10H30M1.567S")
@@ -31,41 +33,45 @@ def test_duration():
assert valid_duration("P0Y1347M") assert valid_duration("P0Y1347M")
assert valid_duration("P0Y1347M0D") assert valid_duration("P0Y1347M0D")
assert valid_duration("-P1347M") assert valid_duration("-P1347M")
assert valid_duration("P1Y2MT2.5H")
raises( NotValid, 'valid_duration("P-1347M")') raises(NotValid, 'valid_duration("P-1347M")')
raises( NotValid, ' valid_duration("P1Y2MT")') raises(NotValid, ' valid_duration("P1Y2MT")')
raises( NotValid, ' valid_duration("P1Y2MT2.5H")') raises(NotValid, ' valid_duration("P1Y2MT2xH")')
raises( NotValid, ' valid_duration("P1Y2MT2xH")')
def test_unsigned_short(): def test_unsigned_short():
assert valid_unsigned_short("1234") assert valid_unsigned_short("1234")
raises( NotValid, ' valid_unsigned_short("-1234")') raises(NotValid, ' valid_unsigned_short("-1234")')
raises( NotValid, ' valid_unsigned_short("1234567890")') raises(NotValid, ' valid_unsigned_short("1234567890")')
def test_valid_non_negative_integer(): def test_valid_non_negative_integer():
assert valid_non_negative_integer("1234567890") assert valid_non_negative_integer("1234567890")
raises( NotValid, 'valid_non_negative_integer("-123")') raises(NotValid, 'valid_non_negative_integer("-123")')
raises( NotValid, 'valid_non_negative_integer("123.56")') raises(NotValid, 'valid_non_negative_integer("123.56")')
assert valid_non_negative_integer("12345678901234567890") assert valid_non_negative_integer("12345678901234567890")
def test_valid_string(): def test_valid_string():
assert valid_string(u'example') assert valid_string(u'example')
raises( NotValid, 'valid_string("02656c6c6f".decode("hex"))') raises(NotValid, 'valid_string("02656c6c6f".decode("hex"))')
def test_valid_anyuri(): def test_valid_anyuri():
assert valid_any_uri("urn:oasis:names:tc:SAML:2.0:attrname-format:uri") assert valid_any_uri("urn:oasis:names:tc:SAML:2.0:attrname-format:uri")
def test_valid_instance(): def test_valid_instance():
attr_statem = saml.AttributeStatement() attr_statem = saml.AttributeStatement()
text = ["value of test attribute", text = ["value of test attribute",
"value1 of test attribute", "value1 of test attribute",
"value2 of test attribute", "value2 of test attribute",
"value1 of test attribute2", "value1 of test attribute2",
"value2 of test attribute2",] "value2 of test attribute2", ]
attr_statem.attribute.append(saml.Attribute()) attr_statem.attribute.append(saml.Attribute())
attr_statem.attribute.append(saml.Attribute()) attr_statem.attribute.append(saml.Attribute())
@@ -94,7 +100,8 @@ def test_valid_instance():
response.status = samlp.Status() response.status = samlp.Status()
response.assertion.append(saml.Assertion()) response.assertion.append(saml.Assertion())
raises( MustValueError, 'valid_instance(response)') raises(MustValueError, 'valid_instance(response)')
def test_valid_anytype(): def test_valid_anytype():
assert valid_anytype("130.239.16.3") assert valid_anytype("130.239.16.3")
@@ -104,4 +111,3 @@ def test_valid_anytype():
assert valid_anytype("P1Y2M3DT10H30M") assert valid_anytype("P1Y2M3DT10H30M")
assert valid_anytype("urn:oasis:names:tc:SAML:2.0:attrname-format:uri") assert valid_anytype("urn:oasis:names:tc:SAML:2.0:attrname-format:uri")

View File

@@ -1,3 +1,4 @@
# coding=utf-8
from saml2.authn_context import pword from saml2.authn_context import pword
from saml2.mdie import to_dict from saml2.mdie import to_dict
from saml2 import md, assertion from saml2 import md, assertion
@@ -75,7 +76,6 @@ def test_filter_on_attributes_1():
assert ava.keys() == ["serialNumber"] assert ava.keys() == ["serialNumber"]
assert ava["serialNumber"] == ["12345"] assert ava["serialNumber"] == ["12345"]
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
def test_lifetime_1(): def test_lifetime_1():
@@ -172,6 +172,8 @@ def test_ava_filter_2():
"surName": "Jeter", "surName": "Jeter",
"mail": "derek@example.com"} "mail": "derek@example.com"}
# mail removed because it doesn't match the regular expression
# So this should fail.
raises(MissingValue, policy.filter, ava, 'urn:mace:umu.se:saml:roland:sp', raises(MissingValue, policy.filter, ava, 'urn:mace:umu.se:saml:roland:sp',
None, [mail], [gn, sn]) None, [mail], [gn, sn])
@@ -183,6 +185,44 @@ def test_ava_filter_2():
None, [gn, sn, mail]) None, [gn, sn, mail])
def test_ava_filter_dont_fail():
conf = {
"default": {
"lifetime": {"minutes": 15},
"attribute_restrictions": None, # means all I have
},
"urn:mace:umu.se:saml:roland:sp": {
"lifetime": {"minutes": 5},
"attribute_restrictions": {
"givenName": None,
"surName": None,
"mail": [".*@.*\.umu\.se"],
},
"fail_on_missing_requested": False
}}
policy = Policy(conf)
ava = {"givenName": "Derek",
"surName": "Jeter",
"mail": "derek@example.com"}
# mail removed because it doesn't match the regular expression
# So it should fail if the 'fail_on_ ...' flag wasn't set
_ava = policy.filter(ava,'urn:mace:umu.se:saml:roland:sp', None,
[mail], [gn, sn])
assert _ava
ava = {"givenName": "Derek",
"surName": "Jeter"}
# it wasn't there to begin with
_ava = policy.filter(ava, 'urn:mace:umu.se:saml:roland:sp',
None, [gn, sn, mail])
assert _ava
def test_filter_attribute_value_assertions_0(AVA): def test_filter_attribute_value_assertions_0(AVA):
p = Policy({ p = Policy({
"default": { "default": {
@@ -255,6 +295,7 @@ def test_filter_attribute_value_assertions_2(AVA):
assert _eq(ava.keys(), ["givenName"]) assert _eq(ava.keys(), ["givenName"])
assert ava["givenName"] == ["Roland"] assert ava["givenName"] == ["Roland"]
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
@@ -290,7 +331,9 @@ def test_assertion_1(AVA):
def test_assertion_2(): def test_assertion_2():
AVA = {'mail': u'roland.hedberg@adm.umu.se', AVA = {'mail': u'roland.hedberg@adm.umu.se',
'eduPersonTargetedID': 'http://lingon.ladok.umu.se:8090/idp!http://lingon.ladok.umu.se:8088/sp!95e9ae91dbe62d35198fbbd5e1fb0976', 'eduPersonTargetedID': 'http://lingon.ladok.umu'
'.se:8090/idp!http://lingon.ladok.umu'
'.se:8088/sp!95e9ae91dbe62d35198fbbd5e1fb0976',
'displayName': u'Roland Hedberg', 'displayName': u'Roland Hedberg',
'uid': 'http://roland.hedberg.myopenid.com/'} 'uid': 'http://roland.hedberg.myopenid.com/'}
@@ -453,6 +496,7 @@ def test_filter_values_req_opt_2():
raises(MissingValue, "filter_on_attributes(ava, r, o)") raises(MissingValue, "filter_on_attributes(ava, r, o)")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -485,6 +529,7 @@ def test_filter_values_req_opt_4():
assert _eq(ava.keys(), ['givenName', 'sn']) assert _eq(ava.keys(), ['givenName', 'sn'])
assert ava == {'givenName': ['Roland'], 'sn': ['Hedberg']} assert ava == {'givenName': ['Roland'], 'sn': ['Hedberg']}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -706,7 +751,7 @@ ACD = pword.AuthenticationContextDeclaration(authn_method=authn_method)
def test_assertion_with_noop_attribute_conv(): def test_assertion_with_noop_attribute_conv():
ava = {"urn:oid:2.5.4.4": "Roland", "urn:oid:2.5.4.42": "Hedberg" } ava = {"urn:oid:2.5.4.4": "Roland", "urn:oid:2.5.4.42": "Hedberg"}
ast = Assertion(ava) ast = Assertion(ava)
policy = Policy({ policy = Policy({
"default": { "default": {
@@ -719,7 +764,7 @@ def test_assertion_with_noop_attribute_conv():
issuer = Issuer(text="entityid", format=NAMEID_FORMAT_ENTITY) issuer = Issuer(text="entityid", format=NAMEID_FORMAT_ENTITY)
msg = ast.construct("sp_entity_id", "in_response_to", "consumer_url", msg = ast.construct("sp_entity_id", "in_response_to", "consumer_url",
name_id, [AttributeConverterNOOP(NAME_FORMAT_URI)], name_id, [AttributeConverterNOOP(NAME_FORMAT_URI)],
policy, issuer=issuer, authn_decl=ACD , policy, issuer=issuer, authn_decl=ACD,
authn_auth="authn_authn") authn_auth="authn_authn")
print msg print msg
@@ -767,7 +812,7 @@ def test_assertion_with_zero_attributes():
issuer = Issuer(text="entityid", format=NAMEID_FORMAT_ENTITY) issuer = Issuer(text="entityid", format=NAMEID_FORMAT_ENTITY)
msg = ast.construct("sp_entity_id", "in_response_to", "consumer_url", msg = ast.construct("sp_entity_id", "in_response_to", "consumer_url",
name_id, [AttributeConverterNOOP(NAME_FORMAT_URI)], name_id, [AttributeConverterNOOP(NAME_FORMAT_URI)],
policy, issuer=issuer, authn_decl=ACD , policy, issuer=issuer, authn_decl=ACD,
authn_auth="authn_authn") authn_auth="authn_authn")
print msg print msg
@@ -797,4 +842,4 @@ def test_assertion_with_authn_instant():
if __name__ == "__main__": if __name__ == "__main__":
test_assertion_2() test_ava_filter_dont_fail()

View File

@@ -122,4 +122,3 @@ class TestClass:
assert inactive == ["bcde"] assert inactive == ["bcde"]
assert ava == {} assert ava == {}

View File

@@ -1,11 +1,12 @@
#!/usr/bin/env python #!/usr/bin/env python
import base64 import base64
from saml2.sigver import pre_encryption_part, make_temp
from saml2.mdstore import MetadataStore from saml2.mdstore import MetadataStore
from saml2.saml import assertion_from_string from saml2.saml import assertion_from_string, EncryptedAssertion
from saml2.samlp import response_from_string from saml2.samlp import response_from_string
from saml2 import sigver from saml2 import sigver, extension_elements_to_elements
from saml2 import class_name from saml2 import class_name
from saml2 import time_util from saml2 import time_util
from saml2 import saml, samlp from saml2 import saml, samlp
@@ -25,9 +26,10 @@ PUB_KEY = full_path("test.pem")
PRIV_KEY = full_path("test.key") PRIV_KEY = full_path("test.key")
def _eq(l1,l2): def _eq(l1, l2):
return set(l1) == set(l2) return set(l1) == set(l2)
CERT1 = """MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV CERT1 = """MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
@@ -104,7 +106,6 @@ class FakeConfig():
class TestSecurity(): class TestSecurity():
def setup_class(self): def setup_class(self):
# This would be one way to initialize the security context : # This would be one way to initialize the security context :
# #
@@ -126,8 +127,8 @@ class TestSecurity():
issue_instant="2009-10-30T13:20:28Z", issue_instant="2009-10-30T13:20:28Z",
signature=sigver.pre_signature_part("11111", self.sec.my_cert, 1), signature=sigver.pre_signature_part("11111", self.sec.my_cert, 1),
attribute_statement=do_attribute_statement({ attribute_statement=do_attribute_statement({
("", "", "surName"): ("Foo", ""), ("", "", "surName"): ("Foo", ""),
("", "", "givenName"): ("Bar", ""), ("", "", "givenName"): ("Bar", ""),
}) })
) )
@@ -144,7 +145,7 @@ class TestSecurity():
def test_non_verify_2(self): def test_non_verify_2(self):
xml_response = open(FALSE_SIGNED).read() xml_response = open(FALSE_SIGNED).read()
raises(sigver.SignatureError,self.sec.correctly_signed_response, raises(sigver.SignatureError, self.sec.correctly_signed_response,
xml_response) xml_response)
def test_sign_assertion(self): def test_sign_assertion(self):
@@ -155,7 +156,7 @@ class TestSecurity():
sass = saml.assertion_from_string(sign_ass) sass = saml.assertion_from_string(sign_ass)
#print sass #print sass
assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant', assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
'version', 'signature', 'id']) 'version', 'signature', 'id'])
assert sass.version == "2.0" assert sass.version == "2.0"
assert sass.id == "11111" assert sass.id == "11111"
assert time_util.str_to_time(sass.issue_instant) assert time_util.str_to_time(sass.issue_instant)
@@ -227,12 +228,14 @@ class TestSecurity():
def test_sign_response(self): def test_sign_response(self):
response = factory(samlp.Response, response = factory(samlp.Response,
assertion=self._assertion, assertion=self._assertion,
id="22222", id="22222",
signature=sigver.pre_signature_part("22222", self.sec.my_cert)) signature=sigver.pre_signature_part("22222",
self.sec
.my_cert))
to_sign = [(class_name(self._assertion), self._assertion.id), to_sign = [(class_name(self._assertion), self._assertion.id),
(class_name(response), response.id)] (class_name(response), response.id)]
s_response = sigver.signed_instance_factory(response, self.sec, to_sign) s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
assert s_response is not None assert s_response is not None
@@ -252,23 +255,27 @@ class TestSecurity():
assert item.id == "22222" assert item.id == "22222"
def test_sign_response_2(self): def test_sign_response_2(self):
assertion2 = factory( saml.Assertion, assertion2 = factory(saml.Assertion,
version= "2.0", version="2.0",
id= "11122", id="11122",
issue_instant= "2009-10-30T13:20:28Z", issue_instant="2009-10-30T13:20:28Z",
signature= sigver.pre_signature_part("11122", self.sec.my_cert), signature=sigver.pre_signature_part("11122",
attribute_statement=do_attribute_statement({ self.sec
("","","surName"): ("Fox",""), .my_cert),
("","","givenName") :("Bear",""), attribute_statement=do_attribute_statement({
}) ("", "", "surName"): ("Fox", ""),
) ("", "", "givenName"): ("Bear", ""),
})
)
response = factory(samlp.Response, response = factory(samlp.Response,
assertion=assertion2, assertion=assertion2,
id="22233", id="22233",
signature=sigver.pre_signature_part("22233", self.sec.my_cert)) signature=sigver.pre_signature_part("22233",
self.sec
.my_cert))
to_sign = [(class_name(assertion2), assertion2.id), to_sign = [(class_name(assertion2), assertion2.id),
(class_name(response), response.id)] (class_name(response), response.id)]
s_response = sigver.signed_instance_factory(response, self.sec, to_sign) s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
@@ -277,7 +284,7 @@ class TestSecurity():
sass = response2.assertion[0] sass = response2.assertion[0]
assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant', assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
'version', 'signature', 'id']) 'version', 'signature', 'id'])
assert sass.version == "2.0" assert sass.version == "2.0"
assert sass.id == "11122" assert sass.id == "11122"
@@ -288,30 +295,34 @@ class TestSecurity():
def test_sign_verify(self): def test_sign_verify(self):
response = factory(samlp.Response, response = factory(samlp.Response,
assertion=self._assertion, assertion=self._assertion,
id="22233", id="22233",
signature=sigver.pre_signature_part("22233", self.sec.my_cert)) signature=sigver.pre_signature_part("22233",
self.sec
.my_cert))
to_sign = [(class_name(self._assertion), self._assertion.id), to_sign = [(class_name(self._assertion), self._assertion.id),
(class_name(response), response.id)] (class_name(response), response.id)]
s_response = sigver.signed_instance_factory(response, self.sec, to_sign) s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
print s_response print s_response
res = self.sec.verify_signature("%s" % s_response, res = self.sec.verify_signature("%s" % s_response,
node_name=class_name(samlp.Response())) node_name=class_name(samlp.Response()))
print res print res
assert res assert res
def test_sign_verify_with_cert_from_instance(self): def test_sign_verify_with_cert_from_instance(self):
response = factory(samlp.Response, response = factory(samlp.Response,
assertion=self._assertion, assertion=self._assertion,
id="22222", id="22222",
signature=sigver.pre_signature_part("22222", self.sec.my_cert)) signature=sigver.pre_signature_part("22222",
self.sec
.my_cert))
to_sign = [(class_name(self._assertion), self._assertion.id), to_sign = [(class_name(self._assertion), self._assertion.id),
(class_name(response), response.id)] (class_name(response), response.id)]
s_response = sigver.signed_instance_factory(response, self.sec, to_sign) s_response = sigver.signed_instance_factory(response, self.sec, to_sign)
@@ -319,11 +330,10 @@ class TestSecurity():
ci = "".join(sigver.cert_from_instance(response2)[0].split()) ci = "".join(sigver.cert_from_instance(response2)[0].split())
assert ci == self.sec.my_cert assert ci == self.sec.my_cert
res = self.sec.verify_signature("%s" % s_response, res = self.sec.verify_signature("%s" % s_response,
node_name=class_name(samlp.Response())) node_name=class_name(samlp.Response()))
assert res assert res
@@ -332,19 +342,22 @@ class TestSecurity():
assert res == response2 assert res == response2
def test_sign_verify_assertion_with_cert_from_instance(self): def test_sign_verify_assertion_with_cert_from_instance(self):
assertion = factory( saml.Assertion, assertion = factory(saml.Assertion,
version= "2.0", version="2.0",
id= "11100", id="11100",
issue_instant= "2009-10-30T13:20:28Z", issue_instant="2009-10-30T13:20:28Z",
signature= sigver.pre_signature_part("11100", self.sec.my_cert), signature=sigver.pre_signature_part("11100",
attribute_statement=do_attribute_statement({ self.sec
("","","surName"): ("Fox",""), .my_cert),
("","","givenName") :("Bear",""), attribute_statement=do_attribute_statement({
}) ("", "", "surName"): ("Fox", ""),
) ("", "", "givenName"): ("Bear", ""),
})
)
to_sign = [(class_name(assertion), assertion.id)] to_sign = [(class_name(assertion), assertion.id)]
s_assertion = sigver.signed_instance_factory(assertion, self.sec, to_sign) s_assertion = sigver.signed_instance_factory(assertion, self.sec,
to_sign)
print s_assertion print s_assertion
ass = assertion_from_string(s_assertion) ass = assertion_from_string(s_assertion)
ci = "".join(sigver.cert_from_instance(ass)[0].split()) ci = "".join(sigver.cert_from_instance(ass)[0].split())
@@ -359,21 +372,24 @@ class TestSecurity():
assert res assert res
def test_exception_sign_verify_with_cert_from_instance(self): def test_exception_sign_verify_with_cert_from_instance(self):
assertion = factory( saml.Assertion, assertion = factory(saml.Assertion,
version= "2.0", version="2.0",
id= "11100", id="11100",
issue_instant= "2009-10-30T13:20:28Z", issue_instant="2009-10-30T13:20:28Z",
#signature= sigver.pre_signature_part("11100", self.sec.my_cert), #signature= sigver.pre_signature_part("11100",
attribute_statement=do_attribute_statement({ # self.sec.my_cert),
("","","surName"): ("Foo",""), attribute_statement=do_attribute_statement({
("","","givenName") :("Bar",""), ("", "", "surName"): ("Foo", ""),
}) ("", "", "givenName"): ("Bar", ""),
) })
)
response = factory(samlp.Response, response = factory(samlp.Response,
assertion=assertion, assertion=assertion,
id="22222", id="22222",
signature=sigver.pre_signature_part("22222", self.sec.my_cert)) signature=sigver.pre_signature_part("22222",
self.sec
.my_cert))
to_sign = [(class_name(response), response.id)] to_sign = [(class_name(response), response.id)]
@@ -383,7 +399,8 @@ class TestSecurity():
# Change something that should make everything fail # Change something that should make everything fail
response2.id = "23456" response2.id = "23456"
raises(sigver.SignatureError, self.sec._check_signature, raises(sigver.SignatureError, self.sec._check_signature,
s_response, response2, class_name(response2)) s_response, response2, class_name(response2))
class TestSecurityMetadata(): class TestSecurityMetadata():
@@ -397,36 +414,71 @@ class TestSecurityMetadata():
conf.only_use_keys_in_metadata = False conf.only_use_keys_in_metadata = False
self.sec = sigver.security_context(conf) self.sec = sigver.security_context(conf)
self._assertion = factory( saml.Assertion, assertion = factory(
version="2.0", saml.Assertion, version="2.0", id="11111",
id="11111", issue_instant="2009-10-30T13:20:28Z",
issue_instant="2009-10-30T13:20:28Z", signature=sigver.pre_signature_part("11111", self.sec.my_cert, 1),
signature=sigver.pre_signature_part("11111", self.sec.my_cert, 1), attribute_statement=do_attribute_statement(
attribute_statement=do_attribute_statement({ {("", "", "surName"): ("Foo", ""),
("","","surName"): ("Foo",""), ("", "", "givenName"): ("Bar", ""), })
("","","givenName") :("Bar",""),
})
) )
def test_sign_assertion(self):
ass = self._assertion
print ass
sign_ass = self.sec.sign_assertion("%s" % ass, node_id=ass.id)
#print sign_ass
sass = saml.assertion_from_string(sign_ass)
#print sass
assert _eq(sass.keyswv(), ['attribute_statement', 'issue_instant',
'version', 'signature', 'id'])
assert sass.version == "2.0"
assert sass.id == "11111"
assert time_util.str_to_time(sass.issue_instant)
print "Crypto version : %s" % (self.sec.crypto.version()) def test_xbox():
conf = config.SPConfig()
conf.load_file("server_conf")
md = MetadataStore([saml, samlp], None, conf)
md.load("local", full_path("idp_example.xml"))
item = self.sec.check_signature(sass, class_name(sass), sign_ass) conf.metadata = md
conf.only_use_keys_in_metadata = False
sec = sigver.security_context(conf)
assertion = factory(
saml.Assertion, version="2.0", id="11111",
issue_instant="2009-10-30T13:20:28Z",
signature=sigver.pre_signature_part("11111", sec.my_cert, 1),
attribute_statement=do_attribute_statement(
{("", "", "surName"): ("Foo", ""),
("", "", "givenName"): ("Bar", ""), })
)
sigass = sec.sign_statement(assertion, class_name(assertion),
key_file="pki/mykey.pem", node_id=assertion.id)
_ass0 = saml.assertion_from_string(sigass)
encrypted_assertion = EncryptedAssertion()
encrypted_assertion.add_extension_element(_ass0)
_, pre = make_temp("%s" % pre_encryption_part(), decode=False)
enctext = sec.crypto.encrypt(
"%s" % encrypted_assertion, conf.cert_file, pre, "des-192",
'/*[local-name()="EncryptedAssertion"]/*[local-name()="Assertion"]')
decr_text = sec.decrypt(enctext)
_seass = saml.encrypted_assertion_from_string(decr_text)
assertions = []
assers = extension_elements_to_elements(_seass.extension_elements,
[saml, samlp])
sign_cert_file = "pki/mycert.pem"
for ass in assers:
_ass = "%s" % ass
#_ass = _ass.replace('xsi:nil="true" ', '')
#assert sigass == _ass
_txt = sec.verify_signature(_ass, sign_cert_file,
node_name=class_name(assertion))
if _txt:
assertions.append(ass)
print assertions
assert isinstance(item, saml.Assertion)
if __name__ == "__main__": if __name__ == "__main__":
t = TestSecurity() #t = TestSecurity()
t.setup_class() #t.setup_class()
#t.test_sign_then_encrypt_assertion()
test_xbox()

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from saml2 import saml
from saml2 import config from saml2 import config
from saml2.authn_context import INTERNETPROTOCOLPASSWORD from saml2.authn_context import INTERNETPROTOCOLPASSWORD
@@ -9,7 +8,8 @@ from saml2.server import Server
from saml2.response import response_factory from saml2.response import response_factory
from saml2.response import StatusResponse from saml2.response import StatusResponse
from saml2.response import AuthnResponse from saml2.response import AuthnResponse
from saml2.sigver import security_context, MissingKey from saml2.sigver import security_context
from saml2.sigver import MissingKey
from pytest import raises from pytest import raises
@@ -26,7 +26,6 @@ IDENTITY = {"eduPersonAffiliation": ["staff", "member"],
"mail": ["foo@gmail.com"], "mail": ["foo@gmail.com"],
"title": ["shortstop"]} "title": ["shortstop"]}
AUTHN = { AUTHN = {
"class_ref": INTERNETPROTOCOLPASSWORD, "class_ref": INTERNETPROTOCOLPASSWORD,
"authn_auth": "http://www.example.com/login" "authn_auth": "http://www.example.com/login"
@@ -39,29 +38,28 @@ class TestResponse:
name_id = server.ident.transient_nameid( name_id = server.ident.transient_nameid(
"urn:mace:example.com:saml:roland:sp", "id12") "urn:mace:example.com:saml:roland:sp", "id12")
self._resp_ = server.create_authn_response(IDENTITY, self._resp_ = server.create_authn_response(
"id12", # in_response_to IDENTITY,
"http://lingon.catalogix.se:8087/", "id12", # in_response_to
"http://lingon.catalogix.se:8087/",
# consumer_url # consumer_url
"urn:mace:example" "urn:mace:example.com:saml:roland:sp",
".com:saml:roland:sp", # sp_entity_id
# sp_entity_id name_id=name_id)
name_id=name_id)
self._sign_resp_ = server.create_authn_response( self._sign_resp_ = server.create_authn_response(
IDENTITY, IDENTITY,
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=name_id, name_id=name_id,
sign_assertion=True) sign_assertion=True)
self._resp_authn = server.create_authn_response( self._resp_authn = server.create_authn_response(
IDENTITY, IDENTITY,
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=name_id, name_id=name_id,
authn=AUTHN) authn=AUTHN)
@@ -72,7 +70,8 @@ class TestResponse:
def test_1(self): def test_1(self):
xml_response = ("%s" % (self._resp_,)) xml_response = ("%s" % (self._resp_,))
resp = response_factory(xml_response, self.conf, resp = response_factory(xml_response, self.conf,
return_addrs=["http://lingon.catalogix.se:8087/"], return_addrs=[
"http://lingon.catalogix.se:8087/"],
outstanding_queries={ outstanding_queries={
"id12": "http://localhost:8088/sso"}, "id12": "http://localhost:8088/sso"},
timeslack=10000, decode=False) timeslack=10000, decode=False)
@@ -83,7 +82,8 @@ class TestResponse:
def test_2(self): def test_2(self):
xml_response = self._sign_resp_ xml_response = self._sign_resp_
resp = response_factory(xml_response, self.conf, resp = response_factory(xml_response, self.conf,
return_addrs=["http://lingon.catalogix.se:8087/"], return_addrs=[
"http://lingon.catalogix.se:8087/"],
outstanding_queries={ outstanding_queries={
"id12": "http://localhost:8088/sso"}, "id12": "http://localhost:8088/sso"},
timeslack=10000, decode=False) timeslack=10000, decode=False)
@@ -92,15 +92,6 @@ class TestResponse:
assert isinstance(resp, AuthnResponse) assert isinstance(resp, AuthnResponse)
def test_only_use_keys_in_metadata(self):
conf = config.SPConfig()
conf.load_file("sp_2_conf")
sc = security_context(conf)
# should fail
raises(MissingKey,
'sc.correctly_signed_response("%s" % self._sign_resp_)')
if __name__ == "__main__": if __name__ == "__main__":
t = TestResponse() t = TestResponse()
t.setup_class() t.setup_class()

View File

@@ -4,6 +4,7 @@ from saml2.sigver import pre_encryption_part, ASSERT_XPATH, EncryptError
from saml2.sigver import CryptoBackendXmlSec1 from saml2.sigver import CryptoBackendXmlSec1
from saml2.sigver import pre_encrypt_assertion from saml2.sigver import pre_encrypt_assertion
from pathutils import xmlsec_path from pathutils import xmlsec_path
from pathutils import full_path
__author__ = 'roland' __author__ = 'roland'
@@ -65,7 +66,7 @@ def test_enc1():
# data_file.close() # data_file.close()
key_type = "des-192" key_type = "des-192"
com_list = [xmlsec_path, "encrypt", "--pubkey-cert-pem", "pubkey.pem", com_list = [xmlsec_path, "encrypt", "--pubkey-cert-pem", full_path("pubkey.pem"),
"--session-key", key_type, "--xml-data", data, "--session-key", key_type, "--xml-data", data,
"--node-xpath", ASSERT_XPATH] "--node-xpath", ASSERT_XPATH]
@@ -89,7 +90,7 @@ def test_enc2():
IDENTITY, "id12", "http://lingon.catalogix.se:8087/", IDENTITY, "id12", "http://lingon.catalogix.se:8087/",
"urn:mace:example.com:saml:roland:sp", name_id=name_id) "urn:mace:example.com:saml:roland:sp", name_id=name_id)
enc_resp = crypto.encrypt_assertion(resp_, "pubkey.pem", enc_resp = crypto.encrypt_assertion(resp_, full_path("pubkey.pem"),
pre_encryption_part()) pre_encryption_part())
print enc_resp print enc_resp

View File

@@ -2,18 +2,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import base64 import base64
from urlparse import parse_qs from urlparse import parse_qs
from saml2.sigver import pre_encryption_part
from saml2.assertion import Policy from saml2.assertion import Policy
from saml2.authn_context import INTERNETPROTOCOLPASSWORD from saml2.authn_context import INTERNETPROTOCOLPASSWORD
from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT from saml2.saml import NameID, NAMEID_FORMAT_TRANSIENT
from saml2.samlp import response_from_string from saml2.samlp import response_from_string
from saml2.server import Server from saml2.server import Server
from saml2 import samlp, saml, client, config from saml2 import samlp
from saml2 import saml
from saml2 import client
from saml2 import config
from saml2 import class_name
from saml2 import extension_elements_to_elements
from saml2 import s_utils from saml2 import s_utils
from saml2 import sigver from saml2 import sigver
from saml2 import time_util from saml2 import time_util
from saml2.s_utils import OtherError from saml2.s_utils import OtherError
from saml2.s_utils import do_attribute_statement, factory from saml2.s_utils import do_attribute_statement
from saml2.s_utils import factory
from saml2.soap import make_soap_enveloped_saml_thingy from saml2.soap import make_soap_enveloped_saml_thingy
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_REDIRECT
@@ -182,7 +189,8 @@ class TestServer1():
name_id_policy = resp_args["name_id_policy"] name_id_policy = resp_args["name_id_policy"]
assert _eq(name_id_policy.keyswv(), ["format", "allow_create"]) assert _eq(name_id_policy.keyswv(), ["format", "allow_create"])
assert name_id_policy.format == saml.NAMEID_FORMAT_TRANSIENT assert name_id_policy.format == saml.NAMEID_FORMAT_TRANSIENT
assert resp_args["sp_entity_id"] == "urn:mace:example.com:saml:roland:sp" assert resp_args[
"sp_entity_id"] == "urn:mace:example.com:saml:roland:sp"
def test_sso_response_with_identity(self): def test_sso_response_with_identity(self):
name_id = self.server.ident.transient_nameid( name_id = self.server.ident.transient_nameid(
@@ -195,8 +203,8 @@ class TestServer1():
"mail": "derek.jeter@nyy.mlb.com", "mail": "derek.jeter@nyy.mlb.com",
"title": "The man" "title": "The man"
}, },
"id12", # in_response_to "id12", # in_response_to
"http://localhost:8087/", # destination "http://localhost:8087/", # destination
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=name_id, name_id=name_id,
authn=AUTHN authn=AUTHN
@@ -227,7 +235,8 @@ class TestServer1():
break break
assert len(attr.attribute_value) == 1 assert len(attr.attribute_value) == 1
assert attr.name == "urn:oid:1.3.6.1.4.1.5923.1.1.1.7" assert attr.name == "urn:oid:1.3.6.1.4.1.5923.1.1.1.7"
assert attr.name_format == "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" assert attr.name_format == "urn:oasis:names:tc:SAML:2" \
".0:attrname-format:uri"
value = attr.attribute_value[0] value = attr.attribute_value[0]
assert value.text.strip() == "Short stop" assert value.text.strip() == "Short stop"
assert value.get_type() == "xs:string" assert value.get_type() == "xs:string"
@@ -242,13 +251,13 @@ class TestServer1():
def test_sso_response_without_identity(self): def test_sso_response_without_identity(self):
resp = self.server.create_authn_response( resp = self.server.create_authn_response(
{}, {},
"id12", # in_response_to "id12", # in_response_to
"http://localhost:8087/", # consumer_url "http://localhost:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
userid="USER1", userid="USER1",
authn=AUTHN, authn=AUTHN,
release_policy=Policy(), release_policy=Policy(),
best_effort=True best_effort=True
) )
print resp.keyswv() print resp.keyswv()
@@ -268,12 +277,12 @@ class TestServer1():
resp = self.server.create_authn_response( resp = self.server.create_authn_response(
{}, {},
"id12", # in_response_to "id12", # in_response_to
"http://localhost:8087/", # consumer_url "http://localhost:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
userid="USER1", userid="USER1",
authn=_authn, authn=_authn,
best_effort=True best_effort=True
) )
print resp.keyswv() print resp.keyswv()
@@ -297,9 +306,9 @@ class TestServer1():
print resp.status print resp.status
assert resp.status.status_code.value == samlp.STATUS_RESPONDER assert resp.status.status_code.value == samlp.STATUS_RESPONDER
assert resp.status.status_code.status_code.value == \ assert resp.status.status_code.status_code.value == \
samlp.STATUS_REQUEST_UNSUPPORTED samlp.STATUS_REQUEST_UNSUPPORTED
assert resp.status.status_message.text == \ assert resp.status.status_message.text == \
"eduPersonAffiliation missing" "eduPersonAffiliation missing"
assert resp.issuer.text == "urn:mace:example.com:saml:roland:idp" assert resp.issuer.text == "urn:mace:example.com:saml:roland:idp"
assert not resp.assertion assert not resp.assertion
@@ -346,8 +355,8 @@ class TestServer1():
signed_resp = self.server.create_authn_response( signed_resp = self.server.create_authn_response(
ava, ava,
"id12", # in_response_to "id12", # in_response_to
"http://lingon.catalogix.se:8087/", # consumer_url "http://lingon.catalogix.se:8087/", # consumer_url
"urn:mace:example.com:saml:roland:sp", # sp_entity_id "urn:mace:example.com:saml:roland:sp", # sp_entity_id
name_id=name_id, name_id=name_id,
sign_assertion=True sign_assertion=True
@@ -480,7 +489,6 @@ def _logout_request(conf_file):
class TestServerLogout(): class TestServerLogout():
def test_1(self): def test_1(self):
server = Server("idp_slo_redirect_conf") server = Server("idp_slo_redirect_conf")
req_id, request = _logout_request("sp_slo_redirect_conf") req_id, request = _logout_request("sp_slo_redirect_conf")
@@ -502,4 +510,3 @@ class TestServerLogout():
if __name__ == "__main__": if __name__ == "__main__":
ts = TestServer1() ts = TestServer1()
ts.setup_class() ts.setup_class()
ts.test_sso_response_specific_instant()

View File

@@ -4,21 +4,32 @@
import base64 import base64
import urllib import urllib
import urlparse import urlparse
from saml2.authn_context import INTERNETPROTOCOLPASSWORD from saml2 import BINDING_HTTP_POST
from saml2.response import LogoutResponse from saml2 import BINDING_HTTP_REDIRECT
from saml2 import config
from saml2 import class_name
from saml2 import extension_elements_to_elements
from saml2 import saml
from saml2 import samlp
from saml2 import sigver
from saml2 import s_utils
from saml2.assertion import Assertion
from saml2.authn_context import INTERNETPROTOCOLPASSWORD
from saml2.client import Saml2Client from saml2.client import Saml2Client
from saml2 import samlp, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT
from saml2 import saml, config, class_name
from saml2.config import SPConfig from saml2.config import SPConfig
from saml2.response import LogoutResponse
from saml2.saml import NAMEID_FORMAT_PERSISTENT from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.saml import NAMEID_FORMAT_TRANSIENT from saml2.saml import NAMEID_FORMAT_TRANSIENT
from saml2.saml import NameID from saml2.saml import NameID
from saml2.server import Server from saml2.server import Server
from saml2.sigver import pre_encryption_part
from saml2.s_utils import do_attribute_statement
from saml2.s_utils import factory
from saml2.time_util import in_a_while from saml2.time_util import in_a_while
from py.test import raises from fakeIDP import FakeIDP
from fakeIDP import FakeIDP, unpack_form from fakeIDP import unpack_form
AUTHN = { AUTHN = {
@@ -341,8 +352,123 @@ class TestClient:
print my_name print my_name
assert my_name == "urn:mace:example.com:saml:roland:sp" assert my_name == "urn:mace:example.com:saml:roland:sp"
# Below can only be done with dummy Server def test_sign_then_encrypt_assertion(self):
# Begin with the IdPs side
_sec = self.server.sec
assertion = s_utils.assertion_factory(
subject=factory(saml.Subject, text="_aaa",
name_id=factory(
saml.NameID,
format=saml.NAMEID_FORMAT_TRANSIENT)),
attribute_statement=do_attribute_statement(
{
("", "", "surName"): ("Jeter", ""),
("", "", "givenName"): ("Derek", ""),
}
),
issuer=self.server._issuer(),
)
assertion.signature = sigver.pre_signature_part(
assertion.id, _sec.my_cert, 1)
sigass = _sec.sign_statement(assertion, class_name(assertion),
key_file="pki/mykey.pem",
node_id=assertion.id)
# Create an Assertion instance from the signed assertion
_ass = saml.assertion_from_string(sigass)
response = sigver.response_factory(
in_response_to="_012345",
destination="https:#www.example.com",
status=s_utils.success_status_factory(),
issuer=self.server._issuer(),
assertion=_ass
)
enctext = _sec.crypto.encrypt_assertion(response, _sec.cert_file,
pre_encryption_part())
seresp = samlp.response_from_string(enctext)
# Now over to the client side
_csec = self.client.sec
if seresp.encrypted_assertion:
decr_text = _csec.decrypt(enctext)
seresp = samlp.response_from_string(decr_text)
resp_ass = []
sign_cert_file = "pki/mycert.pem"
for enc_ass in seresp.encrypted_assertion:
assers = extension_elements_to_elements(
enc_ass.extension_elements, [saml, samlp])
for ass in assers:
if ass.signature:
if not _csec.verify_signature("%s" % ass,
sign_cert_file,
node_name=class_name(ass)):
continue
resp_ass.append(ass)
seresp.assertion = resp_ass
seresp.encrypted_assertion = None
#print _sresp
assert seresp.assertion
def test_sign_then_encrypt_assertion2(self):
# Begin with the IdPs side
_sec = self.server.sec
nameid_policy = samlp.NameIDPolicy(allow_create="false",
format=saml.NAMEID_FORMAT_PERSISTENT)
asser = Assertion({"givenName": "Derek", "surName": "Jeter"})
assertion = asser.construct(
self.client.config.entityid, "_012345",
"http://lingon.catalogix.se:8087/",
factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT),
policy=self.server.config.getattr("policy", "idp"),
issuer=self.server._issuer(),
attrconvs=self.server.config.attribute_converters,
authn_class=INTERNETPROTOCOLPASSWORD,
authn_auth="http://www.example.com/login")
assertion.signature = sigver.pre_signature_part(
assertion.id, _sec.my_cert, 1)
sigass = _sec.sign_statement(assertion, class_name(assertion),
#key_file="pki/mykey.pem",
key_file="test.key",
node_id=assertion.id)
# Create an Assertion instance from the signed assertion
_ass = saml.assertion_from_string(sigass)
response = sigver.response_factory(
in_response_to="_012345",
destination="https://www.example.com",
status=s_utils.success_status_factory(),
issuer=self.server._issuer(),
assertion=_ass
)
enctext = _sec.crypto.encrypt_assertion(response, _sec.cert_file,
pre_encryption_part())
#seresp = samlp.response_from_string(enctext)
resp_str = base64.encodestring(enctext)
# Now over to the client side
resp = self.client.parse_authn_request_response(
resp_str, BINDING_HTTP_POST,
{"_012345": "http://foo.example.com/service"})
#assert resp.encrypted_assertion == []
assert resp.assertion
assert resp.ava == {'givenName': ['Derek'], 'sn': ['Jeter']}
# Below can only be done with dummy Server
IDP = "urn:mace:example.com:saml:roland:idp" IDP = "urn:mace:example.com:saml:roland:idp"
@@ -448,4 +574,4 @@ class TestClientWithDummy():
if __name__ == "__main__": if __name__ == "__main__":
tc = TestClient() tc = TestClient()
tc.setup_class() tc.setup_class()
tc.test_sign_auth_request_0() tc.test_sign_then_encrypt_assertion2()

View File

@@ -73,6 +73,7 @@ class TestSP():
'sn': ['Jeter'], 'sn': ['Jeter'],
'title': ['The man']} 'title': ['The man']}
if __name__ == "__main__": if __name__ == "__main__":
_sp = TestSP() _sp = TestSP()
_sp.setup_class() _sp.setup_class()

View File

@@ -6,6 +6,7 @@ from saml2.server import Server
__author__ = 'rolandh' __author__ = 'rolandh'
def test_basic(): def test_basic():
sp = Saml2Client(config_file="servera_conf") sp = Saml2Client(config_file="servera_conf")
idp = Server(config_file="idp_all_conf") idp = Server(config_file="idp_all_conf")
@@ -18,7 +19,7 @@ def test_basic():
newid = NewID(text="Barfoo") newid = NewID(text="Barfoo")
mid, mreq = sp.create_manage_name_id_request(destination, name_id=nameid, mid, mreq = sp.create_manage_name_id_request(destination, name_id=nameid,
new_id=newid) new_id=newid)
print mreq print mreq
rargs = sp.apply_binding(binding, "%s" % mreq, destination, "") rargs = sp.apply_binding(binding, "%s" % mreq, destination, "")
@@ -31,6 +32,7 @@ def test_basic():
assert mid == _req.message.id assert mid == _req.message.id
def test_flow(): def test_flow():
sp = Saml2Client(config_file="servera_conf") sp = Saml2Client(config_file="servera_conf")
idp = Server(config_file="idp_all_conf") idp = Server(config_file="idp_all_conf")
@@ -42,7 +44,7 @@ def test_flow():
newid = NewID(text="Barfoo") newid = NewID(text="Barfoo")
mid, midq = sp.create_manage_name_id_request(destination, name_id=nameid, mid, midq = sp.create_manage_name_id_request(destination, name_id=nameid,
new_id=newid) new_id=newid)
print midq print midq
rargs = sp.apply_binding(binding, "%s" % midq, destination, "") rargs = sp.apply_binding(binding, "%s" % midq, destination, "")
@@ -67,8 +69,13 @@ def test_flow():
# ---------- @SP --------------- # ---------- @SP ---------------
_response = sp.parse_manage_name_id_request_response(respargs["data"], binding) _response = sp.parse_manage_name_id_request_response(respargs["data"],
binding)
print _response.response print _response.response
assert _response.response.id == mnir.id assert _response.response.id == mnir.id
if __name__ == "__main__":
test_flow()

View File

@@ -8,7 +8,6 @@ from saml2.cert import OpenSSLWrapper
class TestGenerateCertificates(unittest.TestCase): class TestGenerateCertificates(unittest.TestCase):
def test_validate_with_root_cert(self): def test_validate_with_root_cert(self):
cert_info_ca = { cert_info_ca = {
@@ -33,23 +32,35 @@ class TestGenerateCertificates(unittest.TestCase):
ca_cert, ca_key = osw.create_certificate(cert_info_ca, request=False, ca_cert, ca_key = osw.create_certificate(cert_info_ca, request=False,
write_to_file=True, write_to_file=True,
cert_dir=os.path.dirname(os.path.abspath(__file__)) + "/pki") cert_dir=os.path.dirname(
os.path.abspath(
__file__)) + "/pki")
req_cert_str, req_key_str = osw.create_certificate(cert_info, request=True) req_cert_str, req_key_str = osw.create_certificate(cert_info,
request=True)
ca_cert_str = osw.read_str_from_file(ca_cert) ca_cert_str = osw.read_str_from_file(ca_cert)
ca_key_str = osw.read_str_from_file(ca_key) ca_key_str = osw.read_str_from_file(ca_key)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str) cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str,
req_cert_str)
valid, mess = osw.verify(ca_cert_str, cert_str) valid, mess = osw.verify(ca_cert_str, cert_str)
self.assertTrue(valid) self.assertTrue(valid)
false_ca_cert, false_ca_key = osw.create_certificate(cert_info_ca, request=False, write_to_file=False) false_ca_cert, false_ca_key = osw.create_certificate(cert_info_ca,
false_req_cert_str_1, false_req_key_str_1 = osw.create_certificate(cert_info_ca, request=True) request=False,
false_cert_str_1 = osw.create_cert_signed_certificate(false_ca_cert, false_ca_key, false_req_cert_str_1) write_to_file=False)
false_req_cert_str_2, false_req_key_str_2 = osw.create_certificate(cert_info, request=True) false_req_cert_str_1, false_req_key_str_1 = osw.create_certificate(
false_cert_str_2 = osw.create_cert_signed_certificate(false_ca_cert, false_ca_key, false_req_cert_str_2) cert_info_ca, request=True)
false_cert_str_1 = osw.create_cert_signed_certificate(false_ca_cert,
false_ca_key,
false_req_cert_str_1)
false_req_cert_str_2, false_req_key_str_2 = osw.create_certificate(
cert_info, request=True)
false_cert_str_2 = osw.create_cert_signed_certificate(false_ca_cert,
false_ca_key,
false_req_cert_str_2)
valid, mess = osw.verify(false_ca_cert, cert_str) valid, mess = osw.verify(false_ca_cert, cert_str)
self.assertFalse(valid) self.assertFalse(valid)
@@ -106,20 +117,28 @@ class TestGenerateCertificates(unittest.TestCase):
osw = OpenSSLWrapper() osw = OpenSSLWrapper()
ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca, request=False) ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca,
request=False)
req_cert_str, intermediate_1_key_str = osw.create_certificate(cert_intermediate_1_info, request=True) req_cert_str, intermediate_1_key_str = osw.create_certificate(
intermediate_cert_1_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str) cert_intermediate_1_info, request=True)
intermediate_cert_1_str = osw.create_cert_signed_certificate(
ca_cert_str, ca_key_str, req_cert_str)
req_cert_str, intermediate_2_key_str = osw.create_certificate(cert_intermediate_2_info, request=True) req_cert_str, intermediate_2_key_str = osw.create_certificate(
intermediate_cert_2_str = osw.create_cert_signed_certificate(intermediate_cert_1_str, intermediate_1_key_str, cert_intermediate_2_info, request=True)
req_cert_str) intermediate_cert_2_str = osw.create_cert_signed_certificate(
intermediate_cert_1_str, intermediate_1_key_str,
req_cert_str)
req_cert_str, client_key_str = osw.create_certificate(cert_client_cert_info, request=True) req_cert_str, client_key_str = osw.create_certificate(
client_cert_str = osw.create_cert_signed_certificate(intermediate_cert_2_str, intermediate_2_key_str, cert_client_cert_info, request=True)
req_cert_str) client_cert_str = osw.create_cert_signed_certificate(
intermediate_cert_2_str, intermediate_2_key_str,
req_cert_str)
cert_chain = [intermediate_cert_2_str, intermediate_cert_1_str, ca_cert_str] cert_chain = [intermediate_cert_2_str, intermediate_cert_1_str,
ca_cert_str]
valid, mess = osw.verify_chain(cert_chain, client_cert_str) valid, mess = osw.verify_chain(cert_chain, client_cert_str)
self.assertTrue(valid) self.assertTrue(valid)
@@ -145,21 +164,23 @@ class TestGenerateCertificates(unittest.TestCase):
"organization_unit": "asdfg" "organization_unit": "asdfg"
} }
osw = OpenSSLWrapper() osw = OpenSSLWrapper()
ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca, request=False, ca_cert_str, ca_key_str = osw.create_certificate(
cipher_passphrase= cert_info_ca, request=False,
{"cipher": "blowfish", "passphrase": "qwerty"}) cipher_passphrase={"cipher": "blowfish", "passphrase": "qwerty"})
req_cert_str, req_key_str = osw.create_certificate(cert_info, request=True) req_cert_str, req_key_str = osw.create_certificate(cert_info,
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str, request=True)
passphrase="qwerty") cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str,
req_cert_str,
passphrase="qwerty")
valid = False valid = False
try: try:
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str, cert_str = osw.create_cert_signed_certificate(
passphrase="qwertyqwerty") ca_cert_str, ca_key_str, req_cert_str,
passphrase="qwertyqwerty")
except Exception: except Exception:
valid = True valid = True
@@ -185,39 +206,59 @@ class TestGenerateCertificates(unittest.TestCase):
"organization_unit": "asdfg" "organization_unit": "asdfg"
} }
osw = OpenSSLWrapper() osw = OpenSSLWrapper()
ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca, request=False) ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca,
request=False)
req_cert_str, req_key_str = osw.create_certificate(cert_info, request=True) req_cert_str, req_key_str = osw.create_certificate(cert_info,
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str) request=True)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str,
req_cert_str)
valid, mess = osw.verify(ca_cert_str, cert_str) valid, mess = osw.verify(ca_cert_str, cert_str)
ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca, request=False, valid_from=1000, valid_to=100000) ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca,
req_cert_str, req_key_str = osw.create_certificate(cert_info, request=True) request=False,
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str) valid_from=1000,
valid_to=100000)
req_cert_str, req_key_str = osw.create_certificate(cert_info,
request=True)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str,
req_cert_str)
valid, mess = osw.verify(ca_cert_str, cert_str) valid, mess = osw.verify(ca_cert_str, cert_str)
self.assertFalse(valid) self.assertFalse(valid)
ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca, request=False) ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca,
req_cert_str, req_key_str = osw.create_certificate(cert_info, request=True) request=False)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str, valid_from=1000, req_cert_str, req_key_str = osw.create_certificate(cert_info,
request=True)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str,
req_cert_str,
valid_from=1000,
valid_to=100000) valid_to=100000)
valid, mess = osw.verify(ca_cert_str, cert_str) valid, mess = osw.verify(ca_cert_str, cert_str)
self.assertFalse(valid) self.assertFalse(valid)
ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca, request=False, valid_from=0, valid_to=1) ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca,
req_cert_str, req_key_str = osw.create_certificate(cert_info, request=True) request=False,
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str) valid_from=0,
valid_to=1)
req_cert_str, req_key_str = osw.create_certificate(cert_info,
request=True)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str,
req_cert_str)
time.sleep(2) time.sleep(2)
valid, mess = osw.verify(ca_cert_str, cert_str) valid, mess = osw.verify(ca_cert_str, cert_str)
self.assertFalse(valid) self.assertFalse(valid)
ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca, request=False) ca_cert_str, ca_key_str = osw.create_certificate(cert_info_ca,
req_cert_str, req_key_str = osw.create_certificate(cert_info, request=True) request=False)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str, req_cert_str, valid_from=0, valid_to=1) req_cert_str, req_key_str = osw.create_certificate(cert_info,
request=True)
cert_str = osw.create_cert_signed_certificate(ca_cert_str, ca_key_str,
req_cert_str,
valid_from=0, valid_to=1)
time.sleep(2) time.sleep(2)
valid, mess = osw.verify(ca_cert_str, cert_str) valid, mess = osw.verify(ca_cert_str, cert_str)
self.assertFalse(valid) self.assertFalse(valid)

View File

@@ -9,6 +9,7 @@ from saml2.extension.pefim import SPCertEnc
from saml2.samlp import Extensions from saml2.samlp import Extensions
from saml2.samlp import authn_request_from_string from saml2.samlp import authn_request_from_string
from saml2.sigver import read_cert_from_file from saml2.sigver import read_cert_from_file
from pathutils import full_path
__author__ = 'roland' __author__ = 'roland'
@@ -17,7 +18,7 @@ conf.load_file("server_conf")
client = Saml2Client(conf) client = Saml2Client(conf)
# place a certificate in an authn request # place a certificate in an authn request
cert = read_cert_from_file("test.pem", "pem") cert = read_cert_from_file(full_path("test.pem"), "pem")
spcertenc = SPCertEnc( spcertenc = SPCertEnc(
x509_data=ds.X509Data( x509_data=ds.X509Data(

View File

@@ -87,7 +87,7 @@ def base_init(imports):
line.append("%s%s=%s," % (indent4, _name, _name)) line.append("%s%s=%s," % (indent4, _name, _name))
line.append("%s)" % indent4) line.append("%s)" % indent4)
else: else:
# TODO have to keep apart which properties comes from which superior # TODO have to keep apart which properties come from which superior
for sup, elems in imports.items(): for sup, elems in imports.items():
line.append("%s%s.__init__(self, " % (INDENT+INDENT, sup)) line.append("%s%s.__init__(self, " % (INDENT+INDENT, sup))
lattr = elems[:] lattr = elems[:]
@@ -2187,5 +2187,3 @@ def main(argv):
if __name__ == "__main__": if __name__ == "__main__":
main(sys.argv[1:]) main(sys.argv[1:])

140
tools/sync_attrmaps.py Executable file
View File

@@ -0,0 +1,140 @@
#!/usr/bin/env python
from importlib import import_module
import sys
import os
__author__ = 'roland'
def load(head, tail):
if head == "":
if sys.path[0] != ".":
sys.path.insert(0, ".")
else:
sys.path.insert(0, head)
if tail.endswith(".py"):
tail = tail[:-3]
return import_module(tail)
def intcmp(s1, s2):
try:
_i1 = int(s1)
_i2 = int(s2)
except ValueError:
_i1 = s1
_i2 = s2
if _i1 < _i2:
return -1
if _i1 > _i2:
return 1
else:
return 0
class AMap(object):
def __init__(self, head, tail, indent=4 * " "):
self.mod = load(head, tail)
self.variable = {}
self.vars = []
self.text = []
self.indent = indent
for key, val in self.mod.__dict__.items():
if key.startswith("__"):
continue
elif key == "MAP":
continue
else:
self.variable[key] = val
self.vars.append(key)
self.vars.sort()
def sync(self):
for key, val in self.mod.MAP["fro"].items():
try:
assert self.mod.MAP["to"][val] == key
except KeyError: # missing value
print "# Added %s=%s" % (self.mod.MAP["to"][val], key)
self.mod.MAP["to"][val] = key
except AssertionError:
raise Exception("Mismatch key:%s '%s' != '%s'" % (
key, val, self.mod.MAP["to"][val]))
for val in self.mod.MAP["to"].values():
if val not in self.mod.MAP["fro"]:
print "# Missing URN '%s'" % val
def do_fro(self):
txt = ["%s'fro': {" % self.indent]
i2 = self.indent + self.indent
_fro = self.mod.MAP["fro"]
for var in self.vars:
_v = self.variable[var]
li = [k[len(_v):] for k in _fro.keys() if k.startswith(_v)]
li.sort(intcmp)
for item in li:
txt.append("%s%s+'%s': '%s'," % (i2, var, item,
_fro[_v + item]))
txt.append('%s},' % self.indent)
return txt
def do_to(self):
txt = ["%s'to': {" % self.indent]
i2 = self.indent + self.indent
_to = self.mod.MAP["to"]
_keys = _to.keys()
_keys.sort()
invmap = dict([(v, k) for k, v in self.variable.items()])
for key in _keys:
val = _to[key]
for _urn, _name in invmap.items():
if val.startswith(_urn):
txt.append("%s'%s': %s+'%s'," % (i2, key, _name,
val[len(_urn):]))
txt.append('%s}' % self.indent)
return txt
def __str__(self):
self.sync()
text = []
for key in self.vars:
text.append("%s = '%s'" % (key, self.variable[key]))
text.extend(["", ""])
text.append("MAP = {")
text.append("%s'identifier': '%s'," % (self.indent,
self.mod.MAP["identifier"]))
text.extend(self.do_fro())
text.extend(self.do_to())
text.append("}")
text.append("")
return "\n".join(text)
if __name__ == "__main__":
_name = sys.argv[1]
if os.path.isfile(_name):
directory, fname = os.path.split(_name)
amap = AMap(directory, fname, 4 * " ")
f = open(_name)
f.write("%s" % amap)
f.close()
elif os.path.isdir(_name):
for fname in os.listdir(_name):
if fname == "__init__.py":
continue
elif fname.endswith(".pyc"):
continue
print 10 * "=" + fname + 10 * "="
amap = AMap(_name, fname, 4 * " ")
f = open(fname, "w")
f.write("%s" % amap)
f.close()