Initial add
This commit is contained in:
		
							
								
								
									
										10
									
								
								CHANGES
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								CHANGES
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| 0.4.2 (2012-03-27) | ||||
| ------------------ | ||||
| - Add default attribute mappings | ||||
|  | ||||
| 0.4.1 (2012-03-18) | ||||
| ------------------ | ||||
| - Auto sign authentication and logout requests following config options. | ||||
| - Add backwards compatibility with ElementTree in python < 2.7. | ||||
| - Fix minor bugs in the tests. | ||||
| - Support one more nameid format. | ||||
							
								
								
									
										27
									
								
								INSTALL
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								INSTALL
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| You need repoze.who to get the examples working, can be gotten through  | ||||
| easy_install | ||||
|  | ||||
|     easy_install "repoze.who=1.0.16" | ||||
|  | ||||
| !! 2.0 or newer are missing the form plugin which is used in some instances | ||||
|  | ||||
| Or from the PyPi site if you prefer to do it that way. | ||||
| Likewise for pyasn1. | ||||
|  | ||||
| You should get the latest version, which is right now 1.0.18 . | ||||
|  | ||||
| You also need xmlsec, which you can find here: | ||||
|  | ||||
|     http://www.aleksey.com/xmlsec/ | ||||
|  | ||||
| You may also need: | ||||
|  | ||||
|  mako | ||||
|  memcached | ||||
|  python-memcache | ||||
|   | ||||
| Apart from that a normal | ||||
|      | ||||
|     python setup.py install | ||||
|  | ||||
| will install the package. | ||||
							
								
								
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| include INSTALL | ||||
| include README | ||||
| include TODO | ||||
| recursive-include tests * | ||||
| recursive-include example * | ||||
| recursive-include doc * | ||||
							
								
								
									
										57
									
								
								README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| README for PySAML2 | ||||
| ================== | ||||
|  | ||||
| Dependencies | ||||
| ------------ | ||||
| Pylint should be compatible with any python >= 2.6 not 3.X yet. | ||||
| To be able to sign/verify, encrypt/decrypt you need xmlsec1. | ||||
| The repoze stuff works best together with repoze.who . | ||||
|  | ||||
| * http://www.aleksey.com/xmlsec/ | ||||
| * http://static.repoze.org/whodocs/ | ||||
|  | ||||
| Install | ||||
| ------- | ||||
| You need repoze.who to get the examples working, can be gotten through  | ||||
| easy_install | ||||
|  | ||||
|     easy_install repoze.who | ||||
|  | ||||
| Or from the PyPi site if you prefer to do it that way. | ||||
| You should get the latest version, which is right now 1.0.18 . | ||||
|  | ||||
| You also need xmlsec, which you can find here: | ||||
|  | ||||
|     http://www.aleksey.com/xmlsec/ | ||||
|  | ||||
| Apart from that a normal | ||||
|      | ||||
|     python setup.py install | ||||
|  | ||||
| will install the package. | ||||
|  | ||||
| Documentation | ||||
| ------------- | ||||
| Look in the doc/ subdirectory. | ||||
|  | ||||
| Comments, support, bug reports | ||||
| ------------------------------ | ||||
|  | ||||
| Project page on :  | ||||
|  | ||||
| https://code.launchpad.net/~roland-hedberg/pysaml2/main | ||||
|  | ||||
| Use the Pysaml2@uma.es mailing list. Since we do not have | ||||
| publicly available bug tracker yet, bug reports should be emailed | ||||
| there too.  | ||||
|  | ||||
| You can subscribe to this mailing list at | ||||
| http://delfos.sci.uma.es/mailman/listinfo/pysaml2 | ||||
|  | ||||
| Archives are available at  | ||||
| http://delfos.sci.uma.es/mailman/private/pysaml2/ | ||||
|  | ||||
| Contributors | ||||
| ------------ | ||||
| * Roland Hedberg: main author / maintainer | ||||
| * Lorenzo Gil Sanchez: Django integration | ||||
							
								
								
									
										15
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
|  | ||||
| ************************* | ||||
| PySAML2 - SAML2 in Python | ||||
| ************************* | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: 0.3 | ||||
|  | ||||
| PySAML2 is a pure python implementation of a SAML2 service provider and to | ||||
| some extend also the identity provider. Originally written to work in a WSGI | ||||
| environment there are extensions that allow you to use it with other | ||||
| frameworks. | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										3
									
								
								TODO
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								TODO
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| 1. Write documentations. | ||||
| 2. Write unittests for signature related utility methods. | ||||
| 3. Complete saml2 message class. | ||||
							
								
								
									
										88
									
								
								doc/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								doc/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| # Makefile for Sphinx documentation | ||||
| # | ||||
|  | ||||
| # You can set these variables from the command line. | ||||
| SPHINXOPTS    = | ||||
| SPHINXBUILD   = sphinx-build | ||||
| PAPER         = | ||||
|  | ||||
| # Internal variables. | ||||
| PAPEROPT_a4     = -D latex_paper_size=a4 | ||||
| PAPEROPT_letter = -D latex_paper_size=letter | ||||
| ALLSPHINXOPTS   = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||||
|  | ||||
| .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest | ||||
|  | ||||
| help: | ||||
| 	@echo "Please use \`make <target>' where <target> is one of" | ||||
| 	@echo "  html      to make standalone HTML files" | ||||
| 	@echo "  dirhtml   to make HTML files named index.html in directories" | ||||
| 	@echo "  pickle    to make pickle files" | ||||
| 	@echo "  json      to make JSON files" | ||||
| 	@echo "  htmlhelp  to make HTML files and a HTML help project" | ||||
| 	@echo "  qthelp    to make HTML files and a qthelp project" | ||||
| 	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | ||||
| 	@echo "  changes   to make an overview of all changed/added/deprecated items" | ||||
| 	@echo "  linkcheck to check all external links for integrity" | ||||
| 	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)" | ||||
|  | ||||
| clean: | ||||
| 	-rm -rf _build/* | ||||
|  | ||||
| html: | ||||
| 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML pages are in _build/html." | ||||
|  | ||||
| dirhtml: | ||||
| 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML pages are in _build/dirhtml." | ||||
|  | ||||
| pickle: | ||||
| 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can process the pickle files." | ||||
|  | ||||
| json: | ||||
| 	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can process the JSON files." | ||||
|  | ||||
| htmlhelp: | ||||
| 	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can run HTML Help Workshop with the" \ | ||||
| 	      ".hhp project file in _build/htmlhelp." | ||||
|  | ||||
| qthelp: | ||||
| 	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \ | ||||
| 	      ".qhcp project file in _build/qthelp, like this:" | ||||
| 	@echo "# qcollectiongenerator _build/qthelp/pysaml2.qhcp" | ||||
| 	@echo "To view the help file:" | ||||
| 	@echo "# assistant -collectionFile _build/qthelp/pysaml2.qhc" | ||||
|  | ||||
| latex: | ||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex | ||||
| 	@echo | ||||
| 	@echo "Build finished; the LaTeX files are in _build/latex." | ||||
| 	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ | ||||
| 	      "run these through (pdf)latex." | ||||
|  | ||||
| changes: | ||||
| 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes | ||||
| 	@echo | ||||
| 	@echo "The overview file is in _build/changes." | ||||
|  | ||||
| linkcheck: | ||||
| 	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck | ||||
| 	@echo | ||||
| 	@echo "Link check complete; look for any errors in the above output " \ | ||||
| 	      "or in _build/linkcheck/output.txt." | ||||
|  | ||||
| doctest: | ||||
| 	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest | ||||
| 	@echo "Testing of doctests in the sources finished, look at the " \ | ||||
| 	      "results in _build/doctest/output.txt." | ||||
							
								
								
									
										18
									
								
								doc/client.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								doc/client.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| .. _client: | ||||
|  | ||||
| *********************************************** | ||||
| Classes representing Service Provider instances | ||||
| *********************************************** | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: Client | ||||
|    :synopsis: Classes representing Service Provider instances. | ||||
|  | ||||
| Module | ||||
| ========== | ||||
|  | ||||
| .. automodule:: saml2.client | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										194
									
								
								doc/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								doc/conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,194 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # pysaml2 documentation build configuration file, created by | ||||
| # sphinx-quickstart on Mon Aug 24 08:13:41 2009. | ||||
| # | ||||
| # This file is execfile()d with the current directory set to its containing dir. | ||||
| # | ||||
| # Note that not all possible configuration values are present in this | ||||
| # autogenerated file. | ||||
| # | ||||
| # All configuration values have a default; values that are commented out | ||||
| # serve to show the default. | ||||
|  | ||||
| import sys, os | ||||
|  | ||||
| # If extensions (or modules to document with autodoc) are in another directory, | ||||
| # add these directories to sys.path here. If the directory is relative to the | ||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. | ||||
| #sys.path.append(os.path.abspath('.')) | ||||
|  | ||||
| # -- General configuration ----------------------------------------------------- | ||||
|  | ||||
| # Add any Sphinx extension module names here, as strings. They can be extensions | ||||
| # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. | ||||
| extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage'] | ||||
|  | ||||
| # Add any paths that contain templates here, relative to this directory. | ||||
| templates_path = ['_templates'] | ||||
|  | ||||
| # The suffix of source filenames. | ||||
| source_suffix = '.rst' | ||||
|  | ||||
| # The encoding of source files. | ||||
| #source_encoding = 'utf-8' | ||||
|  | ||||
| # The master toctree document. | ||||
| master_doc = 'index' | ||||
|  | ||||
| # General information about the project. | ||||
| project = u'pysaml2' | ||||
| copyright = u'2010-2011, Roland Hedberg' | ||||
|  | ||||
| # The version info for the project you're documenting, acts as replacement for | ||||
| # |version| and |release|, also used in various other places throughout the | ||||
| # built documents. | ||||
| # | ||||
| # The short X.Y version. | ||||
| version = '0.4' | ||||
| # The full version, including alpha/beta/rc tags. | ||||
| release = '0.4.2' | ||||
|  | ||||
| # The language for content autogenerated by Sphinx. Refer to documentation | ||||
| # for a list of supported languages. | ||||
| #language = None | ||||
|  | ||||
| # There are two options for replacing |today|: either, you set today to some | ||||
| # non-false value, then it is used: | ||||
| #today = '' | ||||
| # Else, today_fmt is used as the format for a strftime call. | ||||
| #today_fmt = '%B %d, %Y' | ||||
|  | ||||
| # List of documents that shouldn't be included in the build. | ||||
| #unused_docs = [] | ||||
|  | ||||
| # List of directories, relative to source directory, that shouldn't be searched | ||||
| # for source files. | ||||
| exclude_trees = ['_build'] | ||||
|  | ||||
| # The reST default role (used for this markup: `text`) to use for all documents. | ||||
| #default_role = None | ||||
|  | ||||
| # If true, '()' will be appended to :func: etc. cross-reference text. | ||||
| #add_function_parentheses = True | ||||
|  | ||||
| # If true, the current module name will be prepended to all description | ||||
| # unit titles (such as .. function::). | ||||
| #add_module_names = True | ||||
|  | ||||
| # If true, sectionauthor and moduleauthor directives will be shown in the | ||||
| # output. They are ignored by default. | ||||
| #show_authors = False | ||||
|  | ||||
| # The name of the Pygments (syntax highlighting) style to use. | ||||
| pygments_style = 'sphinx' | ||||
|  | ||||
| # A list of ignored prefixes for module index sorting. | ||||
| #modindex_common_prefix = [] | ||||
|  | ||||
|  | ||||
| # -- Options for HTML output --------------------------------------------------- | ||||
|  | ||||
| # The theme to use for HTML and HTML Help pages.  Major themes that come with | ||||
| # Sphinx are currently 'default' and 'sphinxdoc'. | ||||
| html_theme = 'default' | ||||
|  | ||||
| # Theme options are theme-specific and customize the look and feel of a theme | ||||
| # further.  For a list of options available for each theme, see the | ||||
| # documentation. | ||||
| #html_theme_options = {} | ||||
|  | ||||
| # Add any paths that contain custom themes here, relative to this directory. | ||||
| #html_theme_path = [] | ||||
|  | ||||
| # The name for this set of Sphinx documents.  If None, it defaults to | ||||
| # "<project> v<release> documentation". | ||||
| #html_title = None | ||||
|  | ||||
| # A shorter title for the navigation bar.  Default is the same as html_title. | ||||
| #html_short_title = None | ||||
|  | ||||
| # The name of an image file (relative to this directory) to place at the top | ||||
| # of the sidebar. | ||||
| #html_logo = None | ||||
|  | ||||
| # The name of an image file (within the static path) to use as favicon of the | ||||
| # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 | ||||
| # pixels large. | ||||
| #html_favicon = None | ||||
|  | ||||
| # Add any paths that contain custom static files (such as style sheets) here, | ||||
| # relative to this directory. They are copied after the builtin static files, | ||||
| # so a file named "default.css" will overwrite the builtin "default.css". | ||||
| html_static_path = ['_static'] | ||||
|  | ||||
| # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | ||||
| # using the given strftime format. | ||||
| #html_last_updated_fmt = '%b %d, %Y' | ||||
|  | ||||
| # If true, SmartyPants will be used to convert quotes and dashes to | ||||
| # typographically correct entities. | ||||
| #html_use_smartypants = True | ||||
|  | ||||
| # Custom sidebar templates, maps document names to template names. | ||||
| #html_sidebars = {} | ||||
|  | ||||
| # Additional templates that should be rendered to pages, maps page names to | ||||
| # template names. | ||||
| #html_additional_pages = {} | ||||
|  | ||||
| # If false, no module index is generated. | ||||
| #html_use_modindex = True | ||||
|  | ||||
| # If false, no index is generated. | ||||
| #html_use_index = True | ||||
|  | ||||
| # If true, the index is split into individual pages for each letter. | ||||
| #html_split_index = False | ||||
|  | ||||
| # If true, links to the reST sources are added to the pages. | ||||
| #html_show_sourcelink = True | ||||
|  | ||||
| # If true, an OpenSearch description file will be output, and all pages will | ||||
| # contain a <link> tag referring to it.  The value of this option must be the | ||||
| # base URL from which the finished HTML is served. | ||||
| #html_use_opensearch = '' | ||||
|  | ||||
| # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). | ||||
| #html_file_suffix = '' | ||||
|  | ||||
| # Output file base name for HTML help builder. | ||||
| htmlhelp_basename = 'pysaml2doc' | ||||
|  | ||||
|  | ||||
| # -- Options for LaTeX output -------------------------------------------------- | ||||
|  | ||||
| # The paper size ('letter' or 'a4'). | ||||
| #latex_paper_size = 'letter' | ||||
|  | ||||
| # The font size ('10pt', '11pt' or '12pt'). | ||||
| #latex_font_size = '10pt' | ||||
|  | ||||
| # Grouping the document tree into LaTeX files. List of tuples | ||||
| # (source start file, target name, title, author, documentclass [howto/manual]). | ||||
| latex_documents = [ | ||||
|   ('index', 'pysaml2.tex', u'pysaml2 Documentation', | ||||
|    u'Roland Hedberg', 'manual'), | ||||
| ] | ||||
|  | ||||
| # The name of an image file (relative to this directory) to place at the top of | ||||
| # the title page. | ||||
| #latex_logo = None | ||||
|  | ||||
| # For "manual" documents, if this is true, then toplevel headings are parts, | ||||
| # not chapters. | ||||
| #latex_use_parts = False | ||||
|  | ||||
| # Additional stuff for the LaTeX preamble. | ||||
| #latex_preamble = '' | ||||
|  | ||||
| # Documents to append as an appendix to all manuals. | ||||
| #latex_appendices = [] | ||||
|  | ||||
| # If false, no module index is generated. | ||||
| #latex_use_modindex = True | ||||
							
								
								
									
										4
									
								
								doc/examples/idp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								doc/examples/idp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| .. _example_idp: | ||||
|  | ||||
| An extremly simple example of a SAML2 identity provider. | ||||
| ======================================================== | ||||
							
								
								
									
										23
									
								
								doc/examples/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								doc/examples/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| .. _example_index: | ||||
|  | ||||
| These are examples of the usage of pySAML2! | ||||
| =========================================== | ||||
|  | ||||
| :Release: |version| | ||||
| :Date: |today| | ||||
|  | ||||
| Contents: | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 1 | ||||
|  | ||||
|    sp | ||||
|    idp | ||||
|     | ||||
| Indices and tables | ||||
| ================== | ||||
|  | ||||
| * :ref:`genindex` | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
|  | ||||
							
								
								
									
										193
									
								
								doc/examples/sp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								doc/examples/sp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| .. _example_sp: | ||||
|  | ||||
| An extremly simple example of a SAML2 service provider. | ||||
| ======================================================= | ||||
|  | ||||
| How it works | ||||
| ------------ | ||||
|  | ||||
| A SP works with authentication and possibly attribute aggregation. | ||||
| Both of these functions can be seen as parts of the normal Repoze.who | ||||
| setup. Namely the Challenger, Identifier and MetadataProvider parts. | ||||
|  | ||||
| Normal for Repoze.who Identifier and MetadataProvider plugins are that | ||||
| they place information in environment variables. The convention is to place | ||||
| identity information in environ["repoze.who.identity"]. | ||||
| This is a dictionary with keys like 'login', and 'repoze.who.userid'. | ||||
|  | ||||
| The SP follows this pattern and places the information gathered from  | ||||
| the IdP that handled the authentication and possible extra information | ||||
| received from attribute authorities in the above mentioned dictionary under | ||||
| the key 'user'. | ||||
|  | ||||
| So in environ["repoze.who.identity"] you will find a dictionary with  | ||||
| attributes and values, the attribute names used depends on what's returned | ||||
| from the IdP/AA. If there exists both a name and a friendly name, for | ||||
| instance, the friendly name is used as the key. | ||||
|  | ||||
| Setup | ||||
| ----- | ||||
|  | ||||
| I you look in the example/sp directory of the distribution you will see | ||||
| the necessary files: | ||||
|  | ||||
| application.py  | ||||
|     which is the web application. In this case it will just print the | ||||
|     information provided by the IdP in a table. | ||||
|      | ||||
| sp_conf.py | ||||
|     The SPs configuration  | ||||
|      | ||||
| who.ini | ||||
|     The repoze.who configuration file | ||||
|      | ||||
| And then there are two files with certificates, mykey.pem with the private | ||||
| certificate and mycert.pem with the public part. | ||||
|  | ||||
| I'll go through these step by step. | ||||
|  | ||||
| The application | ||||
| --------------- | ||||
|  | ||||
| Build to use the wsgiref's simple_server, which is fine for testing but | ||||
| not for production. | ||||
|  | ||||
|  | ||||
| SP configuration | ||||
| ---------------- | ||||
|  | ||||
| The configuration is written as described in :ref:`howto_config`. It means among other | ||||
| things that it's easily testable as to the correct syntax. | ||||
|  | ||||
| You can see the whole file in example/sp/sp_conf.py, here I will go through | ||||
| it line by line:: | ||||
|  | ||||
|         "service": ["sp"], | ||||
|  | ||||
| Tells the software what type of services the software are suppost to | ||||
| supply. It is used to check for the  | ||||
| completeness of the configuration and also when constructing metadata from | ||||
| the configuration. More about that later. Allowed values are: "sp"  | ||||
| (service provider), "idp" (identity provider) and "aa" (attribute authority). | ||||
| :: | ||||
|  | ||||
|         "entityid" : "urn:mace:example.com:saml:sp", | ||||
|         "service_url" : "http://example.com:8087/", | ||||
|          | ||||
| The ID of the entity and the URL on which it is listening.:: | ||||
|  | ||||
|         "idp_url" : "https://example.com/saml2/idp/SSOService.php", | ||||
|  | ||||
| Since this is a very simple SP it only need to know about one IdP, therefor there | ||||
| is really no need for a metadata file or a WAYF-function or anything like that. | ||||
| It needs the URL of the IdP and that's all.:: | ||||
|  | ||||
|         "my_name" : "My first SP", | ||||
|          | ||||
| This is just for informal purposes, not really needed but nice to do:: | ||||
|  | ||||
|         "debug" : 1, | ||||
|          | ||||
| Well, at this point in time you'd really like to have as much information | ||||
| as possible as to what's going on, right ? :: | ||||
|  | ||||
|         "key_file" : "./mykey.pem", | ||||
|         "cert_file" : "./mycert.pem", | ||||
|  | ||||
| The necessary certificates.:: | ||||
|  | ||||
|         "xmlsec_binary" : "/opt/local/bin/xmlsec1", | ||||
|  | ||||
| Right now the software is built to use xmlsec binaries and not the python | ||||
| xmlsec package. There are reasons for this but I won't go into them here.:: | ||||
|  | ||||
|         "organization": { | ||||
|             "name": "Example Co", | ||||
|             #display_name | ||||
|             "url":"http://www.example.com/",             | ||||
|         }, | ||||
|  | ||||
| Information about the organization that is behind this SP, only used when | ||||
| building metadata. :: | ||||
|  | ||||
|         "contact": [{ | ||||
|             "given_name":"John", | ||||
|             "sur_name": "Smith", | ||||
|             "email_address": "john.smith@example.com", | ||||
|             #contact_type | ||||
|             #company | ||||
|             #telephone_number | ||||
|         }] | ||||
|  | ||||
| Another piece of information that only is matters if you build and distribute | ||||
| metadata. | ||||
|  | ||||
| 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. | ||||
| 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 | ||||
| by running the make_metadata script you can find in the tools directory.  | ||||
|  | ||||
| Change directory to where you have the configuration file and do :: | ||||
|  | ||||
|     make_metadata.py sp_conf.py > metadata.xml | ||||
|      | ||||
|  | ||||
|  | ||||
| Repoze configuration | ||||
| -------------------- | ||||
|  | ||||
| I'm not going through the INI file format here. You should read | ||||
| `Middleware Responsibilities <http://static.repoze.org/whodocs/narr.html>`_  | ||||
| to get a good introduction to the concept. | ||||
|  | ||||
| The configuration of the pysaml2 part in the applications middleware are | ||||
| first the special module configuration, namely:: | ||||
|  | ||||
|     [plugin:saml2auth] | ||||
|     use = s2repoze.plugins.sp:make_plugin | ||||
|     saml_conf = sp_conf.py | ||||
|     rememberer_name = auth_tkt | ||||
|     debug = 1 | ||||
|     path_logout = .*/logout.* | ||||
|  | ||||
| Which contains a specification ("use") of which function in which module  | ||||
| should be used to initialize the part. After that comes the name of the  | ||||
| file ("saml_conf") that contains the PySaml2 configuration. The third line | ||||
| ("rememberer_name") points at the plugin that should be used to  | ||||
| remember the user information. | ||||
|  | ||||
| After this, the plugin is referenced in a couple of places:: | ||||
|  | ||||
|     [identifiers] | ||||
|     plugins = | ||||
|           saml2auth | ||||
|           auth_tkt | ||||
|            | ||||
|     [authenticators] | ||||
|     plugins = saml2auth | ||||
|  | ||||
|     [challengers] | ||||
|     plugins = saml2auth | ||||
|  | ||||
|     [mdproviders] | ||||
|     plugins = saml2auth | ||||
|  | ||||
| Which means that the plugin is used in all phases. | ||||
|  | ||||
| The application | ||||
| --------------- | ||||
|  | ||||
| Is as said before extremly simple. The only thing that is connected to | ||||
| the PySaml2 configuration are at the bottom, namely where the server are. | ||||
| You have to ascertain that this coincides with what is specified in the  | ||||
| PySaml2 configuration. Apart from that there really are no thing in  | ||||
| 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  | ||||
| application. In the application configuration yes! But not in the application. | ||||
| And that is really how it should be done. | ||||
|  | ||||
| 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 | ||||
| "user" property in the dictionary found under the key "repoze.who.identity" | ||||
| in the environment. | ||||
							
								
								
									
										616
									
								
								doc/howto/config.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										616
									
								
								doc/howto/config.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,616 @@ | ||||
| .. _howto_config: | ||||
|  | ||||
| Configuration of pySAML2 entities | ||||
| ================================= | ||||
|  | ||||
| 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 | ||||
| file is the same disregarding which type of service you plan to run. | ||||
| What differs is some of the directives. | ||||
| Below you will find a list of all the used directives in alphabetic order. | ||||
| The configuration is written as a python module which contains a named | ||||
| dictionary ("CONFIG") that contains the configuration directives. | ||||
|  | ||||
| The basic structure of the configuration file is therefor like this:: | ||||
|  | ||||
|     from saml2 import BINDING_HTTP_REDIRECT | ||||
|  | ||||
|     CONFIG = { | ||||
|         "entityid" : "http://saml.example.com:saml/idp.xml", | ||||
|         "name" : "Rolands IdP", | ||||
|         "service": { | ||||
|             "idp": { | ||||
|                 "endpoints" : { | ||||
|                     "single_sign_on_service" : [ | ||||
|                             ("http://saml.example.com:saml:8088/sso", | ||||
|                                 BINDING_HTTP_REDIRECT)], | ||||
|                     "single_logout_service": [ | ||||
|                             ("http://saml.example.com:saml:8088/slo", | ||||
|                                 BINDING_HTTP_REDIRECT)] | ||||
|                 }, | ||||
|                 ... | ||||
|             } | ||||
|         }, | ||||
|         "key_file" : "my.key", | ||||
|         "cert_file" : "ca.pem", | ||||
|         "xmlsec_binary" : "/usr/local/bin/xmlsec1", | ||||
|         "metadata": { | ||||
|             "local": ["edugain.xml"], | ||||
|         }, | ||||
|         "attribute_map_dir" : "attributemaps", | ||||
|         ... | ||||
|     } | ||||
|  | ||||
| .. note:: You can build the metadata file for your services directly from the | ||||
|     configuration.The make_metadata.py script in the pySAML2 tools directory | ||||
|     will do that for you. | ||||
|  | ||||
| Configuration directives | ||||
| :::::::::::::::::::::::: | ||||
|  | ||||
| .. contents:: | ||||
|     :local: | ||||
|     :backlinks: entry | ||||
|  | ||||
| General directives | ||||
| ------------------ | ||||
|  | ||||
| attribute_map_dir | ||||
| ^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| Format:: | ||||
|  | ||||
|     "attribute_map_dir": "attribute-maps" | ||||
|      | ||||
| Points to a directory which has the attribute maps in Python modules. | ||||
| A typical map file will looks like this:: | ||||
|  | ||||
|     MAP = { | ||||
|         "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", | ||||
|         "fro": { | ||||
|             'urn:mace:dir:attribute-def:aRecord': 'aRecord', | ||||
|             'urn:mace:dir:attribute-def:aliasedEntryName': 'aliasedEntryName', | ||||
|             'urn:mace:dir:attribute-def:aliasedObjectName': 'aliasedObjectName', | ||||
|             'urn:mace:dir:attribute-def:associatedDomain': 'associatedDomain', | ||||
|             'urn:mace:dir:attribute-def:associatedName': 'associatedName', | ||||
|             ... | ||||
|             }, | ||||
|         "to": { | ||||
|             'aRecord': 'urn:mace:dir:attribute-def:aRecord', | ||||
|             'aliasedEntryName': 'urn:mace:dir:attribute-def:aliasedEntryName', | ||||
|             'aliasedObjectName': 'urn:mace:dir:attribute-def:aliasedObjectName', | ||||
|             'associatedDomain': 'urn:mace:dir:attribute-def:associatedDomain', | ||||
|             'associatedName': 'urn:mace:dir:attribute-def:associatedName', | ||||
|             ... | ||||
|         } | ||||
|     } | ||||
|  | ||||
| The attribute map module contains a MAP dictionary with three items.  The | ||||
| `identifier` item is the name-format you expect to support. | ||||
| 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 | ||||
| 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  | ||||
| software allowes you to only specify one of them and it will  | ||||
| automatically create the other. | ||||
|  | ||||
| cert_file | ||||
| ^^^^^^^^^ | ||||
|  | ||||
| Format:: | ||||
|  | ||||
|     cert_file: "cert.pem" | ||||
|  | ||||
| This is the public part of the service private/public key pair. | ||||
| *cert_file* must be a PEM formatted certificate chain file. | ||||
|  | ||||
| contact_person | ||||
| ^^^^^^^^^^^^^^ | ||||
|  | ||||
| This is only used by *make_metadata.py* when it constructs the metadata for  | ||||
| the service described by the configuration file. | ||||
| This is where you described who can be contacted if questions arises | ||||
| about the service or if support is needed. The possible types are according to | ||||
| the standard **technical**, **support**, **administrative**, **billing**  | ||||
| and **other**.:: | ||||
|  | ||||
|     contact_person: [{ | ||||
|         "givenname": "Derek", | ||||
|         "surname": "Jeter", | ||||
|         "company": "Example Co.", | ||||
|         "mail": ["jeter@example.com"], | ||||
|         "type": "technical", | ||||
|     },{ | ||||
|         "givenname": "Joe", | ||||
|         "surname": "Girardi", | ||||
|         "company": "Example Co.", | ||||
|         "mail": "girardi@example.com", | ||||
|         "type": "administrative", | ||||
|     }] | ||||
|  | ||||
| debug | ||||
| ^^^^^ | ||||
|  | ||||
| Format:: | ||||
|  | ||||
|     debug: 1 | ||||
|  | ||||
| Whether debug information should be sent to the log file. | ||||
|  | ||||
| entityid | ||||
| ^^^^^^^^ | ||||
|  | ||||
| Format:: | ||||
|  | ||||
|     entityid: "http://saml.example.com/sp" | ||||
|  | ||||
| The globally unique identifier of the entity. | ||||
|  | ||||
| .. note:: There is a recommendation that the entityid should point to a real | ||||
|     webpage where the metadata for the entity can be found. | ||||
|  | ||||
| key_file | ||||
| ^^^^^^^^ | ||||
|  | ||||
| Format:: | ||||
|  | ||||
|     key_file: "key.pem" | ||||
|  | ||||
| *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 | ||||
| client key in a HTTPS session. | ||||
|  | ||||
| metadata | ||||
| ^^^^^^^^ | ||||
|  | ||||
| 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.:: | ||||
|  | ||||
|     "metadata" : { | ||||
|         "local": [ | ||||
|             "metadata.xml", "vo_metadata.xml" | ||||
|             ], | ||||
|         "remote": [ | ||||
|             { | ||||
|                 "url":"https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2", | ||||
|                 "cert":"kalmar2.cert" | ||||
|             }], | ||||
|     }, | ||||
|  | ||||
| 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 | ||||
| authenticity of the file downloaded from the net the local copy of the  | ||||
| public key should be used. | ||||
| This public key must be acquired by some out-of-band method. | ||||
|  | ||||
| organization | ||||
| ^^^^^^^^^^^^ | ||||
|  | ||||
| Only used by *make_metadata.py*. | ||||
| Where you describe the organization responsible for the service.:: | ||||
|  | ||||
|     "organization": { | ||||
|         "name": [("Example Company","en"), ("Exempel AB","se")], | ||||
|         "display_name": ["Exempel AB"], | ||||
|         "url": [("http://example.com","en"),("http://exempel.se","se")], | ||||
|     } | ||||
|  | ||||
| .. note:: You can specify the language of the name, or the language used on | ||||
|     the webpage, by entering a tuple, instead of a simple string,  | ||||
|     where the second part is the language code. If you don't specify a | ||||
|     language the default is "en" (English). | ||||
|  | ||||
| service | ||||
| ^^^^^^^ | ||||
|  | ||||
| Which services the server will provide, those are combinations of "idp","sp"  | ||||
| and "aa". | ||||
| So if a server is a Service Provider (SP) then the configuration  | ||||
| could look something like this:: | ||||
|  | ||||
|     "service": { | ||||
|         "sp":{ | ||||
|             "name" : "Rolands SP", | ||||
|             "endpoints":{ | ||||
|                 "assertion_consumer_service": ["http://localhost:8087/"], | ||||
|                 "single_logout_service" : [("http://localhost:8087/slo", | ||||
|                                'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect')], | ||||
|             }, | ||||
|             "required_attributes": ["surname", "givenname", "edupersonaffiliation"], | ||||
|             "optional_attributes": ["title"], | ||||
|             "idp": { | ||||
|                 "urn:mace:umu.se:saml:roland:idp": None, | ||||
|             }, | ||||
|         } | ||||
|     }, | ||||
|      | ||||
| 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. | ||||
| Which one is specified along side the name of the option | ||||
|  | ||||
| timeslack | ||||
| ^^^^^^^^^ | ||||
|  | ||||
| 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 | ||||
| difference you are prepared to accept. | ||||
|  | ||||
| .. note:: This will indiscriminately effect all time comparisons. | ||||
|     Hence your server my accept a statement that in fact is to old. | ||||
|  | ||||
| xmlsec_binary | ||||
| ^^^^^^^^^^^^^ | ||||
|  | ||||
| Presently xmlsec1 binaries are used for all the signing and encryption stuff. | ||||
| This option defines where the binary is situated. | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "xmlsec_binary": "/usr/local/bin/xmlsec1", | ||||
|  | ||||
| valid_for | ||||
| ^^^^^^^^^ | ||||
|  | ||||
| How many *hours* this configuration is expected to be accurate.:: | ||||
|  | ||||
|     "valid_for": 24 | ||||
|  | ||||
| This of course is only used by *make_metadata.py*. | ||||
| The server will not stop working when this amount of time has elapsed :-). | ||||
|  | ||||
| Specific directives | ||||
| ------------------- | ||||
|  | ||||
| Directives that are specific to a certain type of service. | ||||
|  | ||||
| idp/aa | ||||
| ^^^^^^ | ||||
|  | ||||
| Directives that are specific to an IdP or AA service instance | ||||
|  | ||||
| policy | ||||
| """""" | ||||
|  | ||||
| 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. | ||||
| 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 | ||||
| no default and only SP entity identifiers as keys, then the server will only | ||||
| except connections from the specified SPs. | ||||
| An example might be:: | ||||
|  | ||||
|     "service": { | ||||
|         "idp": { | ||||
|             "policy": { | ||||
|                 "default": { | ||||
|                     "lifetime": {"minutes":15}, | ||||
|                     "attribute_restrictions": None, # means all I have | ||||
|                     "name_form": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" | ||||
|                 }, | ||||
|                 "urn:mace:example.com:saml:roland:sp": { | ||||
|                     "lifetime": {"minutes": 5}, | ||||
|                     "attribute_restrictions":{ | ||||
|                         "givenName": None, | ||||
|                         "surName": None, | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
| *lifetime*  | ||||
|     is the maximum amount of time before the information should be  | ||||
|     regarded as stale. In an Assertion this is represented in the NotOnOrAfter  | ||||
|     attribute.     | ||||
| *attribute_restrictions* | ||||
|     By default there is no restrictions as to which attributes should be | ||||
|     return. Instead all the attributes and values that is gathered by the  | ||||
|     database backends will be returned if nothing else is stated. | ||||
|     In the example above the SP with the entity identifier | ||||
|     "urn:mace:umu.se:saml:roland:sp"  | ||||
|     has an attribute restriction: only the attributes | ||||
|     'givenName' and 'surName' are to be returned. There is no limitations as to | ||||
|     what values on these attributes that can be returned. | ||||
| *name_form* | ||||
|     Which name-form that should be used when sending assertions. | ||||
|  | ||||
| If restrictions on values are deemed necessary those are represented by  | ||||
| regular expressions.:: | ||||
|  | ||||
|     "service": { | ||||
|         "aa": { | ||||
|             "policy": { | ||||
|                 "urn:mace:umu.se:saml:roland:sp": { | ||||
|                     "lifetime": {"minutes": 5}, | ||||
|                     "attribute_restrictions":{ | ||||
|                          "mail": [".*\.umu\.se$"], | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| Here only mail addresses that ends with ".umu.se" will be returned. | ||||
|  | ||||
| sp | ||||
| ^^ | ||||
|  | ||||
| Directives specific to SP instances | ||||
|  | ||||
| authn_requests_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. | ||||
|  | ||||
| This set the AuthnRequestsSigned attribute of the SPSSODescriptor node. | ||||
| of the metadata so the IdP will know this SP preference. | ||||
|  | ||||
| Valid values are "true" or "false". Default value is "false". | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "service": { | ||||
|         "sp": { | ||||
|             "authn_assertions_signed": "true", | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| idp | ||||
| """ | ||||
|  | ||||
| Defines the set of IdPs that this SP is allowed to use. If not all the IdPs in | ||||
| the metadata is allowed, then the value is expected to be a list with entity | ||||
| identifiers for the allowed IdPs. | ||||
| A typical configuration, when the allowed set of IdPs are limited, would look | ||||
| something like this:: | ||||
|  | ||||
|     "service": { | ||||
|         "sp": { | ||||
|             "idp": ["urn:mace:umu.se:saml:roland:idp"], | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 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 | ||||
| """"""""""""""""""" | ||||
|  | ||||
| Attributes that this SP would like to receive from IdPs. | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "service": { | ||||
|         "sp": { | ||||
|             "optional_attributes": ["title"], | ||||
|         } | ||||
|     } | ||||
|      | ||||
| Since the attribute names used here are the user friendly ones an attribute map | ||||
| must exist, so that the server can use the full name when communicating | ||||
| with other servers. | ||||
|  | ||||
| required_attributes | ||||
| """"""""""""""""""" | ||||
|  | ||||
| Attributes that this SP demands to receive from IdPs. | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "service": { | ||||
|         "sp": { | ||||
|             "required_attributes": ["surname", "givenName", "mail"], | ||||
|         } | ||||
|     } | ||||
|  | ||||
| Again as for *optional_attributes* the names given are expected to be  | ||||
| the user friendly names. | ||||
|  | ||||
| want_assertions_signed | ||||
| """""""""""""""""""""" | ||||
|  | ||||
| Indicates if this SP wants the IdP to send the assertions signed. This | ||||
| set the WantAssertionsSigned attribute of the SPSSODescriptor node. | ||||
| of the metadata so the IdP will know this SP preference. | ||||
|  | ||||
| Valid values are "true" or "false". Default value is "true". | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "service": { | ||||
|         "sp": { | ||||
|             "want_assertions_signed": "true", | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| idp/aa/sp | ||||
| ^^^^^^^^^  | ||||
|  | ||||
| If the configuration is covering both two or three different service types | ||||
| (like if one server is actually acting as both an IdP and a SP) then in some | ||||
| cases you might want to have these below different for the different services. | ||||
|  | ||||
| endpoints | ||||
| """"""""" | ||||
|  | ||||
| Where the endpoints for the services provided are. | ||||
| This directive has as value a dictionary with one of the following keys: | ||||
|  | ||||
| * artifact_resolution_service (aa, idp and sp) | ||||
| * assertion_consumer_service (sp) | ||||
| * assertion_id_request_service (aa, idp) | ||||
| * attribute_service (aa) | ||||
| * manage_name_id_service (aa, idp) | ||||
| * name_id_mapping_service (idp) | ||||
| * single_logout_service (aa, idp, sp) | ||||
| * single_sign_on_service (idp) | ||||
|  | ||||
| The values per service is a list of tuples containing endpoint and binding | ||||
| type. | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "service": | ||||
|         "idp": { | ||||
|             "endpoints" : { | ||||
|                 "single_sign_on_service" : [ | ||||
|                         ("http://localhost:8088/sso", BINDING_HTTP_REDIRECT)], | ||||
|                 "single_logout_service": [ | ||||
|                         ("http://localhost:8088/slo", BINDING_HTTP_REDIRECT)] | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
|  | ||||
| logout_requests_signed | ||||
| """""""""""""""""""""" | ||||
|  | ||||
| Indicates if this entity will sign the Logout Requests originated from it. | ||||
|  | ||||
| This can be overriden by application code for a specific call. | ||||
|  | ||||
| Valid values are "true" or "false". Default value is "false" | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "service": { | ||||
|         "sp": { | ||||
|             "logout_requests_signed": "true", | ||||
|         } | ||||
|     } | ||||
|  | ||||
| subject_data | ||||
| """""""""""" | ||||
|  | ||||
| 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. | ||||
| 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 | ||||
| element in the tuple specifise which type of database you want to use | ||||
| and the second element is the address of the database. | ||||
|  | ||||
| Example:: | ||||
|  | ||||
|     "subject_data": "./idp.subject.db", | ||||
|  | ||||
| or if you want to use for instance memcache:: | ||||
|  | ||||
|     "subject_data": ("memcached", "localhost:12121"), | ||||
|  | ||||
| *shelve* and *memcached* are the only database types that are presently | ||||
| supported. | ||||
|  | ||||
|  | ||||
| virtual_organization | ||||
| """""""""""""""""""" | ||||
|  | ||||
| Gives information about common identifiers for virtual_organizations:: | ||||
|  | ||||
|     "virtual_organization" : { | ||||
|         "urn:mace:example.com:it:tek":{ | ||||
|             "nameid_format" : "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", | ||||
|             "common_identifier": "umuselin", | ||||
|         } | ||||
|     }, | ||||
|  | ||||
| Keys in this dictionary are the identifiers for the virtual organizations. | ||||
| The arguments per organization is 'nameid_format' and 'common_identifier'.  | ||||
| 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. | ||||
|  | ||||
| Complete example | ||||
| ---------------- | ||||
|  | ||||
| We start with a simple but fairly complete Service provider configuration:: | ||||
|  | ||||
|     from saml2 import BINDING_HTTP_REDIRECT | ||||
|  | ||||
|     CONFIG = { | ||||
|         "entityid" : "http://example.com/sp/metadata.xml", | ||||
|         "service": { | ||||
|             "sp":{ | ||||
|                 "name" : "Example SP", | ||||
|                 "endpoints":{ | ||||
|                     "assertion_consumer_service": ["http://example.com/sp"], | ||||
|                     "single_logout_service" : [("http://example.com/sp/slo", | ||||
|                                                 BINDING_HTTP_REDIRECT)], | ||||
|                 }, | ||||
|             } | ||||
|         }, | ||||
|         "key_file" : "./mykey.pem", | ||||
|         "cert_file" : "./mycert.pem", | ||||
|         "xmlsec_binary" : "/usr/local/bin/xmlsec1", | ||||
|         "attribute_map_dir": "./attributemaps", | ||||
|         "metadata": { | ||||
|             "local": ["idp.xml"] | ||||
|         } | ||||
|         "organization": { | ||||
|             "display_name":["Example identities"] | ||||
|         } | ||||
|         "contact_person": [{ | ||||
|             "givenname": "Roland", | ||||
|             "surname": "Hedberg", | ||||
|             "phone": "+46 90510", | ||||
|             "mail": "roland@example.com", | ||||
|             "type": "technical", | ||||
|             }] | ||||
|     } | ||||
|  | ||||
| This is the typical setup for a SP. | ||||
| A metadata file to load is *always* needed, but it can of course be | ||||
| containing anything from 1 up to many entity descriptions. | ||||
|  | ||||
| ------ | ||||
|  | ||||
| A slightly more complex configuration:: | ||||
|  | ||||
|     from saml2 import BINDING_HTTP_REDIRECT | ||||
|  | ||||
|     CONFIG = { | ||||
|         "entityid" : "http://sp.example.com/metadata.xml", | ||||
|         "service": { | ||||
|             "sp":{ | ||||
|                 "name" : "Example SP", | ||||
|                 "endpoints":{ | ||||
|                     "assertion_consumer_service": ["http://sp.example.com/"], | ||||
|                     "single_logout_service" : [("http://sp.example.com/slo", | ||||
|                                    BINDING_HTTP_REDIRECT)], | ||||
|                 }, | ||||
|                 "subject_data": ("memcached", "localhost:12121"), | ||||
|                 "virtual_organization" : { | ||||
|                     "urn:mace:example.com:it:tek":{ | ||||
|                         "nameid_format" : "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", | ||||
|                         "common_identifier": "eduPersonPrincipalName", | ||||
|                     } | ||||
|                 }, | ||||
|             } | ||||
|         }, | ||||
|         "key_file" : "./mykey.pem", | ||||
|         "cert_file" : "./mycert.pem", | ||||
|         "xmlsec_binary" : "/usr/local/bin/xmlsec1", | ||||
|         "metadata" : {  | ||||
|             "local": ["example.xml"], | ||||
|             "remote": [{  | ||||
|                 "url":"https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2", | ||||
|                 "cert":"kalmar2.pem"}] | ||||
|         }, | ||||
|         "attribute_maps" : "attributemaps", | ||||
|         "organization": { | ||||
|             "display_name":["Example identities"] | ||||
|         } | ||||
|         "contact_person": [{ | ||||
|             "givenname": "Roland", | ||||
|             "surname": "Hedberg", | ||||
|             "phone": "+46 90510", | ||||
|             "mail": "roland@example.com", | ||||
|             "type": "technical", | ||||
|             }] | ||||
|     } | ||||
|      | ||||
| Uses metadata files, both local and remote, and will talk to whatever  | ||||
| IdP that appears in any of the metadata files.  | ||||
							
								
								
									
										42
									
								
								doc/howto/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								doc/howto/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| .. _howto: | ||||
|  | ||||
| How to use PySAML2 | ||||
| =================== | ||||
|  | ||||
| :Release: |release| | ||||
| :Date: |today| | ||||
|  | ||||
| Before you can use Pysaml2, you'll need to get it installed.  | ||||
| If you have not done it yet, read the :ref:`install` | ||||
|  | ||||
| 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  | ||||
| this code on it's own. | ||||
|  | ||||
| Sure you can send a AuthenticationRequest to an IdentityProvider or a  | ||||
| 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 | ||||
| the AttributeQuery would be over SOAP and you would get the result over the | ||||
| conenction you have to the AttributeAuthority. | ||||
|  | ||||
| But anyway, you may get my point. This is middleware stuff ! | ||||
|  | ||||
| PySAML2 is built to fit into a  | ||||
| `WSGI  <http://www.python.org/dev/peps/pep-0333/>`_ application | ||||
|  | ||||
| But it can be used in a non-WSGI environment too.  | ||||
|  | ||||
| So you will find descriptions of both cases here. | ||||
|  | ||||
| The configuration is the same disregarding whether you are using PySAML2 in a  | ||||
| WSGI or non-WSGI environment. | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 1 | ||||
|  | ||||
|    config | ||||
|    wsgi/index | ||||
|    nonwsgi/index | ||||
|  | ||||
|     | ||||
							
								
								
									
										30
									
								
								doc/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								doc/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| .. _index: | ||||
|  | ||||
| .. pysaml2 documentation master file, created by | ||||
|    sphinx-quickstart on Mon Aug 24 08:13:41 2009. | ||||
|    You can adapt this file completely to your liking, but it should at least | ||||
|    contain the root `toctree` directive. | ||||
|  | ||||
| Welcome to the documentation of pysaml2! | ||||
| ======================================== | ||||
|  | ||||
| :Release: |release| | ||||
| :Date: |today| | ||||
|  | ||||
| Contents: | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 1 | ||||
|  | ||||
|    install | ||||
|    howto/index | ||||
|    saml2 | ||||
|    examples/index | ||||
|     | ||||
| Indices and tables | ||||
| ================== | ||||
|  | ||||
| * :ref:`genindex` | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
|  | ||||
							
								
								
									
										54
									
								
								doc/install.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								doc/install.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| .. _install: | ||||
|  | ||||
| Quick install guide | ||||
| =================== | ||||
|  | ||||
| Before you can use PySAML2, you'll need to get it installed. This guide  | ||||
| will guide you to a simple, minimal installation. | ||||
|  | ||||
| Install PySAML2 | ||||
| --------------- | ||||
|  | ||||
| For all this to work you need to have Python installed.  | ||||
| The development has been done using 2.6. | ||||
| There is no 3.X version yet. | ||||
|  | ||||
| Prerequisites | ||||
| ^^^^^^^^^^^^^ | ||||
|  | ||||
| You have to have ElementTree, which is either part of your Python distribution | ||||
| if it's recent enough, or if the Python is too old you have to install it, | ||||
| for instance by getting it from the Python Package Instance by using  | ||||
| easy_install. | ||||
|  | ||||
| You also need xmlsec which you can download from http://www.aleksey.com/xmlsec/ | ||||
|  | ||||
| If you're on OS X you can get xmlsec installed from MacPorts or Fink. | ||||
|  | ||||
| Depending on how you are going to use PySAML2 you might also need | ||||
|  | ||||
| * Mako | ||||
| * pyASN1 | ||||
| * repoze.who (make sure you get 1.0.16 and not 2.0) | ||||
| * decorator | ||||
| * python-memcache | ||||
| * memcached | ||||
|  | ||||
| Quick build instructions | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| Once you have installed all the necessary prerequisites a simple:: | ||||
|  | ||||
|     python setup.py install | ||||
|  | ||||
| will install the basic code. | ||||
|  | ||||
| After this you ought to be able to run the tests without an hitch. | ||||
| The tests are based on the pypy test environment, so:: | ||||
|  | ||||
|     cd tests | ||||
|     py.test  | ||||
|  | ||||
| is what you should use. If you don't have py.test, get it it's part of pypy!  | ||||
| It's really good ! | ||||
|  | ||||
							
								
								
									
										112
									
								
								doc/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								doc/make.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| @ECHO OFF | ||||
|  | ||||
| REM Command file for Sphinx documentation | ||||
|  | ||||
| set SPHINXBUILD=sphinx-build | ||||
| set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . | ||||
| if NOT "%PAPER%" == "" ( | ||||
| 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | ||||
| ) | ||||
|  | ||||
| if "%1" == "" goto help | ||||
|  | ||||
| if "%1" == "help" ( | ||||
| 	:help | ||||
| 	echo.Please use `make ^<target^>` where ^<target^> is one of | ||||
| 	echo.  html      to make standalone HTML files | ||||
| 	echo.  dirhtml   to make HTML files named index.html in directories | ||||
| 	echo.  pickle    to make pickle files | ||||
| 	echo.  json      to make JSON files | ||||
| 	echo.  htmlhelp  to make HTML files and a HTML help project | ||||
| 	echo.  qthelp    to make HTML files and a qthelp project | ||||
| 	echo.  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter | ||||
| 	echo.  changes   to make an overview over all changed/added/deprecated items | ||||
| 	echo.  linkcheck to check all external links for integrity | ||||
| 	echo.  doctest   to run all doctests embedded in the documentation if enabled | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "clean" ( | ||||
| 	for /d %%i in (_build\*) do rmdir /q /s %%i | ||||
| 	del /q /s _build\* | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "html" ( | ||||
| 	%SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in _build/html. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "dirhtml" ( | ||||
| 	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in _build/dirhtml. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "pickle" ( | ||||
| 	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can process the pickle files. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "json" ( | ||||
| 	%SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can process the JSON files. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "htmlhelp" ( | ||||
| 	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can run HTML Help Workshop with the ^ | ||||
| .hhp project file in _build/htmlhelp. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "qthelp" ( | ||||
| 	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can run "qcollectiongenerator" with the ^ | ||||
| .qhcp project file in _build/qthelp, like this: | ||||
| 	echo.^> qcollectiongenerator _build\qthelp\pysaml2.qhcp | ||||
| 	echo.To view the help file: | ||||
| 	echo.^> assistant -collectionFile _build\qthelp\pysaml2.ghc | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "latex" ( | ||||
| 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex | ||||
| 	echo. | ||||
| 	echo.Build finished; the LaTeX files are in _build/latex. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "changes" ( | ||||
| 	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes | ||||
| 	echo. | ||||
| 	echo.The overview file is in _build/changes. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "linkcheck" ( | ||||
| 	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck | ||||
| 	echo. | ||||
| 	echo.Link check complete; look for any errors in the above output ^ | ||||
| or in _build/linkcheck/output.txt. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "doctest" ( | ||||
| 	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest | ||||
| 	echo. | ||||
| 	echo.Testing of doctests in the sources finished, look at the ^ | ||||
| results in _build/doctest/output.txt. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| :end | ||||
							
								
								
									
										18
									
								
								doc/metadata.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								doc/metadata.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| .. _metadata: | ||||
|  | ||||
| *************************************************** | ||||
| Base classes representing Saml2.0 MetaData elements | ||||
| *************************************************** | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: MetaData | ||||
|    :synopsis: Base classes representing Saml2.0 metadata elements. | ||||
|     | ||||
| Module | ||||
| ========== | ||||
|  | ||||
| .. automodule:: saml2.metadata | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										18
									
								
								doc/saml.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								doc/saml.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| .. _saml: | ||||
|  | ||||
| ****************************************** | ||||
| Base classes representing Saml2.0 elements | ||||
| ****************************************** | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: SAML2 | ||||
|    :synopsis: Base classes representing Saml2.0 elements. | ||||
|     | ||||
| Module | ||||
| ========== | ||||
|  | ||||
| .. automodule:: saml2.saml | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										29
									
								
								doc/saml2.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								doc/saml2.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| .. _base: | ||||
|  | ||||
| **************************************** | ||||
| Base classes representing basic elements | ||||
| **************************************** | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: Base | ||||
|    :synopsis: Base classes. | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 2 | ||||
|  | ||||
|    saml | ||||
|    samlp | ||||
|    metadata | ||||
|    xmldsig | ||||
|    xmlenc | ||||
|    client | ||||
|    server | ||||
|     | ||||
| Module | ||||
| ========== | ||||
|  | ||||
| .. automodule:: saml2 | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										18
									
								
								doc/samlp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								doc/samlp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| .. _samlp: | ||||
|  | ||||
| *************************************************** | ||||
| Base classes representing Saml2.0 protocol elements | ||||
| *************************************************** | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: SAMLP | ||||
|    :synopsis: Base classes representing Saml2.0 protocol elements. | ||||
|     | ||||
| Module | ||||
| ========== | ||||
|  | ||||
| .. automodule:: saml2.samlp | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										19
									
								
								doc/server.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								doc/server.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| .. _server: | ||||
|  | ||||
| *********************************************************************** | ||||
| Classes representing Identity Provider or Attribute Authority instances | ||||
| *********************************************************************** | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: IdPAA | ||||
|    :synopsis: Classes representing Identity Provider or Attribute  | ||||
|                 Authority instances. | ||||
|  | ||||
| Module | ||||
| ====== | ||||
|  | ||||
| .. automodule:: saml2.server | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										18
									
								
								doc/xmldsig.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								doc/xmldsig.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| .. _xmldsig: | ||||
|  | ||||
| ************************************* | ||||
| Classes representing xmldsig elements | ||||
| ************************************* | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: XmlDsig | ||||
|    :synopsis: Classes representing xmldsig elements. | ||||
|  | ||||
| Module | ||||
| ========== | ||||
|  | ||||
| .. automodule:: xmldsig | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										22
									
								
								doc/xmlenc.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								doc/xmlenc.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| .. _xmlenc: | ||||
|  | ||||
| ************************************* | ||||
| Classes representing xmlenc elements | ||||
| ************************************* | ||||
|  | ||||
| #:mod: 'XmlEnc' -- xmlenc | ||||
|  | ||||
| ===================================================== | ||||
|  | ||||
| :Author: Roland Hedberg | ||||
| :Version: |version| | ||||
|  | ||||
| .. module:: XmlEnc | ||||
|    :synopsis: Classes representing xmlenc elements. | ||||
|  | ||||
| Module | ||||
| ========== | ||||
|  | ||||
| .. automodule:: xmlenc | ||||
|    :members:  | ||||
|  | ||||
							
								
								
									
										26
									
								
								example/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								example/README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| 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 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 | ||||
| is idp/passwd and the ini file is idp/idp_user.ini. | ||||
|  | ||||
| The passwords in passwd in clear text: | ||||
|  | ||||
| roland:one | ||||
| ozzie:two | ||||
| derek:three | ||||
| ryan:four | ||||
| ischiro:five | ||||
|  | ||||
| The SP doesn't do anything but show you the information that the IdP sent. | ||||
|  | ||||
| To make it easy, for me :-), both the IdP and the SP uses the same keys. | ||||
|  | ||||
| To run the setup do | ||||
|  | ||||
| ./run.sh | ||||
|  | ||||
| and then use your favourit webbrowser to look at "http://localhost:8087/whoami" | ||||
							
								
								
									
										326
									
								
								example/idp/attributemaps/basic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								example/idp/attributemaps/basic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,326 @@ | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", | ||||
|     "fro": { | ||||
|         'urn:mace:dir:attribute-def:aRecord': 'aRecord', | ||||
|         'urn:mace:dir:attribute-def:aliasedEntryName': 'aliasedEntryName', | ||||
|         'urn:mace:dir:attribute-def:aliasedObjectName': 'aliasedObjectName', | ||||
|         'urn:mace:dir:attribute-def:associatedDomain': 'associatedDomain', | ||||
|         'urn:mace:dir:attribute-def:associatedName': 'associatedName', | ||||
|         'urn:mace:dir:attribute-def:audio': 'audio', | ||||
|         'urn:mace:dir:attribute-def:authorityRevocationList': 'authorityRevocationList', | ||||
|         'urn:mace:dir:attribute-def:buildingName': 'buildingName', | ||||
|         'urn:mace:dir:attribute-def:businessCategory': 'businessCategory', | ||||
|         'urn:mace:dir:attribute-def:c': 'c', | ||||
|         'urn:mace:dir:attribute-def:cACertificate': 'cACertificate', | ||||
|         'urn:mace:dir:attribute-def:cNAMERecord': 'cNAMERecord', | ||||
|         'urn:mace:dir:attribute-def:carLicense': 'carLicense', | ||||
|         'urn:mace:dir:attribute-def:certificateRevocationList': 'certificateRevocationList', | ||||
|         'urn:mace:dir:attribute-def:cn': 'cn', | ||||
|         'urn:mace:dir:attribute-def:co': 'co', | ||||
|         'urn:mace:dir:attribute-def:commonName': 'commonName', | ||||
|         'urn:mace:dir:attribute-def:countryName': 'countryName', | ||||
|         'urn:mace:dir:attribute-def:crossCertificatePair': 'crossCertificatePair', | ||||
|         'urn:mace:dir:attribute-def:dITRedirect': 'dITRedirect', | ||||
|         'urn:mace:dir:attribute-def:dSAQuality': 'dSAQuality', | ||||
|         'urn:mace:dir:attribute-def:dc': 'dc', | ||||
|         'urn:mace:dir:attribute-def:deltaRevocationList': 'deltaRevocationList', | ||||
|         'urn:mace:dir:attribute-def:departmentNumber': 'departmentNumber', | ||||
|         'urn:mace:dir:attribute-def:description': 'description', | ||||
|         'urn:mace:dir:attribute-def:destinationIndicator': 'destinationIndicator', | ||||
|         'urn:mace:dir:attribute-def:displayName': 'displayName', | ||||
|         'urn:mace:dir:attribute-def:distinguishedName': 'distinguishedName', | ||||
|         'urn:mace:dir:attribute-def:dmdName': 'dmdName', | ||||
|         'urn:mace:dir:attribute-def:dnQualifier': 'dnQualifier', | ||||
|         'urn:mace:dir:attribute-def:documentAuthor': 'documentAuthor', | ||||
|         'urn:mace:dir:attribute-def:documentIdentifier': 'documentIdentifier', | ||||
|         'urn:mace:dir:attribute-def:documentLocation': 'documentLocation', | ||||
|         'urn:mace:dir:attribute-def:documentPublisher': 'documentPublisher', | ||||
|         'urn:mace:dir:attribute-def:documentTitle': 'documentTitle', | ||||
|         'urn:mace:dir:attribute-def:documentVersion': 'documentVersion', | ||||
|         'urn:mace:dir:attribute-def:domainComponent': 'domainComponent', | ||||
|         'urn:mace:dir:attribute-def:drink': 'drink', | ||||
|         'urn:mace:dir:attribute-def:eduOrgHomePageURI': 'eduOrgHomePageURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI': 'eduOrgIdentityAuthNPolicyURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgLegalName': 'eduOrgLegalName', | ||||
|         'urn:mace:dir:attribute-def:eduOrgSuperiorURI': 'eduOrgSuperiorURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgWhitePagesURI': 'eduOrgWhitePagesURI', | ||||
|         'urn:mace:dir:attribute-def:eduPersonAffiliation': 'eduPersonAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonEntitlement': 'eduPersonEntitlement', | ||||
|         'urn:mace:dir:attribute-def:eduPersonNickname': 'eduPersonNickname', | ||||
|         'urn:mace:dir:attribute-def:eduPersonOrgDN': 'eduPersonOrgDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonOrgUnitDN': 'eduPersonOrgUnitDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation': 'eduPersonPrimaryAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN': 'eduPersonPrimaryOrgUnitDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrincipalName': 'eduPersonPrincipalName', | ||||
|         'urn:mace:dir:attribute-def:eduPersonScopedAffiliation': 'eduPersonScopedAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonTargetedID': 'eduPersonTargetedID', | ||||
|         'urn:mace:dir:attribute-def:email': 'email', | ||||
|         'urn:mace:dir:attribute-def:emailAddress': 'emailAddress', | ||||
|         'urn:mace:dir:attribute-def:employeeNumber': 'employeeNumber', | ||||
|         'urn:mace:dir:attribute-def:employeeType': 'employeeType', | ||||
|         'urn:mace:dir:attribute-def:enhancedSearchGuide': 'enhancedSearchGuide', | ||||
|         'urn:mace:dir:attribute-def:facsimileTelephoneNumber': 'facsimileTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:favouriteDrink': 'favouriteDrink', | ||||
|         'urn:mace:dir:attribute-def:fax': 'fax', | ||||
|         'urn:mace:dir:attribute-def:federationFeideSchemaVersion': 'federationFeideSchemaVersion', | ||||
|         'urn:mace:dir:attribute-def:friendlyCountryName': 'friendlyCountryName', | ||||
|         'urn:mace:dir:attribute-def:generationQualifier': 'generationQualifier', | ||||
|         'urn:mace:dir:attribute-def:givenName': 'givenName', | ||||
|         'urn:mace:dir:attribute-def:gn': 'gn', | ||||
|         'urn:mace:dir:attribute-def:homePhone': 'homePhone', | ||||
|         'urn:mace:dir:attribute-def:homePostalAddress': 'homePostalAddress', | ||||
|         'urn:mace:dir:attribute-def:homeTelephoneNumber': 'homeTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:host': 'host', | ||||
|         'urn:mace:dir:attribute-def:houseIdentifier': 'houseIdentifier', | ||||
|         'urn:mace:dir:attribute-def:info': 'info', | ||||
|         'urn:mace:dir:attribute-def:initials': 'initials', | ||||
|         'urn:mace:dir:attribute-def:internationaliSDNNumber': 'internationaliSDNNumber', | ||||
|         'urn:mace:dir:attribute-def:janetMailbox': 'janetMailbox', | ||||
|         'urn:mace:dir:attribute-def:jpegPhoto': 'jpegPhoto', | ||||
|         'urn:mace:dir:attribute-def:knowledgeInformation': 'knowledgeInformation', | ||||
|         'urn:mace:dir:attribute-def:l': 'l', | ||||
|         'urn:mace:dir:attribute-def:labeledURI': 'labeledURI', | ||||
|         'urn:mace:dir:attribute-def:localityName': 'localityName', | ||||
|         'urn:mace:dir:attribute-def:mDRecord': 'mDRecord', | ||||
|         'urn:mace:dir:attribute-def:mXRecord': 'mXRecord', | ||||
|         'urn:mace:dir:attribute-def:mail': 'mail', | ||||
|         'urn:mace:dir:attribute-def:mailPreferenceOption': 'mailPreferenceOption', | ||||
|         'urn:mace:dir:attribute-def:manager': 'manager', | ||||
|         'urn:mace:dir:attribute-def:member': 'member', | ||||
|         'urn:mace:dir:attribute-def:mobile': 'mobile', | ||||
|         'urn:mace:dir:attribute-def:mobileTelephoneNumber': 'mobileTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:nSRecord': 'nSRecord', | ||||
|         'urn:mace:dir:attribute-def:name': 'name', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgAcronym': 'norEduOrgAcronym', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgNIN': 'norEduOrgNIN', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgSchemaVersion': 'norEduOrgSchemaVersion', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier': 'norEduOrgUniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUniqueNumber': 'norEduOrgUniqueNumber', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier': 'norEduOrgUnitUniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber': 'norEduOrgUnitUniqueNumber', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonBirthDate': 'norEduPersonBirthDate', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonLIN': 'norEduPersonLIN', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonNIN': 'norEduPersonNIN', | ||||
|         'urn:mace:dir:attribute-def:o': 'o', | ||||
|         'urn:mace:dir:attribute-def:objectClass': 'objectClass', | ||||
|         'urn:mace:dir:attribute-def:organizationName': 'organizationName', | ||||
|         'urn:mace:dir:attribute-def:organizationalStatus': 'organizationalStatus', | ||||
|         'urn:mace:dir:attribute-def:organizationalUnitName': 'organizationalUnitName', | ||||
|         'urn:mace:dir:attribute-def:otherMailbox': 'otherMailbox', | ||||
|         'urn:mace:dir:attribute-def:ou': 'ou', | ||||
|         'urn:mace:dir:attribute-def:owner': 'owner', | ||||
|         'urn:mace:dir:attribute-def:pager': 'pager', | ||||
|         'urn:mace:dir:attribute-def:pagerTelephoneNumber': 'pagerTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:personalSignature': 'personalSignature', | ||||
|         'urn:mace:dir:attribute-def:personalTitle': 'personalTitle', | ||||
|         'urn:mace:dir:attribute-def:photo': 'photo', | ||||
|         'urn:mace:dir:attribute-def:physicalDeliveryOfficeName': 'physicalDeliveryOfficeName', | ||||
|         'urn:mace:dir:attribute-def:pkcs9email': 'pkcs9email', | ||||
|         'urn:mace:dir:attribute-def:postOfficeBox': 'postOfficeBox', | ||||
|         'urn:mace:dir:attribute-def:postalAddress': 'postalAddress', | ||||
|         'urn:mace:dir:attribute-def:postalCode': 'postalCode', | ||||
|         'urn:mace:dir:attribute-def:preferredDeliveryMethod': 'preferredDeliveryMethod', | ||||
|         'urn:mace:dir:attribute-def:preferredLanguage': 'preferredLanguage', | ||||
|         'urn:mace:dir:attribute-def:presentationAddress': 'presentationAddress', | ||||
|         'urn:mace:dir:attribute-def:protocolInformation': 'protocolInformation', | ||||
|         'urn:mace:dir:attribute-def:pseudonym': 'pseudonym', | ||||
|         'urn:mace:dir:attribute-def:registeredAddress': 'registeredAddress', | ||||
|         'urn:mace:dir:attribute-def:rfc822Mailbox': 'rfc822Mailbox', | ||||
|         'urn:mace:dir:attribute-def:roleOccupant': 'roleOccupant', | ||||
|         'urn:mace:dir:attribute-def:roomNumber': 'roomNumber', | ||||
|         'urn:mace:dir:attribute-def:sOARecord': 'sOARecord', | ||||
|         'urn:mace:dir:attribute-def:searchGuide': 'searchGuide', | ||||
|         'urn:mace:dir:attribute-def:secretary': 'secretary', | ||||
|         'urn:mace:dir:attribute-def:seeAlso': 'seeAlso', | ||||
|         'urn:mace:dir:attribute-def:serialNumber': 'serialNumber', | ||||
|         'urn:mace:dir:attribute-def:singleLevelQuality': 'singleLevelQuality', | ||||
|         'urn:mace:dir:attribute-def:sn': 'sn', | ||||
|         'urn:mace:dir:attribute-def:st': 'st', | ||||
|         'urn:mace:dir:attribute-def:stateOrProvinceName': 'stateOrProvinceName', | ||||
|         'urn:mace:dir:attribute-def:street': 'street', | ||||
|         'urn:mace:dir:attribute-def:streetAddress': 'streetAddress', | ||||
|         'urn:mace:dir:attribute-def:subtreeMaximumQuality': 'subtreeMaximumQuality', | ||||
|         'urn:mace:dir:attribute-def:subtreeMinimumQuality': 'subtreeMinimumQuality', | ||||
|         'urn:mace:dir:attribute-def:supportedAlgorithms': 'supportedAlgorithms', | ||||
|         'urn:mace:dir:attribute-def:supportedApplicationContext': 'supportedApplicationContext', | ||||
|         'urn:mace:dir:attribute-def:surname': 'surname', | ||||
|         'urn:mace:dir:attribute-def:telephoneNumber': 'telephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:teletexTerminalIdentifier': 'teletexTerminalIdentifier', | ||||
|         'urn:mace:dir:attribute-def:telexNumber': 'telexNumber', | ||||
|         'urn:mace:dir:attribute-def:textEncodedORAddress': 'textEncodedORAddress', | ||||
|         'urn:mace:dir:attribute-def:title': 'title', | ||||
|         'urn:mace:dir:attribute-def:uid': 'uid', | ||||
|         'urn:mace:dir:attribute-def:uniqueIdentifier': 'uniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:uniqueMember': 'uniqueMember', | ||||
|         'urn:mace:dir:attribute-def:userCertificate': 'userCertificate', | ||||
|         'urn:mace:dir:attribute-def:userClass': 'userClass', | ||||
|         'urn:mace:dir:attribute-def:userPKCS12': 'userPKCS12', | ||||
|         'urn:mace:dir:attribute-def:userPassword': 'userPassword', | ||||
|         'urn:mace:dir:attribute-def:userSMIMECertificate': 'userSMIMECertificate', | ||||
|         'urn:mace:dir:attribute-def:userid': 'userid', | ||||
|         'urn:mace:dir:attribute-def:x121Address': 'x121Address', | ||||
|         'urn:mace:dir:attribute-def:x500UniqueIdentifier': 'x500UniqueIdentifier', | ||||
|         }, | ||||
|     "to": { | ||||
|         'aRecord': 'urn:mace:dir:attribute-def:aRecord', | ||||
|         'aliasedEntryName': 'urn:mace:dir:attribute-def:aliasedEntryName', | ||||
|         'aliasedObjectName': 'urn:mace:dir:attribute-def:aliasedObjectName', | ||||
|         'associatedDomain': 'urn:mace:dir:attribute-def:associatedDomain', | ||||
|         'associatedName': 'urn:mace:dir:attribute-def:associatedName', | ||||
|         'audio': 'urn:mace:dir:attribute-def:audio', | ||||
|         'authorityRevocationList': 'urn:mace:dir:attribute-def:authorityRevocationList', | ||||
|         'buildingName': 'urn:mace:dir:attribute-def:buildingName', | ||||
|         'businessCategory': 'urn:mace:dir:attribute-def:businessCategory', | ||||
|         'c': 'urn:mace:dir:attribute-def:c', | ||||
|         'cACertificate': 'urn:mace:dir:attribute-def:cACertificate', | ||||
|         'cNAMERecord': 'urn:mace:dir:attribute-def:cNAMERecord', | ||||
|         'carLicense': 'urn:mace:dir:attribute-def:carLicense', | ||||
|         'certificateRevocationList': 'urn:mace:dir:attribute-def:certificateRevocationList', | ||||
|         'cn': 'urn:mace:dir:attribute-def:cn', | ||||
|         'co': 'urn:mace:dir:attribute-def:co', | ||||
|         'commonName': 'urn:mace:dir:attribute-def:commonName', | ||||
|         'countryName': 'urn:mace:dir:attribute-def:countryName', | ||||
|         'crossCertificatePair': 'urn:mace:dir:attribute-def:crossCertificatePair', | ||||
|         'dITRedirect': 'urn:mace:dir:attribute-def:dITRedirect', | ||||
|         'dSAQuality': 'urn:mace:dir:attribute-def:dSAQuality', | ||||
|         'dc': 'urn:mace:dir:attribute-def:dc', | ||||
|         'deltaRevocationList': 'urn:mace:dir:attribute-def:deltaRevocationList', | ||||
|         'departmentNumber': 'urn:mace:dir:attribute-def:departmentNumber', | ||||
|         'description': 'urn:mace:dir:attribute-def:description', | ||||
|         'destinationIndicator': 'urn:mace:dir:attribute-def:destinationIndicator', | ||||
|         'displayName': 'urn:mace:dir:attribute-def:displayName', | ||||
|         'distinguishedName': 'urn:mace:dir:attribute-def:distinguishedName', | ||||
|         'dmdName': 'urn:mace:dir:attribute-def:dmdName', | ||||
|         'dnQualifier': 'urn:mace:dir:attribute-def:dnQualifier', | ||||
|         'documentAuthor': 'urn:mace:dir:attribute-def:documentAuthor', | ||||
|         'documentIdentifier': 'urn:mace:dir:attribute-def:documentIdentifier', | ||||
|         'documentLocation': 'urn:mace:dir:attribute-def:documentLocation', | ||||
|         'documentPublisher': 'urn:mace:dir:attribute-def:documentPublisher', | ||||
|         'documentTitle': 'urn:mace:dir:attribute-def:documentTitle', | ||||
|         'documentVersion': 'urn:mace:dir:attribute-def:documentVersion', | ||||
|         'domainComponent': 'urn:mace:dir:attribute-def:domainComponent', | ||||
|         'drink': 'urn:mace:dir:attribute-def:drink', | ||||
|         'eduOrgHomePageURI': 'urn:mace:dir:attribute-def:eduOrgHomePageURI', | ||||
|         'eduOrgIdentityAuthNPolicyURI': 'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI', | ||||
|         'eduOrgLegalName': 'urn:mace:dir:attribute-def:eduOrgLegalName', | ||||
|         'eduOrgSuperiorURI': 'urn:mace:dir:attribute-def:eduOrgSuperiorURI', | ||||
|         'eduOrgWhitePagesURI': 'urn:mace:dir:attribute-def:eduOrgWhitePagesURI', | ||||
|         'eduPersonAffiliation': 'urn:mace:dir:attribute-def:eduPersonAffiliation', | ||||
|         'eduPersonEntitlement': 'urn:mace:dir:attribute-def:eduPersonEntitlement', | ||||
|         'eduPersonNickname': 'urn:mace:dir:attribute-def:eduPersonNickname', | ||||
|         'eduPersonOrgDN': 'urn:mace:dir:attribute-def:eduPersonOrgDN', | ||||
|         'eduPersonOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonOrgUnitDN', | ||||
|         'eduPersonPrimaryAffiliation': 'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation', | ||||
|         'eduPersonPrimaryOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN', | ||||
|         'eduPersonPrincipalName': 'urn:mace:dir:attribute-def:eduPersonPrincipalName', | ||||
|         'eduPersonScopedAffiliation': 'urn:mace:dir:attribute-def:eduPersonScopedAffiliation', | ||||
|         'eduPersonTargetedID': 'urn:mace:dir:attribute-def:eduPersonTargetedID', | ||||
|         'email': 'urn:mace:dir:attribute-def:email', | ||||
|         'emailAddress': 'urn:mace:dir:attribute-def:emailAddress', | ||||
|         'employeeNumber': 'urn:mace:dir:attribute-def:employeeNumber', | ||||
|         'employeeType': 'urn:mace:dir:attribute-def:employeeType', | ||||
|         'enhancedSearchGuide': 'urn:mace:dir:attribute-def:enhancedSearchGuide', | ||||
|         'facsimileTelephoneNumber': 'urn:mace:dir:attribute-def:facsimileTelephoneNumber', | ||||
|         'favouriteDrink': 'urn:mace:dir:attribute-def:favouriteDrink', | ||||
|         'fax': 'urn:mace:dir:attribute-def:fax', | ||||
|         'federationFeideSchemaVersion': 'urn:mace:dir:attribute-def:federationFeideSchemaVersion', | ||||
|         'friendlyCountryName': 'urn:mace:dir:attribute-def:friendlyCountryName', | ||||
|         'generationQualifier': 'urn:mace:dir:attribute-def:generationQualifier', | ||||
|         'givenName': 'urn:mace:dir:attribute-def:givenName', | ||||
|         'gn': 'urn:mace:dir:attribute-def:gn', | ||||
|         'homePhone': 'urn:mace:dir:attribute-def:homePhone', | ||||
|         'homePostalAddress': 'urn:mace:dir:attribute-def:homePostalAddress', | ||||
|         'homeTelephoneNumber': 'urn:mace:dir:attribute-def:homeTelephoneNumber', | ||||
|         'host': 'urn:mace:dir:attribute-def:host', | ||||
|         'houseIdentifier': 'urn:mace:dir:attribute-def:houseIdentifier', | ||||
|         'info': 'urn:mace:dir:attribute-def:info', | ||||
|         'initials': 'urn:mace:dir:attribute-def:initials', | ||||
|         'internationaliSDNNumber': 'urn:mace:dir:attribute-def:internationaliSDNNumber', | ||||
|         'janetMailbox': 'urn:mace:dir:attribute-def:janetMailbox', | ||||
|         'jpegPhoto': 'urn:mace:dir:attribute-def:jpegPhoto', | ||||
|         'knowledgeInformation': 'urn:mace:dir:attribute-def:knowledgeInformation', | ||||
|         'l': 'urn:mace:dir:attribute-def:l', | ||||
|         'labeledURI': 'urn:mace:dir:attribute-def:labeledURI', | ||||
|         'localityName': 'urn:mace:dir:attribute-def:localityName', | ||||
|         'mDRecord': 'urn:mace:dir:attribute-def:mDRecord', | ||||
|         'mXRecord': 'urn:mace:dir:attribute-def:mXRecord', | ||||
|         'mail': 'urn:mace:dir:attribute-def:mail', | ||||
|         'mailPreferenceOption': 'urn:mace:dir:attribute-def:mailPreferenceOption', | ||||
|         'manager': 'urn:mace:dir:attribute-def:manager', | ||||
|         'member': 'urn:mace:dir:attribute-def:member', | ||||
|         'mobile': 'urn:mace:dir:attribute-def:mobile', | ||||
|         'mobileTelephoneNumber': 'urn:mace:dir:attribute-def:mobileTelephoneNumber', | ||||
|         'nSRecord': 'urn:mace:dir:attribute-def:nSRecord', | ||||
|         'name': 'urn:mace:dir:attribute-def:name', | ||||
|         'norEduOrgAcronym': 'urn:mace:dir:attribute-def:norEduOrgAcronym', | ||||
|         'norEduOrgNIN': 'urn:mace:dir:attribute-def:norEduOrgNIN', | ||||
|         'norEduOrgSchemaVersion': 'urn:mace:dir:attribute-def:norEduOrgSchemaVersion', | ||||
|         'norEduOrgUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier', | ||||
|         'norEduOrgUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUniqueNumber', | ||||
|         'norEduOrgUnitUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier', | ||||
|         'norEduOrgUnitUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber', | ||||
|         'norEduPersonBirthDate': 'urn:mace:dir:attribute-def:norEduPersonBirthDate', | ||||
|         'norEduPersonLIN': 'urn:mace:dir:attribute-def:norEduPersonLIN', | ||||
|         'norEduPersonNIN': 'urn:mace:dir:attribute-def:norEduPersonNIN', | ||||
|         'o': 'urn:mace:dir:attribute-def:o', | ||||
|         'objectClass': 'urn:mace:dir:attribute-def:objectClass', | ||||
|         'organizationName': 'urn:mace:dir:attribute-def:organizationName', | ||||
|         'organizationalStatus': 'urn:mace:dir:attribute-def:organizationalStatus', | ||||
|         'organizationalUnitName': 'urn:mace:dir:attribute-def:organizationalUnitName', | ||||
|         'otherMailbox': 'urn:mace:dir:attribute-def:otherMailbox', | ||||
|         'ou': 'urn:mace:dir:attribute-def:ou', | ||||
|         'owner': 'urn:mace:dir:attribute-def:owner', | ||||
|         'pager': 'urn:mace:dir:attribute-def:pager', | ||||
|         'pagerTelephoneNumber': 'urn:mace:dir:attribute-def:pagerTelephoneNumber', | ||||
|         'personalSignature': 'urn:mace:dir:attribute-def:personalSignature', | ||||
|         'personalTitle': 'urn:mace:dir:attribute-def:personalTitle', | ||||
|         'photo': 'urn:mace:dir:attribute-def:photo', | ||||
|         'physicalDeliveryOfficeName': 'urn:mace:dir:attribute-def:physicalDeliveryOfficeName', | ||||
|         'pkcs9email': 'urn:mace:dir:attribute-def:pkcs9email', | ||||
|         'postOfficeBox': 'urn:mace:dir:attribute-def:postOfficeBox', | ||||
|         'postalAddress': 'urn:mace:dir:attribute-def:postalAddress', | ||||
|         'postalCode': 'urn:mace:dir:attribute-def:postalCode', | ||||
|         'preferredDeliveryMethod': 'urn:mace:dir:attribute-def:preferredDeliveryMethod', | ||||
|         'preferredLanguage': 'urn:mace:dir:attribute-def:preferredLanguage', | ||||
|         'presentationAddress': 'urn:mace:dir:attribute-def:presentationAddress', | ||||
|         'protocolInformation': 'urn:mace:dir:attribute-def:protocolInformation', | ||||
|         'pseudonym': 'urn:mace:dir:attribute-def:pseudonym', | ||||
|         'registeredAddress': 'urn:mace:dir:attribute-def:registeredAddress', | ||||
|         'rfc822Mailbox': 'urn:mace:dir:attribute-def:rfc822Mailbox', | ||||
|         'roleOccupant': 'urn:mace:dir:attribute-def:roleOccupant', | ||||
|         'roomNumber': 'urn:mace:dir:attribute-def:roomNumber', | ||||
|         'sOARecord': 'urn:mace:dir:attribute-def:sOARecord', | ||||
|         'searchGuide': 'urn:mace:dir:attribute-def:searchGuide', | ||||
|         'secretary': 'urn:mace:dir:attribute-def:secretary', | ||||
|         'seeAlso': 'urn:mace:dir:attribute-def:seeAlso', | ||||
|         'serialNumber': 'urn:mace:dir:attribute-def:serialNumber', | ||||
|         'singleLevelQuality': 'urn:mace:dir:attribute-def:singleLevelQuality', | ||||
|         'sn': 'urn:mace:dir:attribute-def:sn', | ||||
|         'st': 'urn:mace:dir:attribute-def:st', | ||||
|         'stateOrProvinceName': 'urn:mace:dir:attribute-def:stateOrProvinceName', | ||||
|         'street': 'urn:mace:dir:attribute-def:street', | ||||
|         'streetAddress': 'urn:mace:dir:attribute-def:streetAddress', | ||||
|         'subtreeMaximumQuality': 'urn:mace:dir:attribute-def:subtreeMaximumQuality', | ||||
|         'subtreeMinimumQuality': 'urn:mace:dir:attribute-def:subtreeMinimumQuality', | ||||
|         'supportedAlgorithms': 'urn:mace:dir:attribute-def:supportedAlgorithms', | ||||
|         'supportedApplicationContext': 'urn:mace:dir:attribute-def:supportedApplicationContext', | ||||
|         'surname': 'urn:mace:dir:attribute-def:surname', | ||||
|         'telephoneNumber': 'urn:mace:dir:attribute-def:telephoneNumber', | ||||
|         'teletexTerminalIdentifier': 'urn:mace:dir:attribute-def:teletexTerminalIdentifier', | ||||
|         'telexNumber': 'urn:mace:dir:attribute-def:telexNumber', | ||||
|         'textEncodedORAddress': 'urn:mace:dir:attribute-def:textEncodedORAddress', | ||||
|         'title': 'urn:mace:dir:attribute-def:title', | ||||
|         'uid': 'urn:mace:dir:attribute-def:uid', | ||||
|         'uniqueIdentifier': 'urn:mace:dir:attribute-def:uniqueIdentifier', | ||||
|         'uniqueMember': 'urn:mace:dir:attribute-def:uniqueMember', | ||||
|         'userCertificate': 'urn:mace:dir:attribute-def:userCertificate', | ||||
|         'userClass': 'urn:mace:dir:attribute-def:userClass', | ||||
|         'userPKCS12': 'urn:mace:dir:attribute-def:userPKCS12', | ||||
|         'userPassword': 'urn:mace:dir:attribute-def:userPassword', | ||||
|         'userSMIMECertificate': 'urn:mace:dir:attribute-def:userSMIMECertificate', | ||||
|         'userid': 'urn:mace:dir:attribute-def:userid', | ||||
|         'x121Address': 'urn:mace:dir:attribute-def:x121Address', | ||||
|         'x500UniqueIdentifier': 'urn:mace:dir:attribute-def:x500UniqueIdentifier', | ||||
|     } | ||||
| } | ||||
							
								
								
									
										199
									
								
								example/idp/attributemaps/saml_uri.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								example/idp/attributemaps/saml_uri.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| __author__ = 'rolandh' | ||||
|  | ||||
| EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." | ||||
| X500ATTR_OID = "urn:oid:2.5.4." | ||||
| NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." | ||||
| NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.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." | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", | ||||
|     "fro": { | ||||
|         EDUPERSON_OID+'2': 'eduPersonNickname', | ||||
|         EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', | ||||
|         EDUPERSON_OID+'11': 'eduPersonAssurance', | ||||
|         EDUPERSON_OID+'10': 'eduPersonTargetedID', | ||||
|         EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', | ||||
|         NOREDUPERSON_OID+'6': 'norEduOrgAcronym', | ||||
|         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+'9': 'federationFeideSchemaVersion', | ||||
|         X500ATTR_OID+'53': 'deltaRevocationList', | ||||
|         X500ATTR_OID+'52': 'supportedAlgorithms', | ||||
|         X500ATTR_OID+'51': 'houseIdentifier', | ||||
|         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', | ||||
|         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', | ||||
|         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', | ||||
|     }, | ||||
|     "to": { | ||||
|         'roleOccupant': X500ATTR_OID+'33', | ||||
|         'gn': X500ATTR_OID+'42', | ||||
|         'norEduPersonNIN': NOREDUPERSON_OID+'5', | ||||
|         'title': X500ATTR_OID+'12', | ||||
|         'facsimileTelephoneNumber': X500ATTR_OID+'23', | ||||
|         'mail': UCL_DIR_PILOT+'3', | ||||
|         'postOfficeBox': X500ATTR_OID+'18', | ||||
|         'fax': X500ATTR_OID+'23', | ||||
|         'telephoneNumber': X500ATTR_OID+'20', | ||||
|         'norEduPersonBirthDate': NOREDUPERSON_OID+'3', | ||||
|         'rfc822Mailbox': UCL_DIR_PILOT+'3', | ||||
|         'dc': UCL_DIR_PILOT+'25', | ||||
|         'countryName': X500ATTR_OID+'6', | ||||
|         'emailAddress': PKCS_9+'1', | ||||
|         'employeeNumber': NETSCAPE_LDAP+'3', | ||||
|         'organizationName': X500ATTR_OID+'10', | ||||
|         'eduPersonAssurance': EDUPERSON_OID+'11', | ||||
|         'norEduOrgAcronym': NOREDUPERSON_OID+'6', | ||||
|         'registeredAddress': X500ATTR_OID+'26', | ||||
|         'physicalDeliveryOfficeName': X500ATTR_OID+'19', | ||||
|         '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', | ||||
|         'eduPersonPrincipalName': EDUPERSON_OID+'6', | ||||
|         'edupersonprincipalname': 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', | ||||
|         'displayName': NETSCAPE_LDAP+'241', | ||||
|         'businessCategory': X500ATTR_OID+'15', | ||||
|         'serialNumber': X500ATTR_OID+'5', | ||||
|         'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7', | ||||
|         'st': X500ATTR_OID+'8', | ||||
|         'carLicense': NETSCAPE_LDAP+'1', | ||||
|         'presentationAddress': X500ATTR_OID+'29', | ||||
|         'sn': X500ATTR_OID+'4', | ||||
|         'domainComponent': UCL_DIR_PILOT+'25', | ||||
|         'labeledURI': UMICH+'57', | ||||
|         'uid': UCL_DIR_PILOT+'1' | ||||
|     } | ||||
| }   | ||||
							
								
								
									
										190
									
								
								example/idp/attributemaps/shibboleth_uri.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								example/idp/attributemaps/shibboleth_uri.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." | ||||
| X500ATTR = "urn:oid:2.5.4." | ||||
| NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." | ||||
| NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.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." | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:mace:shibboleth:1.0:attributeNamespace:uri", | ||||
|     "fro": { | ||||
|         EDUPERSON_OID+'2': 'eduPersonNickname', | ||||
|         EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', | ||||
|         EDUPERSON_OID+'11': 'eduPersonAssurance', | ||||
|         EDUPERSON_OID+'10': 'eduPersonTargetedID', | ||||
|         EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', | ||||
|         NOREDUPERSON_OID+'6': 'norEduOrgAcronym', | ||||
|         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+'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', | ||||
|         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+'25': 'dc', | ||||
|         X500ATTR+'40': 'crossCertificatePair', | ||||
|         X500ATTR+'42': 'givenName', | ||||
|         X500ATTR+'43': 'initials', | ||||
|         X500ATTR+'44': 'generationQualifier', | ||||
|         X500ATTR+'45': 'x500UniqueIdentifier', | ||||
|         X500ATTR+'46': 'dnQualifier', | ||||
|         X500ATTR+'47': 'enhancedSearchGuide', | ||||
|         X500ATTR+'48': 'protocolInformation', | ||||
|         X500ATTR+'54': 'dmdName', | ||||
|         NETSCAPE_LDAP+'4': 'employeeType', | ||||
|         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":{ | ||||
|         'roleOccupant': X500ATTR+'33', | ||||
|         'gn': X500ATTR+'42', | ||||
|         'norEduPersonNIN': NOREDUPERSON_OID+'5', | ||||
|         'title': X500ATTR+'12', | ||||
|         'facsimileTelephoneNumber': X500ATTR+'23', | ||||
|         'mail': UCL_DIR_PILOT+'3', | ||||
|         'postOfficeBox': X500ATTR+'18', | ||||
|         'fax': X500ATTR+'23', | ||||
|         'telephoneNumber': X500ATTR+'20', | ||||
|         'norEduPersonBirthDate': NOREDUPERSON_OID+'3', | ||||
|         'rfc822Mailbox': UCL_DIR_PILOT+'3', | ||||
|         'dc': UCL_DIR_PILOT+'25', | ||||
|         'countryName': X500ATTR+'6', | ||||
|         'emailAddress': PKCS_9+'1', | ||||
|         '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', | ||||
|         'userSMIMECertificate': NETSCAPE_LDAP+'40', | ||||
|         'member': X500ATTR+'31', | ||||
|         'streetAddress': X500ATTR+'9', | ||||
|         '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', | ||||
|         'st': X500ATTR+'8', | ||||
|         'carLicense': NETSCAPE_LDAP+'1', | ||||
|         'presentationAddress': X500ATTR+'29', | ||||
|         'sn': X500ATTR+'4', | ||||
|         'domainComponent': UCL_DIR_PILOT+'25', | ||||
|     } | ||||
| } | ||||
							
								
								
									
										270
									
								
								example/idp/idp.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										270
									
								
								example/idp/idp.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,270 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import re | ||||
| import base64 | ||||
| from cgi import parse_qs | ||||
| from saml2 import server | ||||
| from saml2 import BINDING_HTTP_REDIRECT, BINDING_HTTP_POST | ||||
| from saml2 import time_util | ||||
| from Cookie import SimpleCookie | ||||
|  | ||||
| def _expiration(timeout, format=None): | ||||
|     if timeout == "now": | ||||
|         return time_util.instant(format) | ||||
|     else: | ||||
|         # validity time should match lifetime of assertions | ||||
|         return time_util.in_a_while(minutes=timeout, format=format) | ||||
|  | ||||
| # ----------------------------------------------------------------------------- | ||||
| def dict_to_table(ava, lev=0, width=1): | ||||
|     txt = ['<table border=%s bordercolor="black">\n' % width] | ||||
|     for prop, valarr in ava.items(): | ||||
|         txt.append("<tr>\n") | ||||
|         if isinstance(valarr, basestring): | ||||
|             txt.append("<th>%s</th>\n" % str(prop)) | ||||
|             try: | ||||
|                 txt.append("<td>%s</td>\n" % valarr.encode("utf8")) | ||||
|             except AttributeError: | ||||
|                 txt.append("<td>%s</td>\n" % valarr) | ||||
|         elif isinstance(valarr, list): | ||||
|             index = 0 | ||||
|             num = len(valarr)        | ||||
|             for val in valarr: | ||||
|                 if not index: | ||||
|                     txt.append("<th rowspan=%d>%s</td>\n" % (len(valarr), prop)) | ||||
|                 else: | ||||
|                     txt.append("<tr>\n") | ||||
|                 if isinstance(val, dict): | ||||
|                     txt.append("<td>\n") | ||||
|                     txt.extend(dict_to_table(val, lev+1, width-1)) | ||||
|                     txt.append("</td>\n") | ||||
|                 else: | ||||
|                     try: | ||||
|                         txt.append("<td>%s</td>\n" % val.encode("utf8")) | ||||
|                     except AttributeError: | ||||
|                         txt.append("<td>%s</td>\n" % val) | ||||
|                 if num > 1: | ||||
|                     txt.append("</tr>\n") | ||||
|                 num -= 1 | ||||
|                 index += 1 | ||||
|         elif isinstance(valarr, dict): | ||||
|             txt.append("<th>%s</th>\n" % prop) | ||||
|             txt.append("<td>\n") | ||||
|             txt.extend(dict_to_table(valarr, lev+1, width-1)) | ||||
|             txt.append("</td>\n") | ||||
|         txt.append("</tr>\n") | ||||
|     txt.append('</table>\n') | ||||
|     return txt | ||||
|      | ||||
| REPOZE_ID_EQUIVALENT = "uid" | ||||
| FORM_SPEC = """<form name="myform" method="post" action="%s"> | ||||
|    <input type="hidden" name="SAMLResponse" value="%s" /> | ||||
|    <input type="hidden" name="RelayState" value="%s" /> | ||||
| </form>""" | ||||
|  | ||||
| def sso(environ, start_response, user, logger): | ||||
|     """ Supposted to return a POST """ | ||||
|     #edict = dict_to_table(environ) | ||||
|     #if logger: logger.info("Environ keys: %s" % environ.keys()) | ||||
|     logger.info("--- In SSO ---") | ||||
|     query = None | ||||
|     if "QUERY_STRING" in environ: | ||||
|         if logger: | ||||
|             logger.info("Query string: %s" % environ["QUERY_STRING"]) | ||||
|         query = parse_qs(environ["QUERY_STRING"]) | ||||
|     elif "s2repoze.qinfo" in environ: | ||||
|         query = environ["s2repoze.qinfo"] | ||||
|  | ||||
|     if not query: | ||||
|         start_response('401 Unauthorized', [('Content-Type', 'text/plain')]) | ||||
|         return ['Unknown user'] | ||||
|          | ||||
|     # base 64 encoded request | ||||
|     req_info = IDP.parse_authn_request(query["SAMLRequest"][0]) | ||||
|     logger.info("parsed OK") | ||||
|     logger.info("%s" % req_info) | ||||
|  | ||||
|     identity = dict(environ["repoze.who.identity"]["user"]) | ||||
|     logger.info("Identity: %s" % (identity,)) | ||||
|     userid = environ["repoze.who.identity"]['repoze.who.userid'] | ||||
|     if REPOZE_ID_EQUIVALENT: | ||||
|         identity[REPOZE_ID_EQUIVALENT] = userid | ||||
|     try: | ||||
|         authn_resp = IDP.authn_response(identity,  | ||||
|                                         req_info["id"],  | ||||
|                                         req_info["consumer_url"],  | ||||
|                                         req_info["sp_entity_id"],  | ||||
|                                         req_info["request"].name_id_policy,  | ||||
|                                         userid) | ||||
|     except Exception, excp: | ||||
|         if logger: logger.error("Exception: %s" % (excp,)) | ||||
|         raise | ||||
|          | ||||
|     if logger: logger.info("AuthNResponse: %s" % authn_resp) | ||||
|  | ||||
|     response = ["<head>", | ||||
|                 "<title>SAML 2.0 POST</title>", | ||||
|                 "</head><body>", | ||||
|                 FORM_SPEC % (req_info["consumer_url"], | ||||
|                              base64.b64encode("".join(authn_resp)), "/"), | ||||
|                 """<script type="text/javascript" language="JavaScript">""", | ||||
|                 "     document.myform.submit();", | ||||
|                 """</script>""", | ||||
|                 "</body>"] | ||||
|  | ||||
|     start_response('200 OK', [('Content-Type', 'text/html')]) | ||||
|     return response | ||||
|      | ||||
| def whoami(environ, start_response, user, logger): | ||||
|     start_response('200 OK', [('Content-Type', 'text/html')]) | ||||
|     identity = environ["repoze.who.identity"].copy() | ||||
|     for prop in ["login", "password"]: | ||||
|         try: | ||||
|             del identity[prop] | ||||
|         except KeyError: | ||||
|             continue | ||||
|     response = dict_to_table(identity) | ||||
|     return response[:] | ||||
|      | ||||
| def not_found(environ, start_response, logger): | ||||
|     """Called if no URL matches.""" | ||||
|     start_response('404 NOT FOUND', [('Content-Type', 'text/plain')]) | ||||
|     return ['Not Found'] | ||||
|  | ||||
| def not_authn(environ, start_response, logger): | ||||
|     if "QUERY_STRING" in environ: | ||||
|         query = parse_qs(environ["QUERY_STRING"]) | ||||
|         if logger: logger.info("query: %s" % query) | ||||
|     start_response('401 Unauthorized', [('Content-Type', 'text/plain')]) | ||||
|     return ['Unknown user'] | ||||
|  | ||||
| def slo(environ, start_response, user, logger): | ||||
|     """ Expects a HTTP-redirect logout request """ | ||||
|  | ||||
|     query = None | ||||
|     if "QUERY_STRING" in environ: | ||||
|         if logger: logger.info("Query string: %s" % environ["QUERY_STRING"]) | ||||
|         query = parse_qs(environ["QUERY_STRING"]) | ||||
|  | ||||
|     if not query: | ||||
|         start_response('401 Unauthorized', [('Content-Type', 'text/plain')]) | ||||
|         return ['Unknown user'] | ||||
|  | ||||
|     try: | ||||
|         req_info = IDP.parse_logout_request(query["SAMLRequest"][0], | ||||
|                                             BINDING_HTTP_REDIRECT) | ||||
|         logger.info("LOGOUT request parsed OK") | ||||
|         logger.info("REQ_INFO: %s" % req_info.message) | ||||
|     except KeyError, exc: | ||||
|         if logger: logger.info("logout request error: %s" % (exc,)) | ||||
|         # return error reply | ||||
|  | ||||
|     # look for the subject | ||||
|     subject = req_info.subject_id() | ||||
|     subject = subject.text.strip() | ||||
|     sp_entity_id = req_info.message.issuer.text.strip() | ||||
|     logger.info("Logout subject: %s" % (subject,)) | ||||
|     logger.info("local identifier: %s" % IDP.ident.local_name(sp_entity_id,  | ||||
|                                                                 subject)) | ||||
|     # remove the authentication | ||||
|      | ||||
|     status = None | ||||
|  | ||||
|     # Either HTTP-Post or HTTP-redirect is possible | ||||
|     bindings = [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT] | ||||
|     (resp, headers, message) = IDP.logout_response(req_info.message, bindings) | ||||
|     #headers.append(session.cookie(expire="now")) | ||||
|     logger.info("Response code: %s" % (resp,)) | ||||
|     logger.info("Header: %s" % (headers,)) | ||||
|     delco = delete_cookie(environ, "pysaml2idp") | ||||
|     if delco: | ||||
|         headers.append(delco) | ||||
|     start_response(resp, headers) | ||||
|     return message | ||||
|  | ||||
| def delete_cookie(environ, name): | ||||
|     kaka = environ.get("HTTP_COOKIE", '') | ||||
|     if kaka: | ||||
|         cookie_obj = SimpleCookie(kaka) | ||||
|         morsel = cookie_obj.get(name, None) | ||||
|         cookie = SimpleCookie() | ||||
|         cookie[name] = morsel | ||||
|         cookie[name]["expires"] = \ | ||||
|             _expiration("now", "%a, %d-%b-%Y %H:%M:%S CET") | ||||
|         return tuple(cookie.output().split(": ", 1)) | ||||
|     return None | ||||
|      | ||||
| # ---------------------------------------------------------------------------- | ||||
|  | ||||
| # map urls to functions | ||||
| URLS = [ | ||||
|     (r'whoami$', whoami), | ||||
|     (r'whoami/(.*)$', whoami), | ||||
|     (r'sso$', sso), | ||||
|     (r'sso/(.*)$', sso), | ||||
|     (r'logout$', slo), | ||||
|     (r'logout/(.*)$', slo), | ||||
| ] | ||||
|  | ||||
| # ---------------------------------------------------------------------------- | ||||
|  | ||||
| def application(environ, start_response): | ||||
|     """ | ||||
|     The main WSGI application. Dispatch the current request to | ||||
|     the functions from above and store the regular expression | ||||
|     captures in the WSGI environment as  `myapp.url_args` so that | ||||
|     the functions from above can access the url placeholders. | ||||
|  | ||||
|     If nothing matches call the `not_found` function. | ||||
|      | ||||
|     :param environ: The HTTP application environment | ||||
|     :param start_response: The application to run when the handling of the  | ||||
|         request is done | ||||
|     :return: The response as a list of lines | ||||
|     """ | ||||
|     user = environ.get("REMOTE_USER", "") | ||||
|     kaka = environ.get("HTTP_COOKIE", '') | ||||
|     if not user: | ||||
|         user = environ.get("repoze.who.identity", "") | ||||
|  | ||||
|     path = environ.get('PATH_INFO', '').lstrip('/') | ||||
|     logger = environ.get('repoze.who.logger') | ||||
|     if logger: logger.info("<application> PATH: %s" % path) | ||||
|     if logger: logger.info("Cookie: %s" % (kaka,)) | ||||
|     for regex, callback in URLS: | ||||
|         if user: | ||||
|             match = re.search(regex, path) | ||||
|             if match is not None: | ||||
|                 try: | ||||
|                     environ['myapp.url_args'] = match.groups()[0] | ||||
|                 except IndexError: | ||||
|                     environ['myapp.url_args'] = path | ||||
|                 if logger: logger.info("callback: %s" % (callback,)) | ||||
|                 return callback(environ, start_response, user, logger) | ||||
|         else: | ||||
|             if logger: logger.info("-- No USER --") | ||||
|             return not_authn(environ, start_response, logger) | ||||
|     return not_found(environ, start_response, logger) | ||||
|  | ||||
| # ---------------------------------------------------------------------------- | ||||
|  | ||||
| from repoze.who.config import make_middleware_with_config | ||||
|  | ||||
| APP_WITH_AUTH = make_middleware_with_config(application, {"here":"."},  | ||||
|                         './who.ini', log_file="app.log") | ||||
|  | ||||
| # ---------------------------------------------------------------------------- | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     import sys | ||||
|     from wsgiref.simple_server import make_server | ||||
|     import logging | ||||
|     LOG_FILENAME = "./idp.log" | ||||
|     PORT = 8088 | ||||
|      | ||||
|     logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)     | ||||
|      | ||||
|     IDP = server.Server(sys.argv[1], log=logging, debug=1) | ||||
|     SRV = make_server('localhost', PORT, APP_WITH_AUTH) | ||||
|     print "IdP listening on port: %s" % PORT | ||||
|     SRV.serve_forever() | ||||
							
								
								
									
										45
									
								
								example/idp/idp_conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								example/idp/idp_conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| from saml2 import BINDING_HTTP_REDIRECT | ||||
| from saml2.saml import NAME_FORMAT_URI | ||||
|  | ||||
| BASE = "http://localhost:8088/" | ||||
|  | ||||
| CONFIG={ | ||||
|     "entityid" : "urn:mace:umu.se:saml:roland:idp", | ||||
|     "description": "My IDP", | ||||
|     "service": { | ||||
|         "idp": { | ||||
|             "name" : "Rolands IdP", | ||||
|             "endpoints" : { | ||||
|                 "single_sign_on_service" : [BASE+"sso"], | ||||
|                 "single_logout_service" : [(BASE+"logout", | ||||
|                                             BINDING_HTTP_REDIRECT)], | ||||
|             }, | ||||
|             "policy": { | ||||
|                 "default": { | ||||
|                     "lifetime": {"minutes":15}, | ||||
|                     "attribute_restrictions": None, # means all I have | ||||
|                     "name_form": NAME_FORMAT_URI | ||||
|                 }, | ||||
|                 "urn:mace:umu.se:saml:roland:sp": { | ||||
|                     "lifetime": {"minutes": 5}, | ||||
|                 } | ||||
|             }, | ||||
|             "subject_data": "./idp.subject.db", | ||||
|         } | ||||
|     }, | ||||
|     "debug" : 1, | ||||
|     "key_file" : "pki/mykey.pem", | ||||
|     "cert_file" : "pki/mycert.pem", | ||||
|     "metadata" : { | ||||
|         "local": ["../sp/sp.xml"], | ||||
|     }, | ||||
|     "organization": { | ||||
|         "display_name": "Rolands Identiteter", | ||||
|         "name": "Rolands Identiteter", | ||||
|         "url": "http://www.example.com", | ||||
|     }, | ||||
|     # This database holds the map between a subjects local identifier and | ||||
|     # the identifier returned to a SP | ||||
|     #"xmlsec_binary": "/usr/local/bin/xmlsec1", | ||||
|     "attribute_map_dir" : "./attributemaps", | ||||
| } | ||||
							
								
								
									
										25
									
								
								example/idp/idp_user.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								example/idp/idp_user.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| [roland] | ||||
| surname=Hedberg | ||||
| givenName=Roland | ||||
| eduPersonAffiliation=staff | ||||
| uid=rohe0002 | ||||
|  | ||||
| [ozzie] | ||||
| surname=Guillen | ||||
| givenName=Ozzie | ||||
| eduPersonAffiliation=affiliate | ||||
|  | ||||
| [derek] | ||||
| surname=Jeter | ||||
| givenName=Derek | ||||
| eduPersonAffiliation=affiliate | ||||
|  | ||||
| [ichiro] | ||||
| surname=Suzuki | ||||
| givenName=Ischiro | ||||
| eduPersonAffiliation=affiliate | ||||
|  | ||||
| [ryan] | ||||
| surname=Howard | ||||
| givenName=Ryan | ||||
| eduPersonAffiliation=affiliate | ||||
							
								
								
									
										5
									
								
								example/idp/passwd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								example/idp/passwd
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| roland:0Gwsj0fYeNAIk | ||||
| ozzie:wT390u9XwBFaU | ||||
| derek:efNb53YcncbRI | ||||
| ryan:YlIhvZ6Rdt6fA | ||||
| ischiro:wgMhJvmkQgMGs | ||||
							
								
								
									
										18
									
								
								example/idp/pki/mycert.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/idp/pki/mycert.pem
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV | ||||
| BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx | ||||
| EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz | ||||
| MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l | ||||
| YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw | ||||
| DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7 | ||||
| bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC | ||||
| FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR | ||||
| mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW | ||||
| BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9 | ||||
| o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW | ||||
| BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE | ||||
| AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF | ||||
| BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO | ||||
| zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN | ||||
| +vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI= | ||||
| -----END CERTIFICATE----- | ||||
							
								
								
									
										15
									
								
								example/idp/pki/mykey.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								example/idp/pki/mykey.pem
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| -----BEGIN RSA PRIVATE KEY----- | ||||
| MIICXAIBAAKBgQDkJWP7bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr | ||||
| 6/ROgW96ZeQ57fzVy2MCFiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43 | ||||
| qCfLx+clUlOvtnsoMiiRmo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQAB | ||||
| AoGAbx9rKH91DCw/ZEPhHsVXJ6cYHxGcMoAWvnMMC9WUN+bNo4gNL205DLfsxXA1 | ||||
| jqXFXZj3+38vSFumGPA6IvXrN+Wyp3+Lz3QGc4K5OdHeBtYlxa6EsrxPgvuxYDUB | ||||
| vx3xdWPMjy06G/ML+pR9XHnRaPNubXQX3UxGBuLjwNXVmyECQQD2/D84tYoCGWoq | ||||
| 5FhUBxFUy2nnOLKYC/GGxBTX62iLfMQ3fbQcdg2pJsB5rrniyZf7UL+9FOsAO9k1 | ||||
| 8DO7G12DAkEA7Hkdg1KEw4ZfjnnjEa+KqpyLTLRQ91uTVW6kzR+4zY719iUJ/PXE | ||||
| PxJqm1ot7mJd1LW+bWtjLpxs7jYH19V+kQJBAIEpn2JnxdmdMuFlcy/WVmDy09pg | ||||
| 0z0imdexeXkFmjHAONkQOv3bWv+HzYaVMo8AgCOksfEPHGqN4eUMTfFeuUMCQF+5 | ||||
| E1JSd/2yCkJhYqKJHae8oMLXByNqRXTCyiFioutK4JPYIHfugJdLfC4QziD+Xp85 | ||||
| RrGCU+7NUWcIJhqfiJECQAIgUAzfzhdj5AyICaFPaOQ+N8FVMLcTyqeTXP0sIlFk | ||||
| JStVibemTRCbxdXXM7OVipz1oW3PBVEO3t/VyjiaGGg= | ||||
| -----END RSA PRIVATE KEY----- | ||||
							
								
								
									
										57
									
								
								example/idp/who.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								example/idp/who.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| [plugin:form] | ||||
| # identificaion and challenge | ||||
| use = s2repoze.plugins.formswithhidden:make_plugin | ||||
| login_form_qs = __do_login | ||||
| rememberer_name = auth_tkt | ||||
| #form = %(here)s/login_form.html | ||||
|  | ||||
| [plugin:auth_tkt] | ||||
| # identification | ||||
| use = repoze.who.plugins.auth_tkt:make_plugin | ||||
| secret = cassiopeja | ||||
| cookie_name = pysaml2idp | ||||
| secure = False | ||||
| include_ip = True | ||||
| timeout=3600 | ||||
| reissue_time = 3000 | ||||
|  | ||||
| [plugin:basicauth] | ||||
| # identification and challenge | ||||
| use = repoze.who.plugins.basicauth:make_plugin | ||||
| realm = 'sample' | ||||
|  | ||||
| [plugin:htpasswd] | ||||
| # authentication | ||||
| use = repoze.who.plugins.htpasswd:make_plugin | ||||
| filename = %(here)s/passwd | ||||
| check_fn = repoze.who.plugins.htpasswd:crypt_check | ||||
|  | ||||
| [plugin:ini] | ||||
| use = s2repoze.plugins.ini:make_plugin | ||||
| ini_file = %(here)s/idp_user.ini | ||||
|  | ||||
| [general] | ||||
| request_classifier = repoze.who.classifiers:default_request_classifier | ||||
| challenge_decider = repoze.who.classifiers:default_challenge_decider | ||||
| remote_user_key = REMOTE_USER | ||||
|  | ||||
| [identifiers] | ||||
| # plugin_name;classifier_name:.. or just plugin_name (good for any) | ||||
| plugins = | ||||
|       form;browser | ||||
|       auth_tkt | ||||
|       basicauth | ||||
|  | ||||
| [authenticators] | ||||
| # plugin_name;classifier_name.. or just plugin_name (good for any) | ||||
| plugins = | ||||
|       htpasswd | ||||
|  | ||||
| [challengers] | ||||
| # plugin_name;classifier_name:.. or just plugin_name (good for any) | ||||
| plugins = | ||||
|       form;browser | ||||
|       basicauth | ||||
|  | ||||
| [mdproviders] | ||||
| plugins = ini | ||||
							
								
								
									
										19
									
								
								example/run.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								example/run.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| # Created by Roland Hedberg on 3/25/10. | ||||
| # Copyright 2010 Umeå Universitet. All rights reserved. | ||||
|  | ||||
| cd sp | ||||
| ../../tools/make_metadata.py sp_conf > sp.xml | ||||
|  | ||||
| cd ../idp | ||||
| ../../tools/make_metadata.py idp_conf > idp.xml | ||||
|  | ||||
| cd ../sp | ||||
| ./sp.py sp_conf & | ||||
|  | ||||
| cd ../idp | ||||
| ./idp.py idp_conf & | ||||
|  | ||||
| cd .. | ||||
|  | ||||
							
								
								
									
										326
									
								
								example/sp/attributemaps/basic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								example/sp/attributemaps/basic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,326 @@ | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", | ||||
|     "fro": { | ||||
|         'urn:mace:dir:attribute-def:aRecord': 'aRecord', | ||||
|         'urn:mace:dir:attribute-def:aliasedEntryName': 'aliasedEntryName', | ||||
|         'urn:mace:dir:attribute-def:aliasedObjectName': 'aliasedObjectName', | ||||
|         'urn:mace:dir:attribute-def:associatedDomain': 'associatedDomain', | ||||
|         'urn:mace:dir:attribute-def:associatedName': 'associatedName', | ||||
|         'urn:mace:dir:attribute-def:audio': 'audio', | ||||
|         'urn:mace:dir:attribute-def:authorityRevocationList': 'authorityRevocationList', | ||||
|         'urn:mace:dir:attribute-def:buildingName': 'buildingName', | ||||
|         'urn:mace:dir:attribute-def:businessCategory': 'businessCategory', | ||||
|         'urn:mace:dir:attribute-def:c': 'c', | ||||
|         'urn:mace:dir:attribute-def:cACertificate': 'cACertificate', | ||||
|         'urn:mace:dir:attribute-def:cNAMERecord': 'cNAMERecord', | ||||
|         'urn:mace:dir:attribute-def:carLicense': 'carLicense', | ||||
|         'urn:mace:dir:attribute-def:certificateRevocationList': 'certificateRevocationList', | ||||
|         'urn:mace:dir:attribute-def:cn': 'cn', | ||||
|         'urn:mace:dir:attribute-def:co': 'co', | ||||
|         'urn:mace:dir:attribute-def:commonName': 'commonName', | ||||
|         'urn:mace:dir:attribute-def:countryName': 'countryName', | ||||
|         'urn:mace:dir:attribute-def:crossCertificatePair': 'crossCertificatePair', | ||||
|         'urn:mace:dir:attribute-def:dITRedirect': 'dITRedirect', | ||||
|         'urn:mace:dir:attribute-def:dSAQuality': 'dSAQuality', | ||||
|         'urn:mace:dir:attribute-def:dc': 'dc', | ||||
|         'urn:mace:dir:attribute-def:deltaRevocationList': 'deltaRevocationList', | ||||
|         'urn:mace:dir:attribute-def:departmentNumber': 'departmentNumber', | ||||
|         'urn:mace:dir:attribute-def:description': 'description', | ||||
|         'urn:mace:dir:attribute-def:destinationIndicator': 'destinationIndicator', | ||||
|         'urn:mace:dir:attribute-def:displayName': 'displayName', | ||||
|         'urn:mace:dir:attribute-def:distinguishedName': 'distinguishedName', | ||||
|         'urn:mace:dir:attribute-def:dmdName': 'dmdName', | ||||
|         'urn:mace:dir:attribute-def:dnQualifier': 'dnQualifier', | ||||
|         'urn:mace:dir:attribute-def:documentAuthor': 'documentAuthor', | ||||
|         'urn:mace:dir:attribute-def:documentIdentifier': 'documentIdentifier', | ||||
|         'urn:mace:dir:attribute-def:documentLocation': 'documentLocation', | ||||
|         'urn:mace:dir:attribute-def:documentPublisher': 'documentPublisher', | ||||
|         'urn:mace:dir:attribute-def:documentTitle': 'documentTitle', | ||||
|         'urn:mace:dir:attribute-def:documentVersion': 'documentVersion', | ||||
|         'urn:mace:dir:attribute-def:domainComponent': 'domainComponent', | ||||
|         'urn:mace:dir:attribute-def:drink': 'drink', | ||||
|         'urn:mace:dir:attribute-def:eduOrgHomePageURI': 'eduOrgHomePageURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI': 'eduOrgIdentityAuthNPolicyURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgLegalName': 'eduOrgLegalName', | ||||
|         'urn:mace:dir:attribute-def:eduOrgSuperiorURI': 'eduOrgSuperiorURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgWhitePagesURI': 'eduOrgWhitePagesURI', | ||||
|         'urn:mace:dir:attribute-def:eduPersonAffiliation': 'eduPersonAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonEntitlement': 'eduPersonEntitlement', | ||||
|         'urn:mace:dir:attribute-def:eduPersonNickname': 'eduPersonNickname', | ||||
|         'urn:mace:dir:attribute-def:eduPersonOrgDN': 'eduPersonOrgDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonOrgUnitDN': 'eduPersonOrgUnitDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation': 'eduPersonPrimaryAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN': 'eduPersonPrimaryOrgUnitDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrincipalName': 'eduPersonPrincipalName', | ||||
|         'urn:mace:dir:attribute-def:eduPersonScopedAffiliation': 'eduPersonScopedAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonTargetedID': 'eduPersonTargetedID', | ||||
|         'urn:mace:dir:attribute-def:email': 'email', | ||||
|         'urn:mace:dir:attribute-def:emailAddress': 'emailAddress', | ||||
|         'urn:mace:dir:attribute-def:employeeNumber': 'employeeNumber', | ||||
|         'urn:mace:dir:attribute-def:employeeType': 'employeeType', | ||||
|         'urn:mace:dir:attribute-def:enhancedSearchGuide': 'enhancedSearchGuide', | ||||
|         'urn:mace:dir:attribute-def:facsimileTelephoneNumber': 'facsimileTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:favouriteDrink': 'favouriteDrink', | ||||
|         'urn:mace:dir:attribute-def:fax': 'fax', | ||||
|         'urn:mace:dir:attribute-def:federationFeideSchemaVersion': 'federationFeideSchemaVersion', | ||||
|         'urn:mace:dir:attribute-def:friendlyCountryName': 'friendlyCountryName', | ||||
|         'urn:mace:dir:attribute-def:generationQualifier': 'generationQualifier', | ||||
|         'urn:mace:dir:attribute-def:givenName': 'givenName', | ||||
|         'urn:mace:dir:attribute-def:gn': 'gn', | ||||
|         'urn:mace:dir:attribute-def:homePhone': 'homePhone', | ||||
|         'urn:mace:dir:attribute-def:homePostalAddress': 'homePostalAddress', | ||||
|         'urn:mace:dir:attribute-def:homeTelephoneNumber': 'homeTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:host': 'host', | ||||
|         'urn:mace:dir:attribute-def:houseIdentifier': 'houseIdentifier', | ||||
|         'urn:mace:dir:attribute-def:info': 'info', | ||||
|         'urn:mace:dir:attribute-def:initials': 'initials', | ||||
|         'urn:mace:dir:attribute-def:internationaliSDNNumber': 'internationaliSDNNumber', | ||||
|         'urn:mace:dir:attribute-def:janetMailbox': 'janetMailbox', | ||||
|         'urn:mace:dir:attribute-def:jpegPhoto': 'jpegPhoto', | ||||
|         'urn:mace:dir:attribute-def:knowledgeInformation': 'knowledgeInformation', | ||||
|         'urn:mace:dir:attribute-def:l': 'l', | ||||
|         'urn:mace:dir:attribute-def:labeledURI': 'labeledURI', | ||||
|         'urn:mace:dir:attribute-def:localityName': 'localityName', | ||||
|         'urn:mace:dir:attribute-def:mDRecord': 'mDRecord', | ||||
|         'urn:mace:dir:attribute-def:mXRecord': 'mXRecord', | ||||
|         'urn:mace:dir:attribute-def:mail': 'mail', | ||||
|         'urn:mace:dir:attribute-def:mailPreferenceOption': 'mailPreferenceOption', | ||||
|         'urn:mace:dir:attribute-def:manager': 'manager', | ||||
|         'urn:mace:dir:attribute-def:member': 'member', | ||||
|         'urn:mace:dir:attribute-def:mobile': 'mobile', | ||||
|         'urn:mace:dir:attribute-def:mobileTelephoneNumber': 'mobileTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:nSRecord': 'nSRecord', | ||||
|         'urn:mace:dir:attribute-def:name': 'name', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgAcronym': 'norEduOrgAcronym', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgNIN': 'norEduOrgNIN', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgSchemaVersion': 'norEduOrgSchemaVersion', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier': 'norEduOrgUniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUniqueNumber': 'norEduOrgUniqueNumber', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier': 'norEduOrgUnitUniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber': 'norEduOrgUnitUniqueNumber', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonBirthDate': 'norEduPersonBirthDate', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonLIN': 'norEduPersonLIN', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonNIN': 'norEduPersonNIN', | ||||
|         'urn:mace:dir:attribute-def:o': 'o', | ||||
|         'urn:mace:dir:attribute-def:objectClass': 'objectClass', | ||||
|         'urn:mace:dir:attribute-def:organizationName': 'organizationName', | ||||
|         'urn:mace:dir:attribute-def:organizationalStatus': 'organizationalStatus', | ||||
|         'urn:mace:dir:attribute-def:organizationalUnitName': 'organizationalUnitName', | ||||
|         'urn:mace:dir:attribute-def:otherMailbox': 'otherMailbox', | ||||
|         'urn:mace:dir:attribute-def:ou': 'ou', | ||||
|         'urn:mace:dir:attribute-def:owner': 'owner', | ||||
|         'urn:mace:dir:attribute-def:pager': 'pager', | ||||
|         'urn:mace:dir:attribute-def:pagerTelephoneNumber': 'pagerTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:personalSignature': 'personalSignature', | ||||
|         'urn:mace:dir:attribute-def:personalTitle': 'personalTitle', | ||||
|         'urn:mace:dir:attribute-def:photo': 'photo', | ||||
|         'urn:mace:dir:attribute-def:physicalDeliveryOfficeName': 'physicalDeliveryOfficeName', | ||||
|         'urn:mace:dir:attribute-def:pkcs9email': 'pkcs9email', | ||||
|         'urn:mace:dir:attribute-def:postOfficeBox': 'postOfficeBox', | ||||
|         'urn:mace:dir:attribute-def:postalAddress': 'postalAddress', | ||||
|         'urn:mace:dir:attribute-def:postalCode': 'postalCode', | ||||
|         'urn:mace:dir:attribute-def:preferredDeliveryMethod': 'preferredDeliveryMethod', | ||||
|         'urn:mace:dir:attribute-def:preferredLanguage': 'preferredLanguage', | ||||
|         'urn:mace:dir:attribute-def:presentationAddress': 'presentationAddress', | ||||
|         'urn:mace:dir:attribute-def:protocolInformation': 'protocolInformation', | ||||
|         'urn:mace:dir:attribute-def:pseudonym': 'pseudonym', | ||||
|         'urn:mace:dir:attribute-def:registeredAddress': 'registeredAddress', | ||||
|         'urn:mace:dir:attribute-def:rfc822Mailbox': 'rfc822Mailbox', | ||||
|         'urn:mace:dir:attribute-def:roleOccupant': 'roleOccupant', | ||||
|         'urn:mace:dir:attribute-def:roomNumber': 'roomNumber', | ||||
|         'urn:mace:dir:attribute-def:sOARecord': 'sOARecord', | ||||
|         'urn:mace:dir:attribute-def:searchGuide': 'searchGuide', | ||||
|         'urn:mace:dir:attribute-def:secretary': 'secretary', | ||||
|         'urn:mace:dir:attribute-def:seeAlso': 'seeAlso', | ||||
|         'urn:mace:dir:attribute-def:serialNumber': 'serialNumber', | ||||
|         'urn:mace:dir:attribute-def:singleLevelQuality': 'singleLevelQuality', | ||||
|         'urn:mace:dir:attribute-def:sn': 'sn', | ||||
|         'urn:mace:dir:attribute-def:st': 'st', | ||||
|         'urn:mace:dir:attribute-def:stateOrProvinceName': 'stateOrProvinceName', | ||||
|         'urn:mace:dir:attribute-def:street': 'street', | ||||
|         'urn:mace:dir:attribute-def:streetAddress': 'streetAddress', | ||||
|         'urn:mace:dir:attribute-def:subtreeMaximumQuality': 'subtreeMaximumQuality', | ||||
|         'urn:mace:dir:attribute-def:subtreeMinimumQuality': 'subtreeMinimumQuality', | ||||
|         'urn:mace:dir:attribute-def:supportedAlgorithms': 'supportedAlgorithms', | ||||
|         'urn:mace:dir:attribute-def:supportedApplicationContext': 'supportedApplicationContext', | ||||
|         'urn:mace:dir:attribute-def:surname': 'surname', | ||||
|         'urn:mace:dir:attribute-def:telephoneNumber': 'telephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:teletexTerminalIdentifier': 'teletexTerminalIdentifier', | ||||
|         'urn:mace:dir:attribute-def:telexNumber': 'telexNumber', | ||||
|         'urn:mace:dir:attribute-def:textEncodedORAddress': 'textEncodedORAddress', | ||||
|         'urn:mace:dir:attribute-def:title': 'title', | ||||
|         'urn:mace:dir:attribute-def:uid': 'uid', | ||||
|         'urn:mace:dir:attribute-def:uniqueIdentifier': 'uniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:uniqueMember': 'uniqueMember', | ||||
|         'urn:mace:dir:attribute-def:userCertificate': 'userCertificate', | ||||
|         'urn:mace:dir:attribute-def:userClass': 'userClass', | ||||
|         'urn:mace:dir:attribute-def:userPKCS12': 'userPKCS12', | ||||
|         'urn:mace:dir:attribute-def:userPassword': 'userPassword', | ||||
|         'urn:mace:dir:attribute-def:userSMIMECertificate': 'userSMIMECertificate', | ||||
|         'urn:mace:dir:attribute-def:userid': 'userid', | ||||
|         'urn:mace:dir:attribute-def:x121Address': 'x121Address', | ||||
|         'urn:mace:dir:attribute-def:x500UniqueIdentifier': 'x500UniqueIdentifier', | ||||
|         }, | ||||
|     "to": { | ||||
|         'aRecord': 'urn:mace:dir:attribute-def:aRecord', | ||||
|         'aliasedEntryName': 'urn:mace:dir:attribute-def:aliasedEntryName', | ||||
|         'aliasedObjectName': 'urn:mace:dir:attribute-def:aliasedObjectName', | ||||
|         'associatedDomain': 'urn:mace:dir:attribute-def:associatedDomain', | ||||
|         'associatedName': 'urn:mace:dir:attribute-def:associatedName', | ||||
|         'audio': 'urn:mace:dir:attribute-def:audio', | ||||
|         'authorityRevocationList': 'urn:mace:dir:attribute-def:authorityRevocationList', | ||||
|         'buildingName': 'urn:mace:dir:attribute-def:buildingName', | ||||
|         'businessCategory': 'urn:mace:dir:attribute-def:businessCategory', | ||||
|         'c': 'urn:mace:dir:attribute-def:c', | ||||
|         'cACertificate': 'urn:mace:dir:attribute-def:cACertificate', | ||||
|         'cNAMERecord': 'urn:mace:dir:attribute-def:cNAMERecord', | ||||
|         'carLicense': 'urn:mace:dir:attribute-def:carLicense', | ||||
|         'certificateRevocationList': 'urn:mace:dir:attribute-def:certificateRevocationList', | ||||
|         'cn': 'urn:mace:dir:attribute-def:cn', | ||||
|         'co': 'urn:mace:dir:attribute-def:co', | ||||
|         'commonName': 'urn:mace:dir:attribute-def:commonName', | ||||
|         'countryName': 'urn:mace:dir:attribute-def:countryName', | ||||
|         'crossCertificatePair': 'urn:mace:dir:attribute-def:crossCertificatePair', | ||||
|         'dITRedirect': 'urn:mace:dir:attribute-def:dITRedirect', | ||||
|         'dSAQuality': 'urn:mace:dir:attribute-def:dSAQuality', | ||||
|         'dc': 'urn:mace:dir:attribute-def:dc', | ||||
|         'deltaRevocationList': 'urn:mace:dir:attribute-def:deltaRevocationList', | ||||
|         'departmentNumber': 'urn:mace:dir:attribute-def:departmentNumber', | ||||
|         'description': 'urn:mace:dir:attribute-def:description', | ||||
|         'destinationIndicator': 'urn:mace:dir:attribute-def:destinationIndicator', | ||||
|         'displayName': 'urn:mace:dir:attribute-def:displayName', | ||||
|         'distinguishedName': 'urn:mace:dir:attribute-def:distinguishedName', | ||||
|         'dmdName': 'urn:mace:dir:attribute-def:dmdName', | ||||
|         'dnQualifier': 'urn:mace:dir:attribute-def:dnQualifier', | ||||
|         'documentAuthor': 'urn:mace:dir:attribute-def:documentAuthor', | ||||
|         'documentIdentifier': 'urn:mace:dir:attribute-def:documentIdentifier', | ||||
|         'documentLocation': 'urn:mace:dir:attribute-def:documentLocation', | ||||
|         'documentPublisher': 'urn:mace:dir:attribute-def:documentPublisher', | ||||
|         'documentTitle': 'urn:mace:dir:attribute-def:documentTitle', | ||||
|         'documentVersion': 'urn:mace:dir:attribute-def:documentVersion', | ||||
|         'domainComponent': 'urn:mace:dir:attribute-def:domainComponent', | ||||
|         'drink': 'urn:mace:dir:attribute-def:drink', | ||||
|         'eduOrgHomePageURI': 'urn:mace:dir:attribute-def:eduOrgHomePageURI', | ||||
|         'eduOrgIdentityAuthNPolicyURI': 'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI', | ||||
|         'eduOrgLegalName': 'urn:mace:dir:attribute-def:eduOrgLegalName', | ||||
|         'eduOrgSuperiorURI': 'urn:mace:dir:attribute-def:eduOrgSuperiorURI', | ||||
|         'eduOrgWhitePagesURI': 'urn:mace:dir:attribute-def:eduOrgWhitePagesURI', | ||||
|         'eduPersonAffiliation': 'urn:mace:dir:attribute-def:eduPersonAffiliation', | ||||
|         'eduPersonEntitlement': 'urn:mace:dir:attribute-def:eduPersonEntitlement', | ||||
|         'eduPersonNickname': 'urn:mace:dir:attribute-def:eduPersonNickname', | ||||
|         'eduPersonOrgDN': 'urn:mace:dir:attribute-def:eduPersonOrgDN', | ||||
|         'eduPersonOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonOrgUnitDN', | ||||
|         'eduPersonPrimaryAffiliation': 'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation', | ||||
|         'eduPersonPrimaryOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN', | ||||
|         'eduPersonPrincipalName': 'urn:mace:dir:attribute-def:eduPersonPrincipalName', | ||||
|         'eduPersonScopedAffiliation': 'urn:mace:dir:attribute-def:eduPersonScopedAffiliation', | ||||
|         'eduPersonTargetedID': 'urn:mace:dir:attribute-def:eduPersonTargetedID', | ||||
|         'email': 'urn:mace:dir:attribute-def:email', | ||||
|         'emailAddress': 'urn:mace:dir:attribute-def:emailAddress', | ||||
|         'employeeNumber': 'urn:mace:dir:attribute-def:employeeNumber', | ||||
|         'employeeType': 'urn:mace:dir:attribute-def:employeeType', | ||||
|         'enhancedSearchGuide': 'urn:mace:dir:attribute-def:enhancedSearchGuide', | ||||
|         'facsimileTelephoneNumber': 'urn:mace:dir:attribute-def:facsimileTelephoneNumber', | ||||
|         'favouriteDrink': 'urn:mace:dir:attribute-def:favouriteDrink', | ||||
|         'fax': 'urn:mace:dir:attribute-def:fax', | ||||
|         'federationFeideSchemaVersion': 'urn:mace:dir:attribute-def:federationFeideSchemaVersion', | ||||
|         'friendlyCountryName': 'urn:mace:dir:attribute-def:friendlyCountryName', | ||||
|         'generationQualifier': 'urn:mace:dir:attribute-def:generationQualifier', | ||||
|         'givenName': 'urn:mace:dir:attribute-def:givenName', | ||||
|         'gn': 'urn:mace:dir:attribute-def:gn', | ||||
|         'homePhone': 'urn:mace:dir:attribute-def:homePhone', | ||||
|         'homePostalAddress': 'urn:mace:dir:attribute-def:homePostalAddress', | ||||
|         'homeTelephoneNumber': 'urn:mace:dir:attribute-def:homeTelephoneNumber', | ||||
|         'host': 'urn:mace:dir:attribute-def:host', | ||||
|         'houseIdentifier': 'urn:mace:dir:attribute-def:houseIdentifier', | ||||
|         'info': 'urn:mace:dir:attribute-def:info', | ||||
|         'initials': 'urn:mace:dir:attribute-def:initials', | ||||
|         'internationaliSDNNumber': 'urn:mace:dir:attribute-def:internationaliSDNNumber', | ||||
|         'janetMailbox': 'urn:mace:dir:attribute-def:janetMailbox', | ||||
|         'jpegPhoto': 'urn:mace:dir:attribute-def:jpegPhoto', | ||||
|         'knowledgeInformation': 'urn:mace:dir:attribute-def:knowledgeInformation', | ||||
|         'l': 'urn:mace:dir:attribute-def:l', | ||||
|         'labeledURI': 'urn:mace:dir:attribute-def:labeledURI', | ||||
|         'localityName': 'urn:mace:dir:attribute-def:localityName', | ||||
|         'mDRecord': 'urn:mace:dir:attribute-def:mDRecord', | ||||
|         'mXRecord': 'urn:mace:dir:attribute-def:mXRecord', | ||||
|         'mail': 'urn:mace:dir:attribute-def:mail', | ||||
|         'mailPreferenceOption': 'urn:mace:dir:attribute-def:mailPreferenceOption', | ||||
|         'manager': 'urn:mace:dir:attribute-def:manager', | ||||
|         'member': 'urn:mace:dir:attribute-def:member', | ||||
|         'mobile': 'urn:mace:dir:attribute-def:mobile', | ||||
|         'mobileTelephoneNumber': 'urn:mace:dir:attribute-def:mobileTelephoneNumber', | ||||
|         'nSRecord': 'urn:mace:dir:attribute-def:nSRecord', | ||||
|         'name': 'urn:mace:dir:attribute-def:name', | ||||
|         'norEduOrgAcronym': 'urn:mace:dir:attribute-def:norEduOrgAcronym', | ||||
|         'norEduOrgNIN': 'urn:mace:dir:attribute-def:norEduOrgNIN', | ||||
|         'norEduOrgSchemaVersion': 'urn:mace:dir:attribute-def:norEduOrgSchemaVersion', | ||||
|         'norEduOrgUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier', | ||||
|         'norEduOrgUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUniqueNumber', | ||||
|         'norEduOrgUnitUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier', | ||||
|         'norEduOrgUnitUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber', | ||||
|         'norEduPersonBirthDate': 'urn:mace:dir:attribute-def:norEduPersonBirthDate', | ||||
|         'norEduPersonLIN': 'urn:mace:dir:attribute-def:norEduPersonLIN', | ||||
|         'norEduPersonNIN': 'urn:mace:dir:attribute-def:norEduPersonNIN', | ||||
|         'o': 'urn:mace:dir:attribute-def:o', | ||||
|         'objectClass': 'urn:mace:dir:attribute-def:objectClass', | ||||
|         'organizationName': 'urn:mace:dir:attribute-def:organizationName', | ||||
|         'organizationalStatus': 'urn:mace:dir:attribute-def:organizationalStatus', | ||||
|         'organizationalUnitName': 'urn:mace:dir:attribute-def:organizationalUnitName', | ||||
|         'otherMailbox': 'urn:mace:dir:attribute-def:otherMailbox', | ||||
|         'ou': 'urn:mace:dir:attribute-def:ou', | ||||
|         'owner': 'urn:mace:dir:attribute-def:owner', | ||||
|         'pager': 'urn:mace:dir:attribute-def:pager', | ||||
|         'pagerTelephoneNumber': 'urn:mace:dir:attribute-def:pagerTelephoneNumber', | ||||
|         'personalSignature': 'urn:mace:dir:attribute-def:personalSignature', | ||||
|         'personalTitle': 'urn:mace:dir:attribute-def:personalTitle', | ||||
|         'photo': 'urn:mace:dir:attribute-def:photo', | ||||
|         'physicalDeliveryOfficeName': 'urn:mace:dir:attribute-def:physicalDeliveryOfficeName', | ||||
|         'pkcs9email': 'urn:mace:dir:attribute-def:pkcs9email', | ||||
|         'postOfficeBox': 'urn:mace:dir:attribute-def:postOfficeBox', | ||||
|         'postalAddress': 'urn:mace:dir:attribute-def:postalAddress', | ||||
|         'postalCode': 'urn:mace:dir:attribute-def:postalCode', | ||||
|         'preferredDeliveryMethod': 'urn:mace:dir:attribute-def:preferredDeliveryMethod', | ||||
|         'preferredLanguage': 'urn:mace:dir:attribute-def:preferredLanguage', | ||||
|         'presentationAddress': 'urn:mace:dir:attribute-def:presentationAddress', | ||||
|         'protocolInformation': 'urn:mace:dir:attribute-def:protocolInformation', | ||||
|         'pseudonym': 'urn:mace:dir:attribute-def:pseudonym', | ||||
|         'registeredAddress': 'urn:mace:dir:attribute-def:registeredAddress', | ||||
|         'rfc822Mailbox': 'urn:mace:dir:attribute-def:rfc822Mailbox', | ||||
|         'roleOccupant': 'urn:mace:dir:attribute-def:roleOccupant', | ||||
|         'roomNumber': 'urn:mace:dir:attribute-def:roomNumber', | ||||
|         'sOARecord': 'urn:mace:dir:attribute-def:sOARecord', | ||||
|         'searchGuide': 'urn:mace:dir:attribute-def:searchGuide', | ||||
|         'secretary': 'urn:mace:dir:attribute-def:secretary', | ||||
|         'seeAlso': 'urn:mace:dir:attribute-def:seeAlso', | ||||
|         'serialNumber': 'urn:mace:dir:attribute-def:serialNumber', | ||||
|         'singleLevelQuality': 'urn:mace:dir:attribute-def:singleLevelQuality', | ||||
|         'sn': 'urn:mace:dir:attribute-def:sn', | ||||
|         'st': 'urn:mace:dir:attribute-def:st', | ||||
|         'stateOrProvinceName': 'urn:mace:dir:attribute-def:stateOrProvinceName', | ||||
|         'street': 'urn:mace:dir:attribute-def:street', | ||||
|         'streetAddress': 'urn:mace:dir:attribute-def:streetAddress', | ||||
|         'subtreeMaximumQuality': 'urn:mace:dir:attribute-def:subtreeMaximumQuality', | ||||
|         'subtreeMinimumQuality': 'urn:mace:dir:attribute-def:subtreeMinimumQuality', | ||||
|         'supportedAlgorithms': 'urn:mace:dir:attribute-def:supportedAlgorithms', | ||||
|         'supportedApplicationContext': 'urn:mace:dir:attribute-def:supportedApplicationContext', | ||||
|         'surname': 'urn:mace:dir:attribute-def:surname', | ||||
|         'telephoneNumber': 'urn:mace:dir:attribute-def:telephoneNumber', | ||||
|         'teletexTerminalIdentifier': 'urn:mace:dir:attribute-def:teletexTerminalIdentifier', | ||||
|         'telexNumber': 'urn:mace:dir:attribute-def:telexNumber', | ||||
|         'textEncodedORAddress': 'urn:mace:dir:attribute-def:textEncodedORAddress', | ||||
|         'title': 'urn:mace:dir:attribute-def:title', | ||||
|         'uid': 'urn:mace:dir:attribute-def:uid', | ||||
|         'uniqueIdentifier': 'urn:mace:dir:attribute-def:uniqueIdentifier', | ||||
|         'uniqueMember': 'urn:mace:dir:attribute-def:uniqueMember', | ||||
|         'userCertificate': 'urn:mace:dir:attribute-def:userCertificate', | ||||
|         'userClass': 'urn:mace:dir:attribute-def:userClass', | ||||
|         'userPKCS12': 'urn:mace:dir:attribute-def:userPKCS12', | ||||
|         'userPassword': 'urn:mace:dir:attribute-def:userPassword', | ||||
|         'userSMIMECertificate': 'urn:mace:dir:attribute-def:userSMIMECertificate', | ||||
|         'userid': 'urn:mace:dir:attribute-def:userid', | ||||
|         'x121Address': 'urn:mace:dir:attribute-def:x121Address', | ||||
|         'x500UniqueIdentifier': 'urn:mace:dir:attribute-def:x500UniqueIdentifier', | ||||
|     } | ||||
| } | ||||
							
								
								
									
										199
									
								
								example/sp/attributemaps/saml_uri.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								example/sp/attributemaps/saml_uri.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| __author__ = 'rolandh' | ||||
|  | ||||
| EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." | ||||
| X500ATTR_OID = "urn:oid:2.5.4." | ||||
| NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." | ||||
| NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.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." | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", | ||||
|     "fro": { | ||||
|         EDUPERSON_OID+'2': 'eduPersonNickname', | ||||
|         EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', | ||||
|         EDUPERSON_OID+'11': 'eduPersonAssurance', | ||||
|         EDUPERSON_OID+'10': 'eduPersonTargetedID', | ||||
|         EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', | ||||
|         NOREDUPERSON_OID+'6': 'norEduOrgAcronym', | ||||
|         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+'9': 'federationFeideSchemaVersion', | ||||
|         X500ATTR_OID+'53': 'deltaRevocationList', | ||||
|         X500ATTR_OID+'52': 'supportedAlgorithms', | ||||
|         X500ATTR_OID+'51': 'houseIdentifier', | ||||
|         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', | ||||
|         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', | ||||
|         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', | ||||
|     }, | ||||
|     "to": { | ||||
|         'roleOccupant': X500ATTR_OID+'33', | ||||
|         'gn': X500ATTR_OID+'42', | ||||
|         'norEduPersonNIN': NOREDUPERSON_OID+'5', | ||||
|         'title': X500ATTR_OID+'12', | ||||
|         'facsimileTelephoneNumber': X500ATTR_OID+'23', | ||||
|         'mail': UCL_DIR_PILOT+'3', | ||||
|         'postOfficeBox': X500ATTR_OID+'18', | ||||
|         'fax': X500ATTR_OID+'23', | ||||
|         'telephoneNumber': X500ATTR_OID+'20', | ||||
|         'norEduPersonBirthDate': NOREDUPERSON_OID+'3', | ||||
|         'rfc822Mailbox': UCL_DIR_PILOT+'3', | ||||
|         'dc': UCL_DIR_PILOT+'25', | ||||
|         'countryName': X500ATTR_OID+'6', | ||||
|         'emailAddress': PKCS_9+'1', | ||||
|         'employeeNumber': NETSCAPE_LDAP+'3', | ||||
|         'organizationName': X500ATTR_OID+'10', | ||||
|         'eduPersonAssurance': EDUPERSON_OID+'11', | ||||
|         'norEduOrgAcronym': NOREDUPERSON_OID+'6', | ||||
|         'registeredAddress': X500ATTR_OID+'26', | ||||
|         'physicalDeliveryOfficeName': X500ATTR_OID+'19', | ||||
|         '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', | ||||
|         'eduPersonPrincipalName': EDUPERSON_OID+'6', | ||||
|         'edupersonprincipalname': 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', | ||||
|         'displayName': NETSCAPE_LDAP+'241', | ||||
|         'businessCategory': X500ATTR_OID+'15', | ||||
|         'serialNumber': X500ATTR_OID+'5', | ||||
|         'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7', | ||||
|         'st': X500ATTR_OID+'8', | ||||
|         'carLicense': NETSCAPE_LDAP+'1', | ||||
|         'presentationAddress': X500ATTR_OID+'29', | ||||
|         'sn': X500ATTR_OID+'4', | ||||
|         'domainComponent': UCL_DIR_PILOT+'25', | ||||
|         'labeledURI': UMICH+'57', | ||||
|         'uid': UCL_DIR_PILOT+'1' | ||||
|     } | ||||
| }   | ||||
							
								
								
									
										190
									
								
								example/sp/attributemaps/shibboleth_uri.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								example/sp/attributemaps/shibboleth_uri.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." | ||||
| X500ATTR = "urn:oid:2.5.4." | ||||
| NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." | ||||
| NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.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." | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:mace:shibboleth:1.0:attributeNamespace:uri", | ||||
|     "fro": { | ||||
|         EDUPERSON_OID+'2': 'eduPersonNickname', | ||||
|         EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', | ||||
|         EDUPERSON_OID+'11': 'eduPersonAssurance', | ||||
|         EDUPERSON_OID+'10': 'eduPersonTargetedID', | ||||
|         EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', | ||||
|         NOREDUPERSON_OID+'6': 'norEduOrgAcronym', | ||||
|         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+'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', | ||||
|         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+'25': 'dc', | ||||
|         X500ATTR+'40': 'crossCertificatePair', | ||||
|         X500ATTR+'42': 'givenName', | ||||
|         X500ATTR+'43': 'initials', | ||||
|         X500ATTR+'44': 'generationQualifier', | ||||
|         X500ATTR+'45': 'x500UniqueIdentifier', | ||||
|         X500ATTR+'46': 'dnQualifier', | ||||
|         X500ATTR+'47': 'enhancedSearchGuide', | ||||
|         X500ATTR+'48': 'protocolInformation', | ||||
|         X500ATTR+'54': 'dmdName', | ||||
|         NETSCAPE_LDAP+'4': 'employeeType', | ||||
|         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":{ | ||||
|         'roleOccupant': X500ATTR+'33', | ||||
|         'gn': X500ATTR+'42', | ||||
|         'norEduPersonNIN': NOREDUPERSON_OID+'5', | ||||
|         'title': X500ATTR+'12', | ||||
|         'facsimileTelephoneNumber': X500ATTR+'23', | ||||
|         'mail': UCL_DIR_PILOT+'3', | ||||
|         'postOfficeBox': X500ATTR+'18', | ||||
|         'fax': X500ATTR+'23', | ||||
|         'telephoneNumber': X500ATTR+'20', | ||||
|         'norEduPersonBirthDate': NOREDUPERSON_OID+'3', | ||||
|         'rfc822Mailbox': UCL_DIR_PILOT+'3', | ||||
|         'dc': UCL_DIR_PILOT+'25', | ||||
|         'countryName': X500ATTR+'6', | ||||
|         'emailAddress': PKCS_9+'1', | ||||
|         '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', | ||||
|         'userSMIMECertificate': NETSCAPE_LDAP+'40', | ||||
|         'member': X500ATTR+'31', | ||||
|         'streetAddress': X500ATTR+'9', | ||||
|         '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', | ||||
|         'st': X500ATTR+'8', | ||||
|         'carLicense': NETSCAPE_LDAP+'1', | ||||
|         'presentationAddress': X500ATTR+'29', | ||||
|         'sn': X500ATTR+'4', | ||||
|         'domainComponent': UCL_DIR_PILOT+'25', | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								example/sp/pki/mycert.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								example/sp/pki/mycert.pem
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV | ||||
| BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx | ||||
| EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz | ||||
| MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l | ||||
| YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw | ||||
| DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7 | ||||
| bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC | ||||
| FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR | ||||
| mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW | ||||
| BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9 | ||||
| o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW | ||||
| BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE | ||||
| AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF | ||||
| BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO | ||||
| zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN | ||||
| +vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI= | ||||
| -----END CERTIFICATE----- | ||||
							
								
								
									
										15
									
								
								example/sp/pki/mykey.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								example/sp/pki/mykey.pem
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| -----BEGIN RSA PRIVATE KEY----- | ||||
| MIICXAIBAAKBgQDkJWP7bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr | ||||
| 6/ROgW96ZeQ57fzVy2MCFiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43 | ||||
| qCfLx+clUlOvtnsoMiiRmo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQAB | ||||
| AoGAbx9rKH91DCw/ZEPhHsVXJ6cYHxGcMoAWvnMMC9WUN+bNo4gNL205DLfsxXA1 | ||||
| jqXFXZj3+38vSFumGPA6IvXrN+Wyp3+Lz3QGc4K5OdHeBtYlxa6EsrxPgvuxYDUB | ||||
| vx3xdWPMjy06G/ML+pR9XHnRaPNubXQX3UxGBuLjwNXVmyECQQD2/D84tYoCGWoq | ||||
| 5FhUBxFUy2nnOLKYC/GGxBTX62iLfMQ3fbQcdg2pJsB5rrniyZf7UL+9FOsAO9k1 | ||||
| 8DO7G12DAkEA7Hkdg1KEw4ZfjnnjEa+KqpyLTLRQ91uTVW6kzR+4zY719iUJ/PXE | ||||
| PxJqm1ot7mJd1LW+bWtjLpxs7jYH19V+kQJBAIEpn2JnxdmdMuFlcy/WVmDy09pg | ||||
| 0z0imdexeXkFmjHAONkQOv3bWv+HzYaVMo8AgCOksfEPHGqN4eUMTfFeuUMCQF+5 | ||||
| E1JSd/2yCkJhYqKJHae8oMLXByNqRXTCyiFioutK4JPYIHfugJdLfC4QziD+Xp85 | ||||
| RrGCU+7NUWcIJhqfiJECQAIgUAzfzhdj5AyICaFPaOQ+N8FVMLcTyqeTXP0sIlFk | ||||
| JStVibemTRCbxdXXM7OVipz1oW3PBVEO3t/VyjiaGGg= | ||||
| -----END RSA PRIVATE KEY----- | ||||
							
								
								
									
										191
									
								
								example/sp/sp.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										191
									
								
								example/sp/sp.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import re | ||||
| from cgi import parse_qs | ||||
| from saml2 import BINDING_HTTP_REDIRECT | ||||
|  | ||||
| # ----------------------------------------------------------------------------- | ||||
| def dict_to_table(ava, lev=0, width=1): | ||||
|     txt = ['<table border=%s bordercolor="black">\n' % width] | ||||
|     for prop, valarr in ava.items(): | ||||
|         txt.append("<tr>\n") | ||||
|         if isinstance(valarr, basestring): | ||||
|             txt.append("<th>%s</th>\n" % str(prop)) | ||||
|             try: | ||||
|                 txt.append("<td>%s</td>\n" % valarr.encode("utf8")) | ||||
|             except AttributeError: | ||||
|                 txt.append("<td>%s</td>\n" % valarr) | ||||
|         elif isinstance(valarr, list): | ||||
|             i = 0 | ||||
|             n = len(valarr)        | ||||
|             for val in valarr: | ||||
|                 if not i: | ||||
|                     txt.append("<th rowspan=%d>%s</td>\n" % (len(valarr),prop)) | ||||
|                 else: | ||||
|                     txt.append("<tr>\n") | ||||
|                 if isinstance(val, dict): | ||||
|                     txt.append("<td>\n") | ||||
|                     txt.extend(dict_to_table(val, lev+1, width-1)) | ||||
|                     txt.append("</td>\n") | ||||
|                 else: | ||||
|                     try: | ||||
|                         txt.append("<td>%s</td>\n" % val.encode("utf8")) | ||||
|                     except AttributeError: | ||||
|                         txt.append("<td>%s</td>\n" % val) | ||||
|                 if n > 1: | ||||
|                     txt.append("</tr>\n") | ||||
|                 n -= 1 | ||||
|                 i += 1 | ||||
|         elif isinstance(valarr, dict): | ||||
|             txt.append("<th>%s</th>\n" % prop) | ||||
|             txt.append("<td>\n") | ||||
|             txt.extend(dict_to_table(valarr, lev+1, width-1)) | ||||
|             txt.append("</td>\n") | ||||
|         txt.append("</tr>\n") | ||||
|     txt.append('</table>\n') | ||||
|     return txt | ||||
|  | ||||
|  | ||||
| #noinspection PyUnusedLocal | ||||
| def whoami(environ, start_response, user, logger): | ||||
|     identity = environ["repoze.who.identity"]["user"] | ||||
|     if not identity: | ||||
|         return not_authn(environ, start_response) | ||||
|     response = ["<h2>Your identity are supposed to be</h2>"] | ||||
|     response.extend(dict_to_table(identity)) | ||||
|     response.extend("<a href='logout'>Logout</a>") | ||||
|     start_response('200 OK', [('Content-Type', 'text/html')]) | ||||
|     return response[:] | ||||
|      | ||||
| #noinspection PyUnusedLocal | ||||
| def not_found(environ, start_response): | ||||
|     """Called if no URL matches.""" | ||||
|     start_response('404 NOT FOUND', [('Content-Type', 'text/plain')]) | ||||
|     return ['Not Found'] | ||||
|  | ||||
| #noinspection PyUnusedLocal | ||||
| def not_authn(environ, start_response): | ||||
|     start_response('401 Unauthorized', [('Content-Type', 'text/plain')]) | ||||
|     return ['Unknown user'] | ||||
|  | ||||
| #noinspection PyUnusedLocal | ||||
| def slo(environ, start_response, user, logger): | ||||
|     # so here I might get either a LogoutResponse or a LogoutRequest | ||||
|     client = environ['repoze.who.plugins']["saml2auth"] | ||||
|     sids = None | ||||
|     if "QUERY_STRING" in environ: | ||||
|         query = parse_qs(environ["QUERY_STRING"]) | ||||
|         if logger: | ||||
|             logger.info("query: %s" % query) | ||||
|         try: | ||||
|             (sids, code, head, message) = client.saml_client.logout_response( | ||||
|                                                 query["SAMLResponse"][0], | ||||
|                                                 log=logger, | ||||
|                                                 binding=BINDING_HTTP_REDIRECT) | ||||
|             logger.info("LOGOUT reponse parsed OK") | ||||
|         except KeyError: | ||||
|             # return error reply | ||||
|             pass | ||||
|      | ||||
|     if not sids: | ||||
|         start_response("302 Found", [("Location", "/done")]) | ||||
|         return ["Successfull Logout"] | ||||
|      | ||||
| #noinspection PyUnusedLocal | ||||
| def logout(environ, start_response, user, logger): | ||||
|     client = environ['repoze.who.plugins']["saml2auth"] | ||||
|     subject_id = environ["repoze.who.identity"]['repoze.who.userid'] | ||||
|     logger.info("[logout] subject_id: '%s'" % (subject_id,)) | ||||
|     target = "/done" | ||||
|     # What if more than one | ||||
|     tmp = client.saml_client.global_logout(subject_id, log=logger,  | ||||
|                                             return_to=target) | ||||
|     logger.info("[logout] global_logout > %s" % (tmp,)) | ||||
|     (session_id, code, header, result) = tmp | ||||
|  | ||||
|     if session_id: | ||||
|         start_response(code, header) | ||||
|         return result | ||||
|     else: # All was done using SOAP | ||||
|         if result:  | ||||
|             start_response("302 Found", [("Location", target)]) | ||||
|             return ["Successfull Logout"] | ||||
|         else: | ||||
|             start_response("500 Internal Server Error") | ||||
|             return ["Failed to logout from identity services"] | ||||
|  | ||||
| #noinspection PyUnusedLocal | ||||
| def done(environ, start_response, user, logger): | ||||
|     # remove cookie and stored info | ||||
|     logger.info("[done] environ: %s" % environ) | ||||
|     subject_id = environ["repoze.who.identity"]['repoze.who.userid'] | ||||
|     client = environ['repoze.who.plugins']["saml2auth"] | ||||
|     logger.info("[logout done] remaining subjects: %s" % ( | ||||
|                                         client.saml_client.users.subjects(),)) | ||||
|  | ||||
|     start_response('200 OK', [('Content-Type', 'text/html')]) | ||||
|     return ["<h3>You are now logged out from this service</h3>"] | ||||
|          | ||||
| # ---------------------------------------------------------------------------- | ||||
|  | ||||
| # map urls to functions | ||||
| urls = [ | ||||
|     (r'whoami$', whoami), | ||||
|     (r'logout$', logout), | ||||
|     (r'done$', done), | ||||
|     (r'slo$', slo), | ||||
|     (r'^$', whoami), | ||||
| ] | ||||
|  | ||||
| # ---------------------------------------------------------------------------- | ||||
|  | ||||
| def application(environ, start_response): | ||||
|     """ | ||||
|     The main WSGI application. Dispatch the current request to | ||||
|     the functions from above and store the regular expression | ||||
|     captures in the WSGI environment as  `myapp.url_args` so that | ||||
|     the functions from above can access the url placeholders. | ||||
|  | ||||
|     If nothing matches call the `not_found` function. | ||||
|      | ||||
|     :param environ: The HTTP application environment | ||||
|     :param start_response: The application to run when the handling of the  | ||||
|         request is done | ||||
|     :return: The response as a list of lines | ||||
|     """ | ||||
|     user = environ.get("REMOTE_USER", "") | ||||
|     if not user: | ||||
|         user = environ.get("repoze.who.identity", "") | ||||
|              | ||||
|     path = environ.get('PATH_INFO', '').lstrip('/') | ||||
|     logger = environ.get('repoze.who.logger') | ||||
|     if logger: | ||||
|         logger.info( "<application> PATH: %s" % path) | ||||
|     for regex, callback in urls: | ||||
|         if user: | ||||
|             match = re.search(regex, path) | ||||
|             if match is not None: | ||||
|                 try: | ||||
|                     environ['myapp.url_args'] = match.groups()[0] | ||||
|                 except IndexError: | ||||
|                     environ['myapp.url_args'] = path | ||||
|                 return callback(environ, start_response, user, logger) | ||||
|         else: | ||||
|              return not_authn(environ, start_response) | ||||
|     return not_found(environ, start_response) | ||||
|  | ||||
| # ---------------------------------------------------------------------------- | ||||
|  | ||||
| from repoze.who.config import make_middleware_with_config | ||||
|  | ||||
| app_with_auth = make_middleware_with_config(application, {"here":"."},  | ||||
|                         './who.ini', log_file="sp.log") | ||||
|  | ||||
| # ---------------------------------------------------------------------------- | ||||
| PORT = 8087 | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     from wsgiref.simple_server import make_server | ||||
|     srv = make_server('localhost', PORT, app_with_auth) | ||||
|     print "SP listening on port: %s" % PORT | ||||
|     srv.serve_forever() | ||||
							
								
								
									
										45
									
								
								example/sp/sp_conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								example/sp/sp_conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| from saml2 import BINDING_HTTP_REDIRECT | ||||
| from saml2.saml import NAME_FORMAT_URI | ||||
|  | ||||
| BASE= "http://localhost:8087/" | ||||
|  | ||||
| CONFIG = { | ||||
|     "entityid" : "urn:mace:umu.se:saml:roland:sp", | ||||
|     "description": "My SP", | ||||
|     "service": { | ||||
|         "sp":{ | ||||
|             "name" : "Rolands SP", | ||||
|             "endpoints":{ | ||||
|                 "assertion_consumer_service": [BASE], | ||||
|                 "single_logout_service" : [(BASE+"slo", | ||||
|                                             BINDING_HTTP_REDIRECT)], | ||||
|             }, | ||||
|             "required_attributes": ["surname", "givenname", | ||||
|                                     "edupersonaffiliation"], | ||||
|             "optional_attributes": ["title"], | ||||
|             "idp": [ "urn:mace:umu.se:saml:roland:idp"], | ||||
|         } | ||||
|     }, | ||||
|     "debug" : 1, | ||||
|     "key_file" : "pki/mykey.pem", | ||||
|     "cert_file" : "pki/mycert.pem", | ||||
|     "attribute_map_dir" : "./attributemaps", | ||||
|     "metadata" : { | ||||
|        "local": ["../idp/idp.xml"], | ||||
|     }, | ||||
|     # -- below used by make_metadata -- | ||||
|     "organization": { | ||||
|         "name": "Exempel AB", | ||||
|         "display_name": [("Exempel AB","se"),("Example Co.","en")], | ||||
|         "url":"http://www.example.com/roland", | ||||
|     }, | ||||
|     "contact_person": [{ | ||||
|         "given_name":"John", | ||||
|         "sur_name": "Smith", | ||||
|         "email_address": ["john.smith@example.com"], | ||||
|         "contact_type": "technical", | ||||
|         }, | ||||
|     ], | ||||
|     #"xmlsec_binary":"/usr/local/bin/xmlsec1", | ||||
|     "name_form": NAME_FORMAT_URI | ||||
| } | ||||
							
								
								
									
										42
									
								
								example/sp/who.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								example/sp/who.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| [plugin:auth_tkt] | ||||
| # identification | ||||
| use = repoze.who.plugins.auth_tkt:make_plugin | ||||
| secret = kasamark | ||||
| cookie_name = pysaml2 | ||||
| secure = False | ||||
| include_ip = True | ||||
| timeout = 3600 | ||||
| reissue_time = 3000 | ||||
|  | ||||
| # IDENTIFIER | ||||
| # @param : | ||||
| # - rememberer_name : name of the plugin for remembering (delegate) | ||||
| [plugin:saml2auth] | ||||
| use = s2repoze.plugins.sp:make_plugin | ||||
| saml_conf = sp_conf | ||||
| rememberer_name = auth_tkt | ||||
| debug = 1 | ||||
| sid_store = outstanding | ||||
| identity_cache = identities | ||||
|  | ||||
| [general] | ||||
| request_classifier = s2repoze.plugins.challenge_decider:my_request_classifier | ||||
| challenge_decider = repoze.who.classifiers:default_challenge_decider | ||||
| remote_user_key = REMOTE_USER | ||||
|  | ||||
| [identifiers] | ||||
| # plugin_name;classifier_name:.. or just plugin_name (good for any) | ||||
| plugins = | ||||
|       saml2auth | ||||
|       auth_tkt | ||||
|        | ||||
| [authenticators] | ||||
| # plugin_name;classifier_name.. or just plugin_name (good for any) | ||||
| plugins = saml2auth | ||||
|  | ||||
| [challengers] | ||||
| # plugin_name;classifier_name:.. or just plugin_name (good for any) | ||||
| plugins = saml2auth | ||||
|  | ||||
| [mdproviders] | ||||
| plugins = saml2auth | ||||
							
								
								
									
										69
									
								
								release-howto.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								release-howto.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| Releasing software | ||||
| ------------------- | ||||
|  | ||||
| When releasing a new version, the following steps should be taken: | ||||
|  | ||||
| 1. Make sure all automated tests pass. | ||||
|  | ||||
| 2. Fill in the release date in ``CHANGES``. Make sure the changelog is | ||||
|    complete. Commit this change. | ||||
|  | ||||
| 3. Make sure the package metadata in ``setup.py`` is up-to-date. You can | ||||
|    verify the information by re-generating the egg info:: | ||||
|  | ||||
|      python setup.py  egg_info | ||||
|  | ||||
|    and inspecting ``src/pysaml2.egg-info/PKG-INFO``. You should also make sure | ||||
|    that the long description renders as valid reStructuredText. You can | ||||
|    do this by using the ``rst2html.py`` utility from docutils_:: | ||||
|  | ||||
|      python setup.py --long-description | rst2html > test.html | ||||
|  | ||||
|    If this will produce warning or errors, PyPI will be unable to render | ||||
|    the long description nicely. It will treat it as plain text instead. | ||||
|  | ||||
| 4. Update the version in the setup.py file and the doc conf.py file. Commit | ||||
|    these changes. | ||||
|  | ||||
| 5. Create a release tag:: | ||||
|  | ||||
|       bzr tag X.Y.Z | ||||
|  | ||||
| 6. Push these changes to Launchpad:: | ||||
|  | ||||
|       bzr push | ||||
|  | ||||
| 7. Create a source distribution and upload it to PyPI using the following | ||||
|    command:: | ||||
|  | ||||
|       python setup.py register sdist upload | ||||
|  | ||||
| 8. Upload the documentation to PyPI. First you need to generate the html | ||||
|    version of the documentation:: | ||||
|  | ||||
|       cd doc | ||||
|       make clean | ||||
|       make html | ||||
|       cd _build/html | ||||
|       zip -r pysaml2-docs.zip * | ||||
|  | ||||
|    now go to http://pypi.python.org/pypi?%3Aaction=pkg_edit&name=pysaml2 and | ||||
|    submit the pysaml2-docs.zip file in the form at the bottom of that page. | ||||
|  | ||||
| 9. Create a new release at Launchpad. If no milestone was created for this | ||||
|    release in the past, create it now at https://launchpad.net/pysaml2/main | ||||
|    Then create a release for that milestone. You can copy the section of | ||||
|    the CHANGES file that matches this release in the appropiate field of | ||||
|    the Launchpad form. Finally, add a download file for that release. | ||||
|  | ||||
| 10. Send an email to the pysaml2 list announcing this release | ||||
|  | ||||
|  | ||||
| **Important:** Once released to PyPI or any other public download location, | ||||
| a released egg may *never* be removed, even if it has proven to be a faulty | ||||
| release ("brown bag release"). In such a case it should simply be superseded | ||||
| immediately by a new, improved release. | ||||
|  | ||||
| .. _docutils: http://docutils.sourceforge.net/ | ||||
|  | ||||
| This document is based on http://svn.zope.org/*checkout*/Sandbox/philikon/foundation/releasing-software.txt | ||||
							
								
								
									
										2105
									
								
								runtests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2105
									
								
								runtests.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										93
									
								
								setup.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										93
									
								
								setup.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| #!/usr/bin/env python | ||||
| # | ||||
| # Copyright (C) 2007 SIOS Technology, Inc. | ||||
| # Copyright (C) 2011 Umea Universitet, Sweden | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| # | ||||
| import sys | ||||
|  | ||||
| from distutils.core import  Command | ||||
| from setuptools import setup | ||||
|  | ||||
|  | ||||
| class PyTest(Command): | ||||
|     user_options = [] | ||||
|     def initialize_options(self): | ||||
|         pass | ||||
|     def finalize_options(self): | ||||
|         pass | ||||
|     def run(self): | ||||
|         import sys, subprocess | ||||
|         errno = subprocess.call([sys.executable, 'runtests.py']) | ||||
|         raise SystemExit(errno) | ||||
|  | ||||
|  | ||||
| install_requires=[ | ||||
|     # core dependencies | ||||
|     'decorator', | ||||
|     'httplib2', | ||||
|     'paste', | ||||
|     'zope.interface', | ||||
|     'repoze.who == 1.0.18' | ||||
|     ] | ||||
|  | ||||
| # only for Python 2.6 | ||||
| if sys.version_info < (2,7): | ||||
|     install_requires.append('importlib') | ||||
|  | ||||
| setup( | ||||
|     name='pysaml2', | ||||
|     version='0.4.2', | ||||
|     description='Python implementation of SAML Version 2 to for instance be used in a WSGI environment', | ||||
| #    long_description = read("README"), | ||||
|     author='Roland Hedberg', | ||||
|     author_email='roland.hedberg@adm.umu.se', | ||||
|     license='Apache 2.0', | ||||
|     url='https://code.launchpad.net/~roland-hedberg/pysaml2/main', | ||||
|  | ||||
|     packages=['saml2', 'xmldsig', 'xmlenc', 's2repoze', 's2repoze.plugins', | ||||
|               "saml2/profile", "saml2/schema", "saml2/extension", | ||||
|               "saml2/attributemaps"], | ||||
|  | ||||
|     package_dir = {'':'src'}, | ||||
|     package_data={'': ['xml/*.xml']}, | ||||
|  | ||||
|     classifiers = ["Development Status :: 4 - Beta", | ||||
|         "License :: OSI Approved :: Apache Software License", | ||||
|         "Topic :: Software Development :: Libraries :: Python Modules"], | ||||
|  | ||||
|     scripts=["tools/parse_xsd2.py", "tools/make_metadata.py"], | ||||
|  | ||||
|     tests_require=[ | ||||
|         'pyasn1', | ||||
|         'pymongo', | ||||
|         'python-memcached', | ||||
|         'pytest', | ||||
|         #'pytest-coverage', | ||||
|         ], | ||||
|  | ||||
|     install_requires=install_requires, | ||||
|  | ||||
|     extras_require={ | ||||
|         'cjson': ['python-cjson'], | ||||
|         'pyasn1': ['pyasn1'], | ||||
|         'pymongo': ['pymongo'], | ||||
|         'python-memcached': ['python-memcached'] | ||||
|     }, | ||||
|  | ||||
|     zip_safe=False, | ||||
|  | ||||
|     cmdclass = {'test': PyTest}, | ||||
| ) | ||||
							
								
								
									
										3
									
								
								src/s2repoze/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/s2repoze/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| #  Created by Roland Hedberg  | ||||
| #  Copyright (c) 2009 Umeå Universitet. All rights reserved. | ||||
							
								
								
									
										3
									
								
								src/s2repoze/plugins/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/s2repoze/plugins/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| #  Created by Roland Hedberg | ||||
| #  Copyright (c) 2009 Umeå Universitet. All rights reserved. | ||||
							
								
								
									
										100
									
								
								src/s2repoze/plugins/challenge_decider.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/s2repoze/plugins/challenge_decider.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| from paste.request import construct_url | ||||
| import zope.interface | ||||
| from repoze.who.interfaces import IRequestClassifier | ||||
|  | ||||
| from paste.httpheaders import REQUEST_METHOD | ||||
| from paste.httpheaders import CONTENT_TYPE | ||||
| from paste.httpheaders import USER_AGENT | ||||
|  | ||||
| import re  | ||||
|  | ||||
| _DAV_METHODS = ( | ||||
|     'OPTIONS', | ||||
|     'PROPFIND', | ||||
|     'PROPPATCH', | ||||
|     'MKCOL', | ||||
|     'LOCK', | ||||
|     'UNLOCK', | ||||
|     'TRACE', | ||||
|     'DELETE', | ||||
|     'COPY', | ||||
|     'MOVE' | ||||
|     ) | ||||
|  | ||||
| _DAV_USERAGENTS = ( | ||||
|     'Microsoft Data Access Internet Publishing Provider', | ||||
|     'WebDrive', | ||||
|     'Zope External Editor', | ||||
|     'WebDAVFS', | ||||
|     'Goliath', | ||||
|     'neon', | ||||
|     'davlib', | ||||
|     'wsAPI', | ||||
|     'Microsoft-WebDAV' | ||||
|     ) | ||||
|  | ||||
| def my_request_classifier(environ): | ||||
|     """ Returns one of the classifiers 'dav', 'xmlpost', or 'browser', | ||||
|     depending on the imperative logic below""" | ||||
|     request_method = REQUEST_METHOD(environ) | ||||
|     if request_method in _DAV_METHODS: | ||||
|         return 'dav' | ||||
|     useragent = USER_AGENT(environ) | ||||
|     if useragent: | ||||
|         for agent in _DAV_USERAGENTS: | ||||
|             if useragent.find(agent) != -1: | ||||
|                 return 'dav' | ||||
|     if request_method == 'POST': | ||||
|         if CONTENT_TYPE(environ) == 'text/xml': | ||||
|             return 'xmlpost' | ||||
|         elif CONTENT_TYPE(environ) == "application/soap+xml": | ||||
|             return 'soap' | ||||
|     return 'browser' | ||||
|  | ||||
| zope.interface.directlyProvides(my_request_classifier, IRequestClassifier) | ||||
|  | ||||
| class MyChallengeDecider: | ||||
|     def __init__(self, path_login=""): | ||||
|         self.path_login = path_login | ||||
|     def __call__(self, environ, status, _headers): | ||||
|         if status.startswith('401 '): | ||||
|             return True | ||||
|         else: | ||||
|             # logout : need to "forget" => require a peculiar challenge | ||||
|             if environ.has_key('rwpc.logout'): | ||||
|                 return True | ||||
|  | ||||
|             # If the user is already authent, whatever happens(except logout),  | ||||
|             #   don't make a challenge | ||||
|             if environ.has_key('repoze.who.identity'):  | ||||
|                 return False | ||||
|  | ||||
|             uri = environ.get('REQUEST_URI', None) | ||||
|             if uri is None: | ||||
|                 uri = construct_url(environ) | ||||
|  | ||||
|             # require a challenge for login | ||||
|             for regex in self.path_login: | ||||
|                 if regex.match(uri) is not None: | ||||
|                     return True | ||||
|  | ||||
|         return False | ||||
|  | ||||
|  | ||||
|  | ||||
| def make_plugin(path_login = None): | ||||
|     if path_login is None: | ||||
|         raise ValueError( | ||||
|             'must include path_login in configuration') | ||||
|  | ||||
| # make regexp out of string passed via the config file | ||||
|     list_login = [] | ||||
|     for arg in path_login.splitlines(): | ||||
|         carg = arg.lstrip() | ||||
|         if carg != '': | ||||
|             list_login.append(re.compile(carg)) | ||||
|  | ||||
|     plugin = MyChallengeDecider(list_login) | ||||
|  | ||||
|     return plugin | ||||
|  | ||||
							
								
								
									
										77
									
								
								src/s2repoze/plugins/entitlement.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/s2repoze/plugins/entitlement.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| #!/usr/bin/env python | ||||
| import shelve | ||||
|  | ||||
| from zope.interface import implements | ||||
|  | ||||
| #from repoze.who.interfaces import IChallenger, IIdentifier, IAuthenticator | ||||
| from repoze.who.interfaces import IMetadataProvider | ||||
|  | ||||
| class EntitlementMetadataProvider(object): | ||||
|      | ||||
|     implements(IMetadataProvider) | ||||
|      | ||||
|     def __init__(self, filename, key_attribute): | ||||
|         # Means I have to do explicit syncs on writes, but also | ||||
|         # that it's faster on reads since it will cache data | ||||
|         self._store = shelve.open(filename, writeback=True) | ||||
|         self.key_attribute = key_attribute | ||||
|          | ||||
|     def keys(self): | ||||
|         return self._store.keys() | ||||
|          | ||||
|     def get(self, user, attribute): | ||||
|         return self._store[user][attribute] | ||||
|  | ||||
|     def set(self, user, attribute, value): | ||||
|         if user not in self._store: | ||||
|             self._store[user] = {} | ||||
|  | ||||
|         self._store[user][attribute] = value | ||||
|         self._store.sync() | ||||
|          | ||||
|     def part_of(self, user, virtualorg): | ||||
|         if virtualorg in self._store[user]["entitlement"]: | ||||
|             return True | ||||
|         else: | ||||
|             return False | ||||
|              | ||||
|     def get_entitlement(self, user, virtualorg): | ||||
|         try: | ||||
|             return self._store[user]["entitlement"][virtualorg] | ||||
|         except KeyError: | ||||
|             return [] | ||||
|              | ||||
|     def store_entitlement(self, user, virtualorg, entitlement=None): | ||||
|         if user not in self._store: | ||||
|             self._store[user] = {"entitlement":{}} | ||||
|         elif "entitlement" not in self._store[user]: | ||||
|             self._store[user]["entitlement"] = {} | ||||
|  | ||||
|         if entitlement is None: | ||||
|             entitlement = [] | ||||
|         self._store[user]["entitlement"][virtualorg] = entitlement | ||||
|         self._store.sync() | ||||
|              | ||||
|     def add_metadata(self, environ, identity): | ||||
|         #logger = environ.get('repoze.who.logger','') | ||||
|         try: | ||||
|             user = self._store[identity.get('repoze.who.userid')] | ||||
|         except KeyError: | ||||
|             return | ||||
|              | ||||
|         try: | ||||
|             vorg = environ["myapp.vo"] | ||||
|             try: | ||||
|                 ents = user["entitlement"][vorg] | ||||
|                 identity["user"] = { | ||||
|                             "entitlement": ["%s:%s" % (vorg,e) for e in ents]} | ||||
|             except KeyError: | ||||
|                 pass | ||||
|         except KeyError: | ||||
|             res = [] | ||||
|             for vorg, ents in user["entitlement"].items(): | ||||
|                 res.extend(["%s:%s" % (vorg, e) for e in ents]) | ||||
|             identity["user"] = res | ||||
|          | ||||
| def make_plugin(filename, key_attribute=""): | ||||
|     return EntitlementMetadataProvider(filename, key_attribute) | ||||
							
								
								
									
										163
									
								
								src/s2repoze/plugins/formswithhidden.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/s2repoze/plugins/formswithhidden.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| import urllib | ||||
|  | ||||
| from paste.httpheaders import CONTENT_LENGTH | ||||
| from paste.httpheaders import CONTENT_TYPE | ||||
| from paste.httpheaders import LOCATION | ||||
| from paste.httpexceptions import HTTPFound | ||||
|  | ||||
| from paste.request import parse_dict_querystring | ||||
| from paste.request import parse_formvars | ||||
| from paste.request import construct_url | ||||
|  | ||||
| from zope.interface import implements | ||||
|  | ||||
| from repoze.who.interfaces import IChallenger | ||||
| from repoze.who.interfaces import IIdentifier | ||||
| from repoze.who.plugins.form import FormPlugin | ||||
|  | ||||
| _DEFAULT_FORM = """ | ||||
| <html> | ||||
| <head> | ||||
|   <title>Demo Organization Log In</title> | ||||
| </head> | ||||
| <body> | ||||
|     <div> | ||||
|         <b>Demo Organization Log In</b> | ||||
|     </div> | ||||
|     <br/> | ||||
|     <form method="POST" action="?__do_login=true"> | ||||
|         <table width="350" border="0" cellspacing="0" cellpadding="1"> | ||||
|             <tr> | ||||
|                 <td bgcolor="#999999"> | ||||
|                     <table width="350" border="0"  | ||||
|                         cellpadding="3" cellspacing="0" bgcolor="#e6e6e6"> | ||||
|                         <tr> | ||||
|                             <td colspan="2"> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                         <tr> | ||||
|                             <td width="85"> | ||||
|                                 <font color="#CC3300" > | ||||
|                                     <strong> | ||||
|                                          Användarnamn/Username:  | ||||
|                                     </strong> | ||||
|                                 </font> | ||||
|                             </td> | ||||
|                             <td width="295"> | ||||
|                                 <input type="text" name="login"> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                         <tr> | ||||
|                             <td width="85"> | ||||
|                                 <font color="#CC3300"> | ||||
|                                     <strong> | ||||
|                                          Lösenord/Password: | ||||
|                                     </strong> | ||||
|                                 </font> | ||||
|                             </td> | ||||
|                             <td width="295"> | ||||
|                                 <input type="password" name="password"> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                         <tr> | ||||
|                             <td colspan="2">  | ||||
|                                 <input name="submit" type="submit" value="Logga in"> | ||||
|                             </td> | ||||
|                         </tr> | ||||
|                     </table> | ||||
|                 </td> | ||||
| 		 	</tr> | ||||
| 	 	</table> | ||||
|     %s | ||||
|   </form> | ||||
|   <pre> | ||||
|   </pre> | ||||
| </body> | ||||
| </html> | ||||
| """ | ||||
|  | ||||
| HIDDEN_PRE_LINE = """<input type=hidden name="%s" value="%s">""" | ||||
|  | ||||
| class FormHiddenPlugin(FormPlugin): | ||||
|  | ||||
|     implements(IChallenger, IIdentifier) | ||||
|      | ||||
|     # IIdentifier | ||||
|     def identify(self, environ): | ||||
|         logger = environ.get('repoze.who.logger','') | ||||
|         logger and logger.info("formplugin identify") | ||||
|         #logger and logger.info("environ keys: %s" % environ.keys()) | ||||
|         query = parse_dict_querystring(environ) | ||||
|         # If the extractor finds a special query string on any request, | ||||
|         # it will attempt to find the values in the input body. | ||||
|         if query.get(self.login_form_qs):  | ||||
|             form = parse_formvars(environ) | ||||
|             from StringIO import StringIO | ||||
|             # we need to replace wsgi.input because we've read it | ||||
|             # this smells funny | ||||
|             environ['wsgi.input'] = StringIO() | ||||
|             form.update(query) | ||||
|             qinfo = {} | ||||
|             for key, val in form.items(): | ||||
|                 if key.startswith("_") and key.endswith("_"): | ||||
|                     qinfo[key[1:-1]] = val | ||||
|             if qinfo: | ||||
|                 environ["s2repoze.qinfo"] = qinfo | ||||
|             try: | ||||
|                 login = form['login'] | ||||
|                 password = form['password'] | ||||
|             except KeyError: | ||||
|                 return None | ||||
|             del query[self.login_form_qs] | ||||
|             query.update(qinfo) | ||||
|             environ['QUERY_STRING'] = urllib.urlencode(query) | ||||
|             environ['repoze.who.application'] = HTTPFound( | ||||
|                                                     construct_url(environ)) | ||||
|             credentials = {'login':login, 'password':password} | ||||
|             max_age = form.get('max_age', None) | ||||
|             if max_age is not None: | ||||
|                 credentials['max_age'] = max_age | ||||
|             return credentials | ||||
|  | ||||
|         return None | ||||
|  | ||||
|     # IChallenger | ||||
|     def challenge(self, environ, status, app_headers, forget_headers): | ||||
|         logger = environ.get('repoze.who.logger','') | ||||
|         logger and logger.info("formplugin challenge") | ||||
|         if app_headers: | ||||
|             location = LOCATION(app_headers) | ||||
|             if location: | ||||
|                 headers = list(app_headers) + list(forget_headers) | ||||
|                 return HTTPFound(headers = headers) | ||||
|                  | ||||
|         query = parse_dict_querystring(environ) | ||||
|         hidden = [] | ||||
|         for key, val in query.items(): | ||||
|             hidden.append(HIDDEN_PRE_LINE % ("_%s_" % key, val)) | ||||
|  | ||||
|         logger and logger.info("hidden: %s" % (hidden,)) | ||||
|         form = self.formbody or _DEFAULT_FORM | ||||
|         form = form % "\n".join(hidden) | ||||
|              | ||||
|         if self.formcallable is not None: | ||||
|             form = self.formcallable(environ) | ||||
|         def auth_form(environ, start_response): | ||||
|             content_length = CONTENT_LENGTH.tuples(str(len(form))) | ||||
|             content_type = CONTENT_TYPE.tuples('text/html') | ||||
|             headers = content_length + content_type + forget_headers | ||||
|             start_response('200 OK', headers) | ||||
|             return [form] | ||||
|  | ||||
|         return auth_form | ||||
|  | ||||
|  | ||||
| def make_plugin(login_form_qs='__do_login', rememberer_name=None, form=None): | ||||
|     if rememberer_name is None: | ||||
|         raise ValueError( | ||||
|             'must include rememberer key (name of another IIdentifier plugin)') | ||||
|     if form is not None: | ||||
|         form = open(form).read() | ||||
|     plugin = FormHiddenPlugin(login_form_qs, rememberer_name, form) | ||||
|     return plugin | ||||
|  | ||||
							
								
								
									
										35
									
								
								src/s2repoze/plugins/ini.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/s2repoze/plugins/ini.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import ConfigParser | ||||
|  | ||||
| from zope.interface import implements | ||||
|  | ||||
| #from repoze.who.interfaces import IChallenger, IIdentifier, IAuthenticator | ||||
| from repoze.who.interfaces import IMetadataProvider | ||||
|  | ||||
| class INIMetadataProvider(object): | ||||
|      | ||||
|     implements(IMetadataProvider) | ||||
|      | ||||
|     def __init__(self, ini_file, key_attribute): | ||||
|  | ||||
|         self.users = ConfigParser.ConfigParser() | ||||
|         self.users.readfp(open(ini_file)) | ||||
|         self.key_attribute = key_attribute | ||||
|          | ||||
|     def add_metadata(self, _environ, identity): | ||||
|         #logger = environ.get('repoze.who.logger','') | ||||
|  | ||||
|         key = identity.get('repoze.who.userid') | ||||
|         try: | ||||
|             if self.key_attribute: | ||||
|                 for sec in self.users.sections(): | ||||
|                     if self.users.has_option(sec, self.key_attribute): | ||||
|                         if key in self.users.get(sec, self.key_attribute): | ||||
|                             identity["user"] = dict(self.users.items(sec)) | ||||
|                             break | ||||
|             else: | ||||
|                 identity["user"] = dict(self.users.items(key)) | ||||
|         except ValueError: | ||||
|             pass | ||||
|          | ||||
| def make_plugin(ini_file, key_attribute=""): | ||||
|     return INIMetadataProvider(ini_file, key_attribute) | ||||
							
								
								
									
										569
									
								
								src/s2repoze/plugins/sp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										569
									
								
								src/s2repoze/plugins/sp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,569 @@ | ||||
| # Copyright (C) 2009 Umea University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """  | ||||
| A plugin that allows you to use SAML2 SSO as authentication  | ||||
| and SAML2 attribute aggregations as metadata collector in your | ||||
| WSGI application. | ||||
|  | ||||
| """ | ||||
| import cgi | ||||
| import sys | ||||
| import platform | ||||
| import shelve | ||||
| import traceback | ||||
| from urlparse import parse_qs | ||||
|  | ||||
| from paste.httpexceptions import HTTPSeeOther | ||||
| from paste.httpexceptions import HTTPNotImplemented | ||||
| from paste.httpexceptions import HTTPInternalServerError | ||||
| from paste.request import parse_dict_querystring | ||||
| from paste.request import construct_url | ||||
| from zope.interface import implements | ||||
|  | ||||
| from repoze.who.interfaces import IChallenger, IIdentifier, IAuthenticator | ||||
| from repoze.who.interfaces import IMetadataProvider | ||||
| from repoze.who.plugins.form import FormPluginBase | ||||
|  | ||||
| from saml2 import ecp | ||||
|  | ||||
| from saml2.client import Saml2Client | ||||
| from saml2.s_utils import sid | ||||
| from saml2.config import config_factory | ||||
| from saml2.profile import paos | ||||
|  | ||||
| #from saml2.population import Population | ||||
| #from saml2.attribute_resolver import AttributeResolver | ||||
|  | ||||
| PAOS_HEADER_INFO = 'ver="%s";"%s"' % (paos.NAMESPACE, ecp.SERVICE) | ||||
|  | ||||
| def construct_came_from(environ): | ||||
|     """ The URL that the user used when the process where interupted  | ||||
|     for single-sign-on processing. """ | ||||
|      | ||||
|     came_from = environ.get("PATH_INFO")  | ||||
|     qstr = environ.get("QUERY_STRING","") | ||||
|     if qstr: | ||||
|         came_from += '?' + qstr | ||||
|     return came_from | ||||
|      | ||||
| # FormPluginBase defines the methods remember and forget | ||||
| def cgi_field_storage_to_dict(field_storage): | ||||
|     """Get a plain dictionary, rather than the '.value' system used by the | ||||
|     cgi module.""" | ||||
|      | ||||
|     params = {} | ||||
|     for key in field_storage.keys(): | ||||
|         try: | ||||
|             params[ key ] = field_storage[ key ].value | ||||
|         except AttributeError: | ||||
|             if isinstance(field_storage[ key ], basestring): | ||||
|                 params[key] = field_storage[key] | ||||
|                  | ||||
|     return params | ||||
|  | ||||
| def get_body(environ, log=None): | ||||
|     body = "" | ||||
|  | ||||
|     length = int(environ["CONTENT_LENGTH"]) | ||||
|     try: | ||||
|         body = environ["wsgi.input"].read(length) | ||||
|     except Exception, excp: | ||||
|         if log: | ||||
|             log.info("Exception while reading post: %s" % (excp,)) | ||||
|         raise | ||||
|  | ||||
|     # restore what I might have upset | ||||
|     from StringIO import StringIO | ||||
|     environ['wsgi.input'] = StringIO(body) | ||||
|     environ['s2repoze.body'] = body | ||||
|  | ||||
|     return body | ||||
|  | ||||
| def exception_trace(tag, exc, log): | ||||
|     message = traceback.format_exception(*sys.exc_info()) | ||||
|     log.error("[%s] ExcList: %s" % (tag, "".join(message),)) | ||||
|     log.error("[%s] Exception: %s" % (tag, exc)) | ||||
|  | ||||
| class ECP_response(object): | ||||
|     code = 200 | ||||
|     title = 'OK' | ||||
|  | ||||
|     def __init__(self, content): | ||||
|         self.content = content | ||||
|  | ||||
|     #noinspection PyUnusedLocal | ||||
|     def __call__(self, environ, start_response): | ||||
|         start_response('%s %s' % (self.code, self.title), | ||||
|                        [('Content-Type', "text/xml")]) | ||||
|         return [self.content] | ||||
|  | ||||
| class SAML2Plugin(FormPluginBase): | ||||
|  | ||||
|     implements(IChallenger, IIdentifier, IAuthenticator, IMetadataProvider) | ||||
|      | ||||
|     def __init__(self, rememberer_name, config, saml_client,  | ||||
|                     wayf, cache, debug, sid_store=None, discovery=""): | ||||
|         FormPluginBase.__init__(self) | ||||
|          | ||||
|         self.rememberer_name = rememberer_name | ||||
|         self.debug = debug         | ||||
|         self.wayf = wayf | ||||
|         self.saml_client = saml_client | ||||
|         self.discovery = discovery | ||||
|         self.conf = config | ||||
|         self.log = None | ||||
|         self.cache = cache | ||||
|                      | ||||
|         try: | ||||
|             self.metadata = self.conf.metadata | ||||
|         except KeyError: | ||||
|             self.metadata = None | ||||
|         if sid_store: | ||||
|             self.outstanding_queries = shelve.open(sid_store, writeback=True) | ||||
|         else: | ||||
|             self.outstanding_queries = {} | ||||
|         self.iam = platform.node() | ||||
|  | ||||
|     def _get_post(self, environ): | ||||
|         """ | ||||
|         Get the posted information | ||||
|      | ||||
|         :param environ: A dictionary with environment variables | ||||
|         """ | ||||
|      | ||||
|         post = {} | ||||
|      | ||||
|         post_env = environ.copy() | ||||
|         post_env['QUERY_STRING'] = '' | ||||
|      | ||||
|         _ = get_body(environ, self.log) | ||||
|          | ||||
|         try: | ||||
|             post = cgi.FieldStorage( | ||||
|                 fp=environ['wsgi.input'], | ||||
|                 environ=post_env, | ||||
|                 keep_blank_values=True | ||||
|             ) | ||||
|         except Exception, excp: | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("Exception (II): %s" % (excp,)) | ||||
|                 raise | ||||
|      | ||||
|         if self.debug and self.log: | ||||
|             self.log.info('identify post: %s' % (post,)) | ||||
|      | ||||
|         return post | ||||
|  | ||||
|     def _wayf_redirect(self, came_from): | ||||
|         sid_ = sid() | ||||
|         self.outstanding_queries[sid_] = came_from | ||||
|         self.log.info("Redirect to WAYF function: %s" % self.wayf) | ||||
|         return -1, HTTPSeeOther(headers = [('Location', | ||||
|                                     "%s?%s" % (self.wayf, sid_))]) | ||||
|  | ||||
|     #noinspection PyUnusedLocal | ||||
|     def _pick_idp(self, environ, came_from): | ||||
|         """  | ||||
|         If more than one idp and if none is selected, I have to do wayf or  | ||||
|         disco | ||||
|         """ | ||||
|  | ||||
|         # check headers to see if it's an ECP request | ||||
| #        headers = { | ||||
| #                    'Accept' : 'text/html; application/vnd.paos+xml', | ||||
| #                    'PAOS'   : 'ver="%s";"%s"' % (paos.NAMESPACE, SERVICE) | ||||
| #                    } | ||||
|  | ||||
|         self.log.info("[_pick_idp] %s" % environ) | ||||
|         if "HTTP_PAOS" in environ: | ||||
|             if environ["HTTP_PAOS"] == PAOS_HEADER_INFO: | ||||
|                 if 'application/vnd.paos+xml' in environ["HTTP_ACCEPT"]: | ||||
|                     # Where should I redirect the user to | ||||
|                     # entityid -> the IdP to use | ||||
|                     # relay_state -> when back from authentication | ||||
|  | ||||
|                     self.log.info("- ECP client detected -") | ||||
|  | ||||
|                     _relay_state = construct_came_from(environ) | ||||
|                     _entityid = self.saml_client.config.ecp_endpoint( | ||||
|                                                     environ["REMOTE_ADDR"]) | ||||
|                     if not _entityid: | ||||
|                         return -1, HTTPInternalServerError( | ||||
|                                         detail="No IdP to talk to" | ||||
|                         ) | ||||
|                     self.log.info("IdP to talk to: %s" % _entityid) | ||||
|                     return ecp.ecp_auth_request(self.saml_client, _entityid, | ||||
|                                                 _relay_state, log=self.log) | ||||
|                 else: | ||||
|                     return -1, HTTPInternalServerError( | ||||
|                                     detail='Faulty Accept header') | ||||
|             else: | ||||
|                 return -1, HTTPInternalServerError( | ||||
|                                                 detail='unknown ECP version') | ||||
|  | ||||
|  | ||||
|         idps = self.conf.idps() | ||||
|          | ||||
|         if self.log: | ||||
|             self.log.info("IdP URL: %s" % idps) | ||||
|  | ||||
|         if len( idps ) == 1: | ||||
|             # idps is a dictionary | ||||
|             idp_entity_id = idps.keys()[0] | ||||
|         elif not len(idps): | ||||
|             return -1, HTTPInternalServerError(detail='Misconfiguration') | ||||
|         else: | ||||
|             idp_entity_id = "" | ||||
|             if self.log: | ||||
|                 self.log.info("ENVIRON: %s" % environ) | ||||
|             query = environ.get('s2repoze.body','') | ||||
|             if not query: | ||||
|                 query = environ.get("QUERY_STRING","") | ||||
|                  | ||||
|             if self.log: | ||||
|                 self.log.info("<_pick_idp> query: %s" % query) | ||||
|  | ||||
|             if self.wayf: | ||||
|                 if query: | ||||
|                     try: | ||||
|                         wayf_selected = dict(parse_qs(query))["wayf_selected"][0] | ||||
|                     except KeyError: | ||||
|                         return self._wayf_redirect(came_from) | ||||
|                     idp_entity_id = wayf_selected | ||||
|                 else: | ||||
|                     return self._wayf_redirect(came_from) | ||||
|             elif self.discovery: | ||||
|                 if query: | ||||
|                     idp_entity_id = self.saml_client.get_idp_from_discovery_service( | ||||
|                                             query=environ.get("QUERY_STRING")) | ||||
|                 else: | ||||
|                     sid_ = sid() | ||||
|                     self.outstanding_queries[sid_] = came_from | ||||
|                     self.log.info("Redirect to Discovery Service function") | ||||
|                     loc = self.saml_client.request_to_discovery_service( | ||||
|                                                                 self.discovery) | ||||
|                     return -1, HTTPSeeOther(headers = [('Location',loc)]) | ||||
|             else: | ||||
|                 return -1, HTTPNotImplemented(detail='No WAYF or DJ present!') | ||||
|  | ||||
|         self.log.info("Choosen IdP: '%s'" % idp_entity_id) | ||||
|         return 0, idp_entity_id | ||||
|          | ||||
|     #### IChallenger #### | ||||
|     #noinspection PyUnusedLocal | ||||
|     def challenge(self, environ, _status, _app_headers, _forget_headers): | ||||
|  | ||||
|         # this challenge consist in login out | ||||
|         if environ.has_key('rwpc.logout'):  | ||||
|             # ignore right now? | ||||
|             pass | ||||
|  | ||||
|         self.log = environ.get('repoze.who.logger','') | ||||
|         self.saml_client.log = self.log | ||||
|          | ||||
|         # Which page was accessed to get here | ||||
|         came_from = construct_came_from(environ) | ||||
|         environ["myapp.came_from"] = came_from | ||||
|         if self.debug and self.log: | ||||
|             self.log.info("[sp.challenge] RelayState >> %s" % came_from) | ||||
|          | ||||
|         # Am I part of a virtual organization ? | ||||
|         try: | ||||
|             vorg_name = environ["myapp.vo"] | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 vorg_name = self.saml_client.vorg.vorg_name | ||||
|             except AttributeError: | ||||
|                 vorg_name = "" | ||||
|              | ||||
|         if self.log: | ||||
|             self.log.info("[sp.challenge] VO: %s" % vorg_name) | ||||
|  | ||||
|         # If more than one idp and if none is selected, I have to do wayf | ||||
|         (done, response) = self._pick_idp(environ, came_from) | ||||
|         # Three cases: -1 something went wrong or Discovery service used | ||||
|         #               0 I've got an IdP to send a request to | ||||
|         #               >0 ECP in progress | ||||
|         if self.log: | ||||
|             self.log.debug("_idp_pick returned: %s" % done) | ||||
|         if done == -1: | ||||
|             return response | ||||
|         elif done > 0: | ||||
|             self.outstanding_queries[done] = came_from | ||||
|             return ECP_response(response) | ||||
|         else: | ||||
|             idp_url = response | ||||
|             if self.log: | ||||
|                 self.log.info("[sp.challenge] idp_url: %s" % idp_url) | ||||
|             # Do the AuthnRequest | ||||
|  | ||||
|             (sid_, result) = self.saml_client.authenticate(idp_url, | ||||
|                                                     relay_state=came_from, | ||||
|                                                     log=self.log, | ||||
|                                                     vorg=vorg_name) | ||||
|  | ||||
|             # remember the request | ||||
|             self.outstanding_queries[sid_] = came_from | ||||
|  | ||||
|             if isinstance(result, tuple): | ||||
|                 if self.debug and self.log: | ||||
|                     self.log.info('redirect to: %s' % result[1]) | ||||
|                 return HTTPSeeOther(headers=[result]) | ||||
|             else : | ||||
|                 return HTTPInternalServerError(detail='Incorrect returned data') | ||||
|  | ||||
|     def _construct_identity(self, session_info): | ||||
|         identity = { | ||||
|             "login": session_info["name_id"], | ||||
|             "password": "", | ||||
|             'repoze.who.userid': session_info["name_id"], | ||||
|             "user": session_info["ava"], | ||||
|         } | ||||
|         if self.debug and self.log: | ||||
|             self.log.info("Identity: %s" % identity) | ||||
|  | ||||
|         return identity | ||||
|          | ||||
|     def _eval_authn_response(self, environ, post): | ||||
|         if self.log: | ||||
|             self.log.info("Got AuthN response, checking..") | ||||
|             self.log.info("Outstanding: %s" % (self.outstanding_queries,)) | ||||
|  | ||||
|         try: | ||||
|             # Evaluate the response, returns a AuthnResponse instance | ||||
|             try: | ||||
|                 authresp = self.saml_client.response(post,  | ||||
|                                                     self.outstanding_queries, | ||||
|                                                     self.log) | ||||
|             except Exception, excp: | ||||
|                 if self.log: | ||||
|                     self.log.error("Exception: %s" % (excp,)) | ||||
|                 raise | ||||
|                  | ||||
|             session_info = authresp.session_info() | ||||
|         except TypeError, excp: | ||||
|             if self.log: | ||||
|                 self.log.error("Exception: %s" % (excp,)) | ||||
|             return None | ||||
|                                          | ||||
|         if session_info["came_from"]: | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("came_from << %s" % session_info["came_from"]) | ||||
|             try: | ||||
|                 path, query = session_info["came_from"].split('?') | ||||
|                 environ["PATH_INFO"] = path | ||||
|                 environ["QUERY_STRING"] = query | ||||
|             except ValueError: | ||||
|                 environ["PATH_INFO"] = session_info["came_from"] | ||||
|  | ||||
|         if self.log: | ||||
|             self.log.info("Session_info: %s" % session_info) | ||||
|         return session_info | ||||
|  | ||||
|     def do_ecp_response(self, body, environ): | ||||
|         response, _relay_state = ecp.handle_ecp_authn_response(self.saml_client, | ||||
|                                                                body) | ||||
|  | ||||
|         environ["s2repoze.relay_state"] = _relay_state.text | ||||
|         session_info = response.session_info() | ||||
|         if self.log: | ||||
|             self.log.info("Session_info: %s" % session_info) | ||||
|  | ||||
|         return session_info | ||||
|  | ||||
|     #### IIdentifier #### | ||||
|     def identify(self, environ): | ||||
|         """ | ||||
|         Tries do the identification  | ||||
|         """ | ||||
|         self.log = environ.get('repoze.who.logger', '') | ||||
|         self.saml_client.log = self.log | ||||
|          | ||||
|         if "CONTENT_LENGTH" not in environ or not environ["CONTENT_LENGTH"]: | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info('[identify] get or empty post') | ||||
|             return {} | ||||
|          | ||||
|         # if self.log: | ||||
|         #     self.log.info("ENVIRON: %s" % environ) | ||||
|         #     self.log.info("self: %s" % (self.__dict__,)) | ||||
|          | ||||
|         uri = environ.get('REQUEST_URI', construct_url(environ)) | ||||
|          | ||||
|         if self.debug: | ||||
|             #if self.log: self.log.info("environ.keys(): %s" % environ.keys()) | ||||
|             #if self.log: self.log.info("Environment: %s" % environ) | ||||
|             if self.log: | ||||
|                 self.log.info('[sp.identify] uri: %s' % (uri,)) | ||||
|  | ||||
|         query = parse_dict_querystring(environ) | ||||
|         if self.debug and self.log: | ||||
|             self.log.info('[sp.identify] query: %s' % (query,)) | ||||
|          | ||||
|         post = self._get_post(environ) | ||||
|  | ||||
|         if self.debug and self.log: | ||||
|             try: | ||||
|                 self.log.info('[sp.identify] post keys: %s' % (post.keys(),)) | ||||
|             except (TypeError, IndexError): | ||||
|                 pass | ||||
|              | ||||
|         try: | ||||
|             if not post.has_key("SAMLResponse"): | ||||
|                 self.log.info("[sp.identify] --- NOT SAMLResponse ---") | ||||
|                 # Not for me, put the post back where next in line can | ||||
|                 # find it | ||||
|                 environ["post.fieldstorage"] = post | ||||
|                 return {} | ||||
|             else: | ||||
|                 self.log.info("[sp.identify] --- SAMLResponse ---") | ||||
|                 # check for SAML2 authN response | ||||
|                 #if self.debug: | ||||
|                 try: | ||||
|                     session_info = self._eval_authn_response(environ, | ||||
|                                                 cgi_field_storage_to_dict(post)) | ||||
|                 except Exception: | ||||
|                     return None | ||||
|         except TypeError, exc: | ||||
|             # might be a ECP (=SOAP) response | ||||
|             body = environ.get('s2repoze.body', None) | ||||
|             if body: | ||||
|                 # might be a ECP response | ||||
|                 try: | ||||
|                     session_info = self.do_ecp_response(body, environ) | ||||
|                 except Exception: | ||||
|                     environ["post.fieldstorage"] = post | ||||
|                     return {} | ||||
|             else: | ||||
|                 exception_trace("sp.identity", exc, self.log) | ||||
|                 environ["post.fieldstorage"] = post | ||||
|                 return {} | ||||
|              | ||||
|         if session_info:         | ||||
|             environ["s2repoze.sessioninfo"] = session_info | ||||
|             name_id = session_info["name_id"] | ||||
|             # contruct and return the identity | ||||
|             identity = { | ||||
|                 "login": name_id, | ||||
|                 "password": "", | ||||
|                 'repoze.who.userid': name_id, | ||||
|                 "user": self.saml_client.users.get_identity(name_id)[0], | ||||
|             } | ||||
|             self.log.info("[sp.identify] IDENTITY: %s" % (identity,)) | ||||
|             return identity | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|                      | ||||
|     # IMetadataProvider | ||||
|     def add_metadata(self, environ, identity): | ||||
|         """ Add information to the knowledge I have about the user """ | ||||
|         subject_id = identity['repoze.who.userid'] | ||||
|  | ||||
|         self.log = environ.get('repoze.who.logger','') | ||||
|         self.saml_client.log = self.log | ||||
|  | ||||
|         if self.debug and self.log: | ||||
|             self.log.info( | ||||
|                 "[add_metadata] for %s" % subject_id) | ||||
|             try: | ||||
|                 self.log.info( | ||||
|                     "Issuers: %s" % self.saml_client.users.sources(subject_id)) | ||||
|             except KeyError: | ||||
|                 pass | ||||
|              | ||||
|         if "user" not in identity: | ||||
|             identity["user"] = {} | ||||
|         try: | ||||
|             (ava, _) = self.saml_client.users.get_identity(subject_id) | ||||
|             #now = time.gmtime()         | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("[add_metadata] adds: %s" % ava) | ||||
|             identity["user"].update(ava) | ||||
|         except KeyError: | ||||
|             pass | ||||
|  | ||||
|         if "pysaml2_vo_expanded" not in identity: | ||||
|             # is this a Virtual Organization situation | ||||
|             if self.saml_client.vorg: | ||||
|                 try: | ||||
|                     if self.saml_client.vorg.do_aggregation(subject_id,  | ||||
|                                                             log=self.log): | ||||
|                         # Get the extended identity | ||||
|                         identity["user"] = self.saml_client.users.get_identity( | ||||
|                                                                 subject_id)[0] | ||||
|                         # Only do this once, mark that the identity has been  | ||||
|                         # expanded | ||||
|                         identity["pysaml2_vo_expanded"] = 1 | ||||
|                 except KeyError: | ||||
|                     if self.log: | ||||
|                         self.log.error("Failed to do attribute aggregation, " | ||||
|                                         "missing common attribute") | ||||
|         if self.debug and self.log: | ||||
|             self.log.info("[add_metadata] returns: %s" % (dict(identity),)) | ||||
|  | ||||
|         if not identity["user"]: | ||||
|             # remove cookie and demand re-authentication | ||||
|             pass | ||||
|          | ||||
| # @return | ||||
| # used 2 times : one to get the ticket, the other to validate it | ||||
|     def _service_url(self, environ, qstr=None): | ||||
|         if qstr is not None: | ||||
|             url = construct_url(environ, querystring = qstr) | ||||
|         else: | ||||
|             url = construct_url(environ) | ||||
|         return url | ||||
|  | ||||
|     #### IAuthenticatorPlugin ####  | ||||
|     #noinspection PyUnusedLocal | ||||
|     def authenticate(self, environ, identity=None): | ||||
|         if identity: | ||||
|             return identity.get('login', None) | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|  | ||||
| def make_plugin(rememberer_name=None, # plugin for remember | ||||
|                  cache= "", # cache | ||||
|                  # Which virtual organization to support | ||||
|                  virtual_organization="",  | ||||
|                  saml_conf="", | ||||
|                  wayf="", | ||||
|                  debug=0, | ||||
|                  sid_store="", | ||||
|                  identity_cache="", | ||||
|                  discovery="" | ||||
|                  ): | ||||
|      | ||||
|     if saml_conf is "": | ||||
|         raise ValueError( | ||||
|             'must include saml_conf in configuration') | ||||
|  | ||||
|     if rememberer_name is None: | ||||
|         raise ValueError( | ||||
|              'must include rememberer_name in configuration') | ||||
|  | ||||
|     conf = config_factory("sp", saml_conf) | ||||
|  | ||||
|     scl = Saml2Client(config=conf, identity_cache=identity_cache, | ||||
|                         virtual_organization=virtual_organization) | ||||
|  | ||||
|     plugin = SAML2Plugin(rememberer_name, conf, scl, wayf, cache, debug, | ||||
|                         sid_store, discovery) | ||||
|     return plugin | ||||
|  | ||||
| # came_from = re.sub(r'ticket=[^&]*&?', '', came_from) | ||||
|  | ||||
							
								
								
									
										866
									
								
								src/saml2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										866
									
								
								src/saml2/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,866 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2006 Google Inc. | ||||
| # Copyright (C) 2009 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """Contains base classes representing SAML elements. | ||||
|  | ||||
|     These codes were originally written by Jeffrey Scudder for | ||||
|     representing Saml elements. Takashi Matsuo had added some codes, and | ||||
|     changed some. Roland Hedberg rewrote the whole thing from bottom up so | ||||
|     barely anything but the original structures remained. | ||||
|  | ||||
|     Module objective: provide data classes for SAML constructs. These | ||||
|     classes hide the XML-ness of SAML and provide a set of native Python | ||||
|     classes to interact with. | ||||
|  | ||||
|     Conversions to and from XML should only be necessary when the SAML classes | ||||
|     "touch the wire" and are sent over HTTP. For this reason this module  | ||||
|     provides methods and functions to convert SAML classes to and from strings. | ||||
| """ | ||||
|  | ||||
| # try: | ||||
| #     # lxml: best performance for XML processing | ||||
| #     import lxml.etree as ET | ||||
| # except ImportError: | ||||
| #     try: | ||||
| #         # Python 2.5+: batteries included | ||||
| #         import xml.etree.cElementTree as ET | ||||
| #     except ImportError: | ||||
| #         try: | ||||
| #             # Python <2.5: standalone ElementTree install | ||||
| #             import elementtree.cElementTree as ET | ||||
| #         except ImportError: | ||||
| #             raise ImportError, "lxml or ElementTree are not installed, "\ | ||||
| #                 +"see http://codespeak.net/lxml "\ | ||||
| #                 +"or http://effbot.org/zone/element-index.htm" | ||||
|  | ||||
| import logging | ||||
|  | ||||
| try: | ||||
|     from xml.etree import cElementTree as ElementTree | ||||
|     if ElementTree.VERSION < '1.3.0': | ||||
|         # cElementTree has no support for register_namespace | ||||
|         # neither _namespace_map, thus we sacrify performance | ||||
|         # for correctness | ||||
|         from xml.etree import ElementTree | ||||
| except ImportError: | ||||
|     try: | ||||
|         import cElementTree as ElementTree | ||||
|     except ImportError: | ||||
|         from elementtree import ElementTree | ||||
|  | ||||
| root_logger = logging.getLogger("pySAML2") | ||||
| root_logger.level = logging.NOTSET | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:assertion' | ||||
| #TEMPLATE = '{urn:oasis:names:tc:SAML:2.0:assertion}%s' | ||||
| #XSI_NAMESPACE = 'http://www.w3.org/2001/XMLSchema-instance' | ||||
|  | ||||
| NAMEID_FORMAT_EMAILADDRESS = ( | ||||
|     "urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress") | ||||
|  | ||||
| # These are defined in saml2.saml | ||||
| #NAME_FORMAT_UNSPECIFIED = ( | ||||
| #    "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified") | ||||
| #NAME_FORMAT_URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" | ||||
| #NAME_FORMAT_BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" | ||||
|  | ||||
| SUBJECT_CONFIRMATION_METHOD_BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer" | ||||
|  | ||||
| DECISION_TYPE_PERMIT = "Permit" | ||||
| DECISION_TYPE_DENY = "Deny" | ||||
| DECISION_TYPE_INDETERMINATE = "Indeterminate" | ||||
|  | ||||
| VERSION = "2.0" | ||||
|  | ||||
| BINDING_SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP' | ||||
| BINDING_PAOS = 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS' | ||||
| BINDING_HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' | ||||
| BINDING_HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' | ||||
| BINDING_HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact' | ||||
| BINDING_URI = 'urn:oasis:names:tc:SAML:2.0:bindings:URI' | ||||
|  | ||||
| def class_name(instance): | ||||
|     return "%s:%s" % (instance.c_namespace, instance.c_tag) | ||||
|  | ||||
| def create_class_from_xml_string(target_class, xml_string): | ||||
|     """Creates an instance of the target class from a string. | ||||
|      | ||||
|     :param target_class: The class which will be instantiated and populated | ||||
|         with the contents of the XML. This class must have a c_tag and a | ||||
|         c_namespace class variable. | ||||
|     :param xml_string: A string which contains valid XML. The root element | ||||
|         of the XML string should match the tag and namespace of the desired | ||||
|         class. | ||||
|  | ||||
|     :return: An instance of the target class with members assigned according to | ||||
|         the contents of the XML - or None if the root XML tag and namespace did  | ||||
|         not match those of the target class. | ||||
|     """ | ||||
|     tree = ElementTree.fromstring(xml_string) | ||||
|     return create_class_from_element_tree(target_class, tree) | ||||
|  | ||||
|  | ||||
| def create_class_from_element_tree(target_class, tree, namespace=None,  | ||||
|                                     tag=None): | ||||
|     """Instantiates the class and populates members according to the tree. | ||||
|  | ||||
|     Note: Only use this function with classes that have c_namespace and c_tag | ||||
|     class members. | ||||
|  | ||||
|     :param target_class: The class which will be instantiated and populated | ||||
|         with the contents of the XML. | ||||
|     :param tree: An element tree whose contents will be converted into | ||||
|         members of the new target_class instance. | ||||
|     :param namespace: The namespace which the XML tree's root node must | ||||
|         match. If omitted, the namespace defaults to the c_namespace of the  | ||||
|         target class. | ||||
|     :param tag: The tag which the XML tree's root node must match. If | ||||
|         omitted, the tag defaults to the c_tag class member of the target  | ||||
|         class. | ||||
|  | ||||
|     :return: An instance of the target class - or None if the tag and namespace | ||||
|         of the XML tree's root node did not match the desired namespace and tag. | ||||
|     """ | ||||
|     if namespace is None: | ||||
|         namespace = target_class.c_namespace | ||||
|     if tag is None: | ||||
|         tag = target_class.c_tag | ||||
|     if tree.tag == '{%s}%s' % (namespace, tag): | ||||
|         target = target_class() | ||||
|         target.harvest_element_tree(tree) | ||||
|         return target | ||||
|     else: | ||||
|         return None | ||||
|  | ||||
| class Error(Exception): | ||||
|     """Exception class thrown by this module.""" | ||||
|     pass | ||||
|  | ||||
| class ExtensionElement(object): | ||||
|     """XML which is not part of the SAML specification, | ||||
|     these are called extension elements. If a classes parser | ||||
|     encounters an unexpected XML construct, it is translated into an | ||||
|     ExtensionElement instance. ExtensionElement is designed to fully | ||||
|     capture the information in the XML. Child nodes in an XML | ||||
|     extension are turned into ExtensionElements as well. | ||||
|     """ | ||||
|      | ||||
|     def __init__(self, tag, namespace=None, attributes=None, | ||||
|             children=None, text=None): | ||||
|         """Constructor for ExtensionElement | ||||
|  | ||||
|         :param namespace: The XML namespace for this element. | ||||
|         :param tag: The tag (without the namespace qualifier) for | ||||
|             this element. To reconstruct the full qualified name of the  | ||||
|             element, combine this tag with the namespace. | ||||
|         :param attributes: The attribute value string pairs for the XML  | ||||
|             attributes of this element. | ||||
|         :param children: list (optional) A list of ExtensionElements which | ||||
|             represent the XML child nodes of this element. | ||||
|         """ | ||||
|  | ||||
|         self.namespace = namespace | ||||
|         self.tag = tag | ||||
|         self.attributes = attributes or {} | ||||
|         self.children = children or [] | ||||
|         self.text = text | ||||
|          | ||||
|     def to_string(self): | ||||
|         """ Serialize the object into a XML string """ | ||||
|         element_tree = self.transfer_to_element_tree() | ||||
|         return ElementTree.tostring(element_tree, encoding="UTF-8") | ||||
|          | ||||
|     def transfer_to_element_tree(self): | ||||
|         if self.tag is None: | ||||
|             return None | ||||
|              | ||||
|         element_tree = ElementTree.Element('') | ||||
|  | ||||
|         if self.namespace is not None: | ||||
|             element_tree.tag = '{%s}%s' % (self.namespace, self.tag) | ||||
|         else: | ||||
|             element_tree.tag = self.tag | ||||
|              | ||||
|         for key, value in self.attributes.iteritems(): | ||||
|             element_tree.attrib[key] = value | ||||
|              | ||||
|         for child in self.children: | ||||
|             child.become_child_element_of(element_tree) | ||||
|              | ||||
|         element_tree.text = self.text | ||||
|              | ||||
|         return element_tree | ||||
|  | ||||
|     def become_child_element_of(self, element_tree): | ||||
|         """Converts this object into an etree element and adds it as a child  | ||||
|         node in an etree element. | ||||
|  | ||||
|         Adds self to the ElementTree. This method is required to avoid verbose  | ||||
|         XML which constantly redefines the namespace. | ||||
|  | ||||
|         :param element_tree: ElementTree._Element The element to which this  | ||||
|             object's XML will be added. | ||||
|         """ | ||||
|         new_element = self.transfer_to_element_tree() | ||||
|         element_tree.append(new_element) | ||||
|  | ||||
|     def find_children(self, tag=None, namespace=None): | ||||
|         """Searches child nodes for objects with the desired tag/namespace. | ||||
|  | ||||
|         Returns a list of extension elements within this object whose tag | ||||
|         and/or namespace match those passed in. To find all children in | ||||
|         a particular namespace, specify the namespace but not the tag name. | ||||
|         If you specify only the tag, the result list may contain extension | ||||
|         elements in multiple namespaces. | ||||
|  | ||||
|         :param tag: str (optional) The desired tag | ||||
|         :param namespace: str (optional) The desired namespace | ||||
|  | ||||
|         :return: A list of elements whose tag and/or namespace match the | ||||
|             parameters values | ||||
|         """ | ||||
|  | ||||
|         results = [] | ||||
|  | ||||
|         if tag and namespace: | ||||
|             for element in self.children: | ||||
|                 if element.tag == tag and element.namespace == namespace: | ||||
|                     results.append(element) | ||||
|         elif tag and not namespace: | ||||
|             for element in self.children: | ||||
|                 if element.tag == tag: | ||||
|                     results.append(element) | ||||
|         elif namespace and not tag: | ||||
|             for element in self.children: | ||||
|                 if element.namespace == namespace: | ||||
|                     results.append(element) | ||||
|         else: | ||||
|             for element in self.children: | ||||
|                 results.append(element) | ||||
|  | ||||
|         return results | ||||
|   | ||||
|     def loadd(self, ava): | ||||
|         """ expects a special set of keys """ | ||||
|          | ||||
|         if "attributes" in ava: | ||||
|             for key, val in ava["attributes"].items(): | ||||
|                 self.attributes[key] = val | ||||
|          | ||||
|         try: | ||||
|             self.tag = ava["tag"] | ||||
|         except KeyError: | ||||
|             if not self.tag: | ||||
|                 raise KeyError("ExtensionElement must have a tag") | ||||
|          | ||||
|         try: | ||||
|             self.namespace = ava["namespace"] | ||||
|         except KeyError: | ||||
|             if not self.namespace: | ||||
|                 raise KeyError("ExtensionElement must belong to a namespace") | ||||
|          | ||||
|         try: | ||||
|             self.text = ava["text"] | ||||
|         except KeyError: | ||||
|             pass | ||||
|              | ||||
|         if "children" in ava: | ||||
|             for item in ava["children"]: | ||||
|                 self.children.append(ExtensionElement(item["tag"]).loadd(item)) | ||||
|                  | ||||
|         return self | ||||
|          | ||||
| def extension_element_from_string(xml_string): | ||||
|     element_tree = ElementTree.fromstring(xml_string) | ||||
|     return _extension_element_from_element_tree(element_tree) | ||||
|  | ||||
|  | ||||
| def _extension_element_from_element_tree(element_tree): | ||||
|     elementc_tag = element_tree.tag | ||||
|     if '}' in elementc_tag: | ||||
|         namespace = elementc_tag[1:elementc_tag.index('}')] | ||||
|         tag = elementc_tag[elementc_tag.index('}')+1:] | ||||
|     else:  | ||||
|         namespace = None | ||||
|         tag = elementc_tag | ||||
|     extension = ExtensionElement(namespace=namespace, tag=tag) | ||||
|     for key, value in element_tree.attrib.iteritems(): | ||||
|         extension.attributes[key] = value | ||||
|     for child in element_tree: | ||||
|         extension.children.append(_extension_element_from_element_tree(child)) | ||||
|     extension.text = element_tree.text | ||||
|     return extension | ||||
|  | ||||
|  | ||||
| class ExtensionContainer(object): | ||||
|      | ||||
|     c_tag = "" | ||||
|     c_namespace = "" | ||||
|      | ||||
|     def __init__(self, text=None, extension_elements=None,  | ||||
|                     extension_attributes=None): | ||||
|  | ||||
|         self.text = text | ||||
|         self.extension_elements = extension_elements or [] | ||||
|         self.extension_attributes = extension_attributes or {} | ||||
|   | ||||
|     # Three methods to create an object from an ElementTree | ||||
|     def harvest_element_tree(self, tree): | ||||
|         # Fill in the instance members from the contents of the XML tree. | ||||
|         for child in tree: | ||||
|             self._convert_element_tree_to_member(child) | ||||
|         for attribute, value in tree.attrib.iteritems(): | ||||
|             self._convert_element_attribute_to_member(attribute, value) | ||||
|         self.text = tree.text | ||||
|          | ||||
|     def _convert_element_tree_to_member(self, child_tree): | ||||
|         self.extension_elements.append(_extension_element_from_element_tree( | ||||
|                 child_tree)) | ||||
|  | ||||
|     def _convert_element_attribute_to_member(self, attribute, value): | ||||
|         self.extension_attributes[attribute] = value | ||||
|  | ||||
|     # One method to create an ElementTree from an object | ||||
|     def _add_members_to_element_tree(self, tree): | ||||
|         for child in self.extension_elements: | ||||
|             child.become_child_element_of(tree) | ||||
|         for attribute, value in self.extension_attributes.iteritems(): | ||||
|             tree.attrib[attribute] = value | ||||
|         tree.text = self.text | ||||
|  | ||||
|     def find_extensions(self, tag=None, namespace=None): | ||||
|         """Searches extension elements for child nodes with the desired name. | ||||
|  | ||||
|         Returns a list of extension elements within this object whose tag | ||||
|         and/or namespace match those passed in. To find all extensions in | ||||
|         a particular namespace, specify the namespace but not the tag name. | ||||
|         If you specify only the tag, the result list may contain extension | ||||
|         elements in multiple namespaces. | ||||
|  | ||||
|         :param tag: str (optional) The desired tag | ||||
|         :param namespace: str (optional) The desired namespace | ||||
|  | ||||
|         :Return: A list of elements whose tag and/or namespace match the  | ||||
|             parameters values | ||||
|         """ | ||||
|  | ||||
|         results = [] | ||||
|  | ||||
|         if tag and namespace: | ||||
|             for element in self.extension_elements: | ||||
|                 if element.tag == tag and element.namespace == namespace: | ||||
|                     results.append(element) | ||||
|         elif tag and not namespace: | ||||
|             for element in self.extension_elements: | ||||
|                 if element.tag == tag: | ||||
|                     results.append(element) | ||||
|         elif namespace and not tag: | ||||
|             for element in self.extension_elements: | ||||
|                 if element.namespace == namespace: | ||||
|                     results.append(element) | ||||
|         else: | ||||
|             for element in self.extension_elements: | ||||
|                 results.append(element) | ||||
|  | ||||
|         return results | ||||
|  | ||||
|     def extensions_as_elements(self, tag, schema): | ||||
|         """ Return extensions that has the given tag and belongs to the | ||||
|         given schema as native elements of that schema. | ||||
|  | ||||
|         :param tag: The tag of the element | ||||
|         :param schema: Which schema the element should originate from | ||||
|         :return: a list of native elements | ||||
|         """ | ||||
|         result = [] | ||||
|         for ext in self.find_extensions(tag, schema.NAMESPACE): | ||||
|             ets = schema.ELEMENT_FROM_STRING[tag] | ||||
|             result.append(ets(ext.to_string())) | ||||
|         return result | ||||
|  | ||||
|     def add_extension_elements(self, items): | ||||
|         for item in items: | ||||
|             self.extension_elements.append(element_to_extension_element(item)) | ||||
|  | ||||
|     def add_extension_element(self, item): | ||||
|         self.extension_elements.append(element_to_extension_element(item)) | ||||
|  | ||||
|  | ||||
|     def add_extension_attribute(self, name, value): | ||||
|         self.extension_attributes[name] = value | ||||
|  | ||||
|  | ||||
|          | ||||
| def make_vals(val, klass, klass_inst=None, prop=None, part=False, | ||||
|                 base64encode=False): | ||||
|     """ | ||||
|     Creates a class instance with a specified value, the specified | ||||
|     class instance may be a value on a property in a defined class instance. | ||||
|      | ||||
|     :param val: The value | ||||
|     :param klass: The value class | ||||
|     :param klass_inst: The class instance which has a property on which  | ||||
|         what this function returns is a value. | ||||
|     :param prop: The property which the value should be assigned to. | ||||
|     :param part: If the value is one of a possible list of values it should be | ||||
|         handled slightly different compared to if it isn't. | ||||
|     :return: Value class instance | ||||
|     """ | ||||
|     cinst = None | ||||
|  | ||||
|     #print "make_vals(%s, %s)" % (val, klass) | ||||
|      | ||||
|     if isinstance(val, dict): | ||||
|         cinst = klass().loadd(val, base64encode=base64encode) | ||||
|     else: | ||||
|         try: | ||||
|             cinst = klass().set_text(val) | ||||
|         except ValueError: | ||||
|             if not part: | ||||
|                 cis = [make_vals(sval, klass, klass_inst, prop, True,  | ||||
|                         base64encode) for sval in val] | ||||
|                 setattr(klass_inst, prop, cis) | ||||
|             else: | ||||
|                 raise | ||||
|              | ||||
|     if part: | ||||
|         return cinst | ||||
|     else:         | ||||
|         if cinst:             | ||||
|             cis = [cinst] | ||||
|             setattr(klass_inst, prop, cis) | ||||
|      | ||||
| def make_instance(klass, spec, base64encode=False): | ||||
|     """ | ||||
|     Constructs a class instance containing the specified information | ||||
|      | ||||
|     :param klass: The class | ||||
|     :param spec: Information to be placed in the instance (a dictionary) | ||||
|     :return: The instance | ||||
|     """ | ||||
|  | ||||
|     return klass().loadd(spec, base64encode) | ||||
|  | ||||
| class SamlBase(ExtensionContainer): | ||||
|     """A foundation class on which SAML classes are built. It  | ||||
|     handles the parsing of attributes and children which are common to all | ||||
|     SAML classes. By default, the SamlBase class translates all XML child  | ||||
|     nodes into ExtensionElements. | ||||
|     """ | ||||
|      | ||||
|     c_children = {} | ||||
|     c_attributes = {} | ||||
|     c_attribute_type = {} | ||||
|     #c_attribute_use = {} | ||||
|     #c_attribute_required = {} | ||||
|     c_child_order = [] | ||||
|     c_cardinality = {} | ||||
|      | ||||
|     def _get_all_c_children_with_order(self): | ||||
|         if len(self.c_child_order) > 0: | ||||
|             for child in self.c_child_order: | ||||
|                 yield child | ||||
|         else: | ||||
|             for _, values in self.__class__.c_children.iteritems(): | ||||
|                 yield values[0] | ||||
|          | ||||
|     def _convert_element_tree_to_member(self, child_tree): | ||||
|         # Find the element's tag in this class's list of child members | ||||
|         if self.__class__.c_children.has_key(child_tree.tag): | ||||
|             member_name = self.__class__.c_children[child_tree.tag][0] | ||||
|             member_class = self.__class__.c_children[child_tree.tag][1] | ||||
|             # If the class member is supposed to contain a list, make sure the | ||||
|             # matching member is set to a list, then append the new member | ||||
|             # instance to the list. | ||||
|             if isinstance(member_class, list): | ||||
|                 if getattr(self, member_name) is None: | ||||
|                     setattr(self, member_name, []) | ||||
|                 getattr(self, member_name).append( | ||||
|                         create_class_from_element_tree( | ||||
|                             member_class[0], child_tree)) | ||||
|             else: | ||||
|                 setattr(self, member_name,  | ||||
|                             create_class_from_element_tree(member_class,  | ||||
|                                                             child_tree)) | ||||
|         else: | ||||
|             ExtensionContainer._convert_element_tree_to_member(self,  | ||||
|                                                                 child_tree) | ||||
|  | ||||
|     def _convert_element_attribute_to_member(self, attribute, value): | ||||
|         # Find the attribute in this class's list of attributes.  | ||||
|         if self.__class__.c_attributes.has_key(attribute): | ||||
|             # Find the member of this class which corresponds to the XML  | ||||
|             # attribute(lookup in current_class.c_attributes) and set this  | ||||
|             # member to the desired value (using self.__dict__). | ||||
|             setattr(self, self.__class__.c_attributes[attribute][0], value) | ||||
|         else: | ||||
|             # If it doesn't appear in the attribute list it's an extension | ||||
|             ExtensionContainer._convert_element_attribute_to_member(self,  | ||||
|                                                             attribute, value) | ||||
|  | ||||
|     # Three methods to create an ElementTree from an object | ||||
|     def _add_members_to_element_tree(self, tree): | ||||
|         # Convert the members of this class which are XML child nodes.  | ||||
|         # This uses the class's c_children dictionary to find the members which | ||||
|         # should become XML child nodes. | ||||
|         for member_name in self._get_all_c_children_with_order(): | ||||
|             member = getattr(self, member_name) | ||||
|             if member is None: | ||||
|                 pass | ||||
|             elif isinstance(member, list): | ||||
|                 for instance in member: | ||||
|                     instance.become_child_element_of(tree) | ||||
|             else: | ||||
|                 member.become_child_element_of(tree) | ||||
|         # Convert the members of this class which are XML attributes. | ||||
|         for xml_attribute, attribute_info in \ | ||||
|                     self.__class__.c_attributes.iteritems(): | ||||
|             (member_name, member_type, required) = attribute_info | ||||
|             member = getattr(self, member_name) | ||||
|             if member is not None: | ||||
|                 tree.attrib[xml_attribute] = member | ||||
|         # Lastly, call the ExtensionContainers's _add_members_to_element_tree  | ||||
|         # to convert any extension attributes. | ||||
|         ExtensionContainer._add_members_to_element_tree(self, tree) | ||||
|          | ||||
|      | ||||
|     def become_child_element_of(self, tree): | ||||
|         """ | ||||
|         Note: Only for use with classes that have a c_tag and c_namespace class  | ||||
|         member. It is in SamlBase so that it can be inherited but it should | ||||
|         not be called on instances of SamlBase. | ||||
|          | ||||
|         :param tree: The tree to which this instance should be a child | ||||
|         """ | ||||
|         new_child = self._to_element_tree() | ||||
|         tree.append(new_child) | ||||
|  | ||||
|     def _to_element_tree(self): | ||||
|         """ | ||||
|  | ||||
|         Note, this method is designed to be used only with classes that have a  | ||||
|         c_tag and c_namespace. It is placed in SamlBase for inheritance but  | ||||
|         should not be called on in this class. | ||||
|  | ||||
|         """ | ||||
|         new_tree = ElementTree.Element('{%s}%s' % (self.__class__.c_namespace, | ||||
|                                                     self.__class__.c_tag)) | ||||
|         self._add_members_to_element_tree(new_tree) | ||||
|         return new_tree | ||||
|  | ||||
|     def to_string(self, nspair=None): | ||||
|         """Converts the Saml object to a string containing XML.""" | ||||
|         if nspair: | ||||
|             for prefix, uri in nspair.items(): | ||||
|                 try: | ||||
|                     ElementTree.register_namespace(prefix, uri) | ||||
|                 except AttributeError: | ||||
|                     # Backwards compatibility with ET < 1.3 | ||||
|                     ElementTree._namespace_map[uri] = prefix | ||||
|          | ||||
|         return ElementTree.tostring(self._to_element_tree(), encoding="UTF-8") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.to_string() | ||||
|  | ||||
| #    def _init_attribute(self, extension_attribute_id, | ||||
| #                extension_attribute_name, value=None): | ||||
| # | ||||
| #        self.c_attributes[extension_attribute_id] = (extension_attribute_name, | ||||
| #                                                    None, False) | ||||
| #        if value: | ||||
| #            self.__dict__[extension_attribute_name] = value | ||||
|                      | ||||
|     def keyswv(self): | ||||
|         """ Return the keys of attributes or children that has values | ||||
|          | ||||
|         :return: list of keys | ||||
|         """ | ||||
|         return [key for key, val in self.__dict__.items() if val] | ||||
|  | ||||
|     def keys(self): | ||||
|         """ Return all the keys that represent possible attributes and  | ||||
|         children. | ||||
|          | ||||
|         :return: list of keys | ||||
|         """ | ||||
|         keys = ['text'] | ||||
|         keys.extend([n for (n, t, r) in self.c_attributes.values()]) | ||||
|         keys.extend([v[0] for v in self.c_children.values()]) | ||||
|         return keys | ||||
|          | ||||
|     def children_with_values(self): | ||||
|         """ Returns all children that has values | ||||
|          | ||||
|         :return: Possibly empty list of children. | ||||
|         """ | ||||
|         childs = [] | ||||
|         for attribute in self._get_all_c_children_with_order(): | ||||
|             member = getattr(self, attribute) | ||||
|             if member is None or member == []: | ||||
|                 pass | ||||
|             elif isinstance(member, list): | ||||
|                 for instance in member: | ||||
|                     childs.append(instance) | ||||
|             else: | ||||
|                 childs.append(member) | ||||
|         return childs | ||||
|  | ||||
|     #noinspection PyUnusedLocal | ||||
|     def set_text(self, val, base64encode=False): | ||||
|         """ Sets the text property of this instance. | ||||
|          | ||||
|         :param val: The value of the text property | ||||
|         :param base64encode: Whether the value should be base64encoded | ||||
|         :return: The instance | ||||
|         """ | ||||
|          | ||||
|         #print "set_text: %s" % (val,) | ||||
|         if isinstance(val, bool): | ||||
|             if val: | ||||
|                 setattr(self, "text", "true") | ||||
|             else: | ||||
|                 setattr(self, "text", "false") | ||||
|         elif isinstance(val, int): | ||||
|             setattr(self, "text", "%d" % val) | ||||
|         elif isinstance(val, basestring): | ||||
|             setattr(self, "text", val) | ||||
|         elif val is None: | ||||
|             pass | ||||
|         else: | ||||
|             raise ValueError( "Type shouldn't be '%s'" % (val,)) | ||||
|          | ||||
|         return self | ||||
|          | ||||
|     def loadd(self, ava, base64encode=False): | ||||
|         """  | ||||
|         Sets attributes, children, extension elements and extension  | ||||
|         attributes of this element instance depending on what is in  | ||||
|         the given dictionary. If there are already values on properties | ||||
|         those will be overwritten. If the keys in the dictionary does | ||||
|         not correspond to known attributes/children/.. they are ignored. | ||||
|          | ||||
|         :param ava: The dictionary | ||||
|         :param base64encode: Whether the values on attributes or texts on | ||||
|             children shoule be base64encoded. | ||||
|         :return: The instance | ||||
|         """ | ||||
|          | ||||
|         for prop, _typ, _req in self.c_attributes.values(): | ||||
|             #print "# %s" % (prop) | ||||
|             if prop in ava: | ||||
|                 if isinstance(ava[prop], bool): | ||||
|                     setattr(self, prop, "%s" % ava[prop]) | ||||
|                 elif isinstance(ava[prop], int): | ||||
|                     setattr(self, prop, "%d" % ava[prop]) | ||||
|                 else: | ||||
|                     setattr(self, prop, ava[prop]) | ||||
|  | ||||
|         if "text" in ava: | ||||
|             self.set_text(ava["text"], base64encode) | ||||
|              | ||||
|         for prop, klassdef in self.c_children.values(): | ||||
|             #print "## %s, %s" % (prop, klassdef) | ||||
|             if prop in ava: | ||||
|                 #print "### %s" % ava[prop] | ||||
|                 # means there can be a list of values | ||||
|                 if isinstance(klassdef, list):  | ||||
|                     make_vals(ava[prop], klassdef[0], self, prop, | ||||
|                                 base64encode=base64encode) | ||||
|                 else: | ||||
|                     cis = make_vals(ava[prop], klassdef, self, prop, True, | ||||
|                                 base64encode) | ||||
|                     setattr(self, prop, cis) | ||||
|  | ||||
|         if "extension_elements" in ava: | ||||
|             for item in ava["extension_elements"]: | ||||
|                 self.extension_elements.append(ExtensionElement( | ||||
|                                                 item["tag"]).loadd(item)) | ||||
|              | ||||
|         if "extension_attributes" in ava: | ||||
|             for key, val in ava["extension_attributes"].items(): | ||||
|                 self.extension_attributes[key] = val | ||||
|              | ||||
|         return self | ||||
|      | ||||
| #    def complete(self): | ||||
| #        for prop, _typ, req in self.c_attributes.values(): | ||||
| #            if req and not getattr(self, prop): | ||||
| #                return False | ||||
| # | ||||
| #        for prop, klassdef in self.c_children.values(): | ||||
| #            try: | ||||
| #                restriction = self.c_cardinality[prop] | ||||
| #                val = getattr(self, prop) | ||||
| #                if val is None: | ||||
| #                    num = 0 | ||||
| #                elif isinstance(val, list): | ||||
| #                    num = len(val) | ||||
| #                else: | ||||
| #                    num = 1 | ||||
| # | ||||
| #                try: | ||||
| #                    minimum = restriction["min"] | ||||
| #                except KeyError: | ||||
| #                    minimum = 1 | ||||
| #                if num < minimum: | ||||
| #                    return False | ||||
| #                try: | ||||
| #                    maximum = restriction["max"] | ||||
| #                except KeyError: | ||||
| #                    maximum = 1 | ||||
| #                # what if max == 0 ?? | ||||
| #                if maximum == "unbounded": | ||||
| #                    continue | ||||
| #                elif num > maximum: | ||||
| #                    return False | ||||
| #            except KeyError: | ||||
| #                # default cardinality: min=max=1 | ||||
| #                if not getattr(self, prop): | ||||
| #                    return False | ||||
| # | ||||
| #        return True | ||||
|          | ||||
|     def child_class(self, child): | ||||
|         """ Return the class a child element should be an instance of | ||||
|  | ||||
|         :param child: The name of the child element | ||||
|         :return: The class | ||||
|         """ | ||||
|         for prop, klassdef in self.c_children.values(): | ||||
|             if child == prop: | ||||
|                 if isinstance(klassdef, list): | ||||
|                     return klassdef[0] | ||||
|                 else: | ||||
|                     return klassdef | ||||
|         return None | ||||
|  | ||||
|     def child_cardinality(self, child): | ||||
|         """ Return the cardinality of a child element | ||||
|  | ||||
|         :param child: The name of the child element | ||||
|         :return: The cardinality as a 2-tuple (min, max). | ||||
|             The max value is either a number or the string "unbounded". | ||||
|             The min value is always a number. | ||||
|         """ | ||||
|         for prop, klassdef in self.c_children.values(): | ||||
|             if child == prop: | ||||
|                 if isinstance(klassdef, list): | ||||
|                     try: | ||||
|                         min = self.c_cardinality["min"] | ||||
|                     except KeyError: | ||||
|                         min = 1 | ||||
|                     try: | ||||
|                         max = self.c_cardinality["max"] | ||||
|                     except KeyError: | ||||
|                         max = "unbounded" | ||||
|  | ||||
|                     return min, max | ||||
|                 else: | ||||
|                     return 1,1 | ||||
|         return None | ||||
|  | ||||
|  | ||||
|  | ||||
| def element_to_extension_element(element): | ||||
|     """ | ||||
|     Convert an element into a extension element | ||||
|      | ||||
|     :param element: The element instance | ||||
|     :return: An extension element instance | ||||
|     """ | ||||
|      | ||||
|     exel = ExtensionElement(element.c_tag, element.c_namespace,  | ||||
|                             text=element.text) | ||||
|  | ||||
|     exel.attributes.update(element.extension_attributes) | ||||
|     exel.children.extend(element.extension_elements) | ||||
|      | ||||
|     for xml_attribute, (member_name, typ, req) in element.c_attributes.iteritems(): | ||||
|         member_value = getattr(element, member_name) | ||||
|         if member_value is not None: | ||||
|             exel.attributes[xml_attribute] = member_value | ||||
|                  | ||||
|     exel.children.extend([element_to_extension_element(c) \ | ||||
|                                 for c in element.children_with_values()]) | ||||
|      | ||||
|     return exel | ||||
|      | ||||
| def extension_element_to_element(extension_element, translation_functions, | ||||
|                                     namespace=None): | ||||
|     """ Convert an extension element to a normal element. | ||||
|     In order to do this you need to have an idea of what type of  | ||||
|     element it is. Or rather which module it belongs to. | ||||
|      | ||||
|     :param extension_element: The extension element | ||||
|     :prama translation_functions: A dictionary which klass identifiers | ||||
|         as keys and string-to-element translations functions as values | ||||
|     :param namespace: The namespace of the translation functions. | ||||
|     :return: An element instance or None | ||||
|     """ | ||||
|      | ||||
|     try: | ||||
|         element_namespace = extension_element.namespace | ||||
|     except AttributeError: | ||||
|         element_namespace = extension_element.c_namespace | ||||
|     if element_namespace == namespace: | ||||
|         try: | ||||
|             try: | ||||
|                 ets = translation_functions[extension_element.tag] | ||||
|             except AttributeError: | ||||
|                 ets = translation_functions[extension_element.c_tag] | ||||
|             return ets(extension_element.to_string()) | ||||
|         except KeyError: | ||||
|             pass | ||||
|              | ||||
|     return None | ||||
|          | ||||
| def extension_elements_to_elements(extension_elements, schemas): | ||||
|     """ Create a list of elements each one matching one of the | ||||
|     given extension elements. This is of course dependent on the access | ||||
|     to schemas that describe the extension elements. | ||||
|  | ||||
|     :param extension_elements: The list of extension elements | ||||
|     :param schemas: Imported Python modules that represent the different | ||||
|         known schemas used for the extension elements | ||||
|     :return: A list of elements, representing the set of extension elements | ||||
|         that was possible to match against a Class in the given schemas. | ||||
|         The elements returned are the native representation of the elements | ||||
|         according to the schemas. | ||||
|     """ | ||||
|     res = [] | ||||
|     for extension_element in extension_elements: | ||||
|         for schema in schemas: | ||||
|             inst = extension_element_to_element(extension_element, | ||||
|                                                     schema.ELEMENT_FROM_STRING, | ||||
|                                                     schema.NAMESPACE) | ||||
|             if inst: | ||||
|                 res.append(inst) | ||||
|                 break | ||||
|  | ||||
|     return res | ||||
|  | ||||
| def extension_elements_as_dict(extension_elements, onts): | ||||
|     ees_ = extension_elements_to_elements(extension_elements, onts) | ||||
|     res = {} | ||||
|     for elem in ees_: | ||||
|         try: | ||||
|             res[elem.c_tag].append(elem) | ||||
|         except KeyError: | ||||
|             res[elem.c_tag] = [elem] | ||||
|     return res | ||||
							
								
								
									
										505
									
								
								src/saml2/assertion.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										505
									
								
								src/saml2/assertion.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,505 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2010-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| import re | ||||
| import sys | ||||
| import xmlenc | ||||
|  | ||||
| from saml2 import saml | ||||
|  | ||||
| from saml2.time_util import instant, in_a_while | ||||
| from saml2.attribute_converter import from_local | ||||
|  | ||||
| from saml2.s_utils import sid, MissingValue | ||||
| from saml2.s_utils import factory | ||||
| from saml2.s_utils import assertion_factory | ||||
|  | ||||
|  | ||||
| def _filter_values(vals, vlist=None, must=False): | ||||
|     """ Removes values from *vals* that does not appear in vlist | ||||
|      | ||||
|     :param vals: The values that are to be filtered | ||||
|     :param vlist: required or optional value | ||||
|     :param must: Whether the allowed values must appear | ||||
|     :return: The set of values after filtering | ||||
|     """ | ||||
|      | ||||
|     if not vlist: # No value specified equals any value | ||||
|         return vals | ||||
|      | ||||
|     if isinstance(vlist, basestring): | ||||
|         vlist = [vlist] | ||||
|          | ||||
|     res = [] | ||||
|      | ||||
|     for val in vlist: | ||||
|         if val in vals: | ||||
|             res.append(val) | ||||
|      | ||||
|     if must: | ||||
|         if res: | ||||
|             return res | ||||
|         else: | ||||
|             raise MissingValue("Required attribute value missing") | ||||
|     else: | ||||
|         return res | ||||
|  | ||||
|  | ||||
| def filter_on_attributes(ava, required=None, optional=None): | ||||
|     """ Filter | ||||
|      | ||||
|     :param ava: An attribute value assertion as a dictionary | ||||
|     :param required: list of RequestedAttribute instances defined to be  | ||||
|         required | ||||
|     :param optional: list of     RequestedAttribute instances defined to be  | ||||
|         optional | ||||
|     :return: The modified attribute value assertion | ||||
|     """ | ||||
|     res = {} | ||||
|      | ||||
|     if required is None: | ||||
|         required = [] | ||||
|          | ||||
|     for attr in required: | ||||
|         if attr.friendly_name in ava: | ||||
|             values = [av.text for av in attr.attribute_value] | ||||
|             res[attr.friendly_name] = _filter_values(ava[attr.friendly_name], values, True) | ||||
|         elif attr.name in ava: | ||||
|             values = [av.text for av in attr.attribute_value] | ||||
|             res[attr.name] = _filter_values(ava[attr.name], values, True) | ||||
|         else: | ||||
|             print >> sys.stderr, ava.keys() | ||||
|             raise MissingValue("Required attribute missing: '%s'" % (attr.friendly_name,)) | ||||
|  | ||||
|     if optional is None: | ||||
|         optional = [] | ||||
|          | ||||
|     for attr in optional: | ||||
|         if attr.friendly_name in ava: | ||||
|             values = [av.text for av in attr.attribute_value] | ||||
|             try: | ||||
|                 res[attr.friendly_name].extend(_filter_values(ava[attr.friendly_name], values)) | ||||
|             except KeyError: | ||||
|                 res[attr.friendly_name] = _filter_values(ava[attr.friendly_name], values) | ||||
|         elif attr.name in ava: | ||||
|             values = [av.text for av in attr.attribute_value] | ||||
|             try: | ||||
|                 res[attr.name].extend(_filter_values(ava[attr.name], values)) | ||||
|             except KeyError: | ||||
|                 res[attr.name] = _filter_values(ava[attr.name], values) | ||||
|      | ||||
|     return res | ||||
|  | ||||
| def filter_on_demands(ava, required=None, optional=None): | ||||
|     """ Never return more than is needed. Filters out everything | ||||
|     the server is prepared to return but the receiver doesn't ask for | ||||
|      | ||||
|     :param ava: Attribute value assertion as a dictionary | ||||
|     :param required: Required attributes | ||||
|     :param optional: Optional attributes | ||||
|     :return: The possibly reduced assertion | ||||
|     """ | ||||
|      | ||||
|     # Is all what's required there: | ||||
|     if required is None: | ||||
|         required = {} | ||||
|          | ||||
|     for attr, vals in required.items(): | ||||
|         if attr in ava: | ||||
|             if vals: | ||||
|                 for val in vals: | ||||
|                     if val not in ava[attr]: | ||||
|                         raise MissingValue( | ||||
|                             "Required attribute value missing: %s,%s" % (attr, | ||||
|                                                                         val)) | ||||
|         else: | ||||
|             raise MissingValue("Required attribute missing: %s" % (attr,)) | ||||
|  | ||||
|     if optional is None: | ||||
|         optional = {} | ||||
|          | ||||
|     # OK, so I can imaging releasing values that are not absolutely necessary | ||||
|     # but not attributes | ||||
|     for attr, vals in ava.items(): | ||||
|         if attr not in required and attr not in optional: | ||||
|             del ava[attr] | ||||
|      | ||||
|     return ava | ||||
|  | ||||
| def filter_on_wire_representation(ava, acs, required=None, optional=None): | ||||
|     """ | ||||
|     :param ava: A dictionary with attributes and values | ||||
|     :param required: A list of saml.Attributes | ||||
|     :param optional: A list of saml.Attributes | ||||
|     :return: Dictionary of expected/wanted attributes and values | ||||
|     """ | ||||
|     acsdic = dict([(ac.name_format, ac) for ac in acs]) | ||||
|  | ||||
|     if required is None: | ||||
|         required = [] | ||||
|     if optional is None: | ||||
|         optional = [] | ||||
|  | ||||
|     res = {} | ||||
|     for attr, val in ava.items(): | ||||
|         done = False | ||||
|         for req in required: | ||||
|             try: | ||||
|                 _name = acsdic[req.name_format]._to[attr] | ||||
|                 if _name == req.name: | ||||
|                     res[attr] = val | ||||
|                     done = True | ||||
|             except KeyError: | ||||
|                 pass | ||||
|         if done: | ||||
|             continue | ||||
|         for opt in optional: | ||||
|             try: | ||||
|                 _name = acsdic[opt.name_format]._to[attr] | ||||
|                 if _name == opt.name: | ||||
|                     res[attr] = val | ||||
|                     break | ||||
|             except KeyError: | ||||
|                 pass | ||||
|  | ||||
|     return res | ||||
|  | ||||
| def filter_attribute_value_assertions(ava, attribute_restrictions=None): | ||||
|     """ Will weed out attribute values and values according to the | ||||
|     rules defined in the attribute restrictions. If filtering results in | ||||
|     an attribute without values, then the attribute is removed from the | ||||
|     assertion. | ||||
|      | ||||
|     :param ava: The incoming attribute value assertion (dictionary) | ||||
|     :param attribute_restrictions: The rules that govern which attributes | ||||
|         and values that are allowed. (dictionary) | ||||
|     :return: The modified attribute value assertion | ||||
|     """ | ||||
|     if not attribute_restrictions: | ||||
|         return ava | ||||
|      | ||||
|     for attr, vals in ava.items(): | ||||
|         if attr in attribute_restrictions: | ||||
|             if attribute_restrictions[attr]: | ||||
|                 rvals = [] | ||||
|                 for restr in attribute_restrictions[attr]: | ||||
|                     for val in vals: | ||||
|                         if restr.match(val): | ||||
|                             rvals.append(val) | ||||
|                  | ||||
|                 if rvals: | ||||
|                     ava[attr] = list(set(rvals)) | ||||
|                 else: | ||||
|                     del ava[attr] | ||||
|         else: | ||||
|             del ava[attr] | ||||
|     return ava | ||||
|  | ||||
| class Policy(object): | ||||
|     """ handles restrictions on assertions """ | ||||
|      | ||||
|     def __init__(self, restrictions=None): | ||||
|         if restrictions: | ||||
|             self.compile(restrictions) | ||||
|         else: | ||||
|             self._restrictions = None | ||||
|      | ||||
|     def compile(self, restrictions): | ||||
|         """ This is only for IdPs or AAs, and it's about limiting what | ||||
|         is returned to the SP. | ||||
|         In the configuration file, restrictions on which values that | ||||
|         can be returned are specified with the help of regular expressions. | ||||
|         This function goes through and pre-compiles the regular expressions. | ||||
|          | ||||
|         :param restrictions: | ||||
|         :return: The assertion with the string specification replaced with | ||||
|             a compiled regular expression. | ||||
|         """ | ||||
|          | ||||
|         self._restrictions = restrictions.copy() | ||||
|          | ||||
|         for _, spec in self._restrictions.items(): | ||||
|             if spec is None: | ||||
|                 continue | ||||
|              | ||||
|             try: | ||||
|                 restr = spec["attribute_restrictions"] | ||||
|             except KeyError: | ||||
|                 continue | ||||
|              | ||||
|             if restr is None: | ||||
|                 continue | ||||
|              | ||||
|             for key, values in restr.items(): | ||||
|                 if not values: | ||||
|                     spec["attribute_restrictions"][key] = None | ||||
|                     continue | ||||
|                  | ||||
|                 spec["attribute_restrictions"][key] = \ | ||||
|                         [re.compile(value) for value in values] | ||||
|          | ||||
|         return self._restrictions | ||||
|      | ||||
|     def get_nameid_format(self, sp_entity_id): | ||||
|         """ Get the NameIDFormat to used for the entity id  | ||||
|         :param: The SP entity ID | ||||
|         :retur: The format | ||||
|         """ | ||||
|         try: | ||||
|             form = self._restrictions[sp_entity_id]["nameid_format"] | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 form = self._restrictions["default"]["nameid_format"] | ||||
|             except KeyError: | ||||
|                 form = saml.NAMEID_FORMAT_TRANSIENT | ||||
|          | ||||
|         return form | ||||
|      | ||||
|     def get_name_form(self, sp_entity_id): | ||||
|         """ Get the NameFormat to used for the entity id  | ||||
|         :param: The SP entity ID | ||||
|         :retur: The format | ||||
|         """ | ||||
|         form = "" | ||||
|          | ||||
|         try: | ||||
|             form = self._restrictions[sp_entity_id]["name_form"] | ||||
|         except TypeError: | ||||
|             pass | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 form = self._restrictions["default"]["name_form"] | ||||
|             except KeyError: | ||||
|                 pass | ||||
|          | ||||
|         return form | ||||
|      | ||||
|     def get_lifetime(self, sp_entity_id): | ||||
|         """ The lifetime of the assertion  | ||||
|         :param sp_entity_id: The SP entity ID | ||||
|         :param: lifetime as a dictionary  | ||||
|         """ | ||||
|         # default is a hour | ||||
|         spec = {"hours":1} | ||||
|         if not self._restrictions: | ||||
|             return spec | ||||
|          | ||||
|         try: | ||||
|             spec = self._restrictions[sp_entity_id]["lifetime"] | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 spec = self._restrictions["default"]["lifetime"] | ||||
|             except KeyError: | ||||
|                 pass | ||||
|          | ||||
|         return spec | ||||
|      | ||||
|     def get_attribute_restriction(self, sp_entity_id): | ||||
|         """ Return the attribute restriction for SP that want the information | ||||
|          | ||||
|         :param sp_entity_id: The SP entity ID | ||||
|         :return: The restrictions | ||||
|         """ | ||||
|          | ||||
|         if not self._restrictions: | ||||
|             return None | ||||
|          | ||||
|         try: | ||||
|             try: | ||||
|                 restrictions = self._restrictions[sp_entity_id][ | ||||
|                                                 "attribute_restrictions"] | ||||
|             except KeyError: | ||||
|                 try: | ||||
|                     restrictions = self._restrictions["default"][ | ||||
|                                                 "attribute_restrictions"] | ||||
|                 except KeyError: | ||||
|                     restrictions = None | ||||
|         except KeyError: | ||||
|             restrictions = None | ||||
|          | ||||
|         return restrictions | ||||
|      | ||||
|     def not_on_or_after(self, sp_entity_id): | ||||
|         """ When the assertion stops being valid, should not be | ||||
|         used after this time. | ||||
|          | ||||
|         :param sp_entity_id: The SP entity ID | ||||
|         :return: String representation of the time | ||||
|         """ | ||||
|          | ||||
|         return in_a_while(**self.get_lifetime(sp_entity_id)) | ||||
|      | ||||
|     def filter(self, ava, sp_entity_id, required=None, optional=None): | ||||
|         """ What attribute and attribute values returns depends on what | ||||
|         the SP has said it wants in the request or in the metadata file and | ||||
|         what the IdP/AA wants to release. An assumption is that what the SP | ||||
|         asks for overrides whatever is in the metadata. But of course the | ||||
|         IdP never releases anything it doesn't want to. | ||||
|          | ||||
|         :param ava: The information about the subject as a dictionary | ||||
|         :param sp_entity_id: The entity ID of the SP | ||||
|         :param required: Attributes that the SP requires in the assertion | ||||
|         :param optional: Attributes that the SP regards as optional | ||||
|         :return: A possibly modified AVA | ||||
|         """ | ||||
|                                  | ||||
|          | ||||
|         ava = filter_attribute_value_assertions(ava, | ||||
|                                 self.get_attribute_restriction(sp_entity_id)) | ||||
|          | ||||
|         if required or optional: | ||||
|             ava = filter_on_attributes(ava, required, optional) | ||||
|          | ||||
|         return ava | ||||
|      | ||||
|     def restrict(self, ava, sp_entity_id, metadata=None): | ||||
|         """ Identity attribute names are expected to be expressed in | ||||
|         the local lingo (== friendlyName) | ||||
|          | ||||
|         :return: A filtered ava according to the IdPs/AAs rules and | ||||
|             the list of required/optional attributes according to the SP. | ||||
|             If the requirements can't be met an exception is raised. | ||||
|         """ | ||||
|         if metadata: | ||||
|             (required, optional) = metadata.attribute_consumer(sp_entity_id) | ||||
|             #(required, optional) = metadata.wants(sp_entity_id) | ||||
|         else: | ||||
|             required = optional = None | ||||
|          | ||||
|         return self.filter(ava, sp_entity_id, required, optional) | ||||
|      | ||||
|     def conditions(self, sp_entity_id): | ||||
|         """ Return a saml.Condition instance | ||||
|          | ||||
|         :param sp_entity_id: The SP entity ID | ||||
|         :return: A saml.Condition instance | ||||
|         """ | ||||
|         return factory( saml.Conditions, | ||||
|                         not_before=instant(), | ||||
|                         # How long might depend on who's getting it | ||||
|                         not_on_or_after=self.not_on_or_after(sp_entity_id), | ||||
|                         audience_restriction=[factory( saml.AudienceRestriction, | ||||
|                                 audience=factory(saml.Audience,  | ||||
|                                                 text=sp_entity_id))]) | ||||
|  | ||||
| class Assertion(dict): | ||||
|     """ Handles assertions about subjects """ | ||||
|      | ||||
|     def __init__(self, dic=None): | ||||
|         dict.__init__(self, dic) | ||||
|      | ||||
|     def _authn_context_decl_ref(self, authn_class): | ||||
|         # authn_class: saml.AUTHN_PASSWORD | ||||
|         return factory(saml.AuthnContext,  | ||||
|                         authn_context_decl_ref=factory( | ||||
|                                 saml.AuthnContextDeclRef, text=authn_class)) | ||||
|  | ||||
|     def _authn_context_class_ref(self, authn_class, authn_auth=None): | ||||
|         # authn_class: saml.AUTHN_PASSWORD | ||||
|         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): | ||||
|         if authn_class: | ||||
|             return factory(saml.AuthnStatement,  | ||||
|                         authn_instant=instant(),  | ||||
|                         session_index=sid(), | ||||
|                         authn_context=self._authn_context_class_ref( | ||||
|                                                     authn_class, authn_auth)) | ||||
|         elif authn_decl: | ||||
|             return factory(saml.AuthnStatement,  | ||||
|                         authn_instant=instant(),  | ||||
|                         session_index=sid(), | ||||
|                         authn_context=self._authn_context_decl_ref(authn_decl)) | ||||
|         else: | ||||
|             return factory(saml.AuthnStatement, | ||||
|                         authn_instant=instant(),  | ||||
|                         session_index=sid()) | ||||
|  | ||||
|     def construct(self, sp_entity_id, in_response_to, consumer_url, | ||||
|                     name_id, attrconvs, policy, issuer, authn_class=None,  | ||||
|                     authn_auth=None, authn_decl=None, encrypt=None, | ||||
|                     sec_context=None): | ||||
|         """ Construct the Assertion  | ||||
|          | ||||
|         :param sp_entity_id: The entityid of the SP | ||||
|         :param in_response_to: An identifier of the message, this message is  | ||||
|             a response to | ||||
|         :param consumer_url: The intended consumer of the assertion | ||||
|         :param name_id: An NameID instance | ||||
|         :param attrconvs: AttributeConverters | ||||
|         :param policy: The policy that should be adhered to when replying | ||||
|         :param issuer: Who is issuing the statement | ||||
|         :param authn_class: The authentication class | ||||
|         :param authn_auth: The authentication instance | ||||
|         :param encrypt: Whether to encrypt parts or all of the Assertion | ||||
|         :param sec_context: The security context used when encrypting | ||||
|         :return: An Assertion instance | ||||
|         """ | ||||
|         attr_statement = saml.AttributeStatement(attribute=from_local( | ||||
|                                 attrconvs, self,  | ||||
|                                 policy.get_name_form(sp_entity_id))) | ||||
|  | ||||
|         if encrypt == "attributes": | ||||
|             for attr in attr_statement.attribute: | ||||
|                 enc = sec_context.encrypt(text="%s" % attr) | ||||
|  | ||||
|                 encd = xmlenc.encrypted_data_from_string(enc) | ||||
|                 encattr = saml.EncryptedAttribute(encrypted_data=encd) | ||||
|                 attr_statement.encrypted_attribute.append(encattr) | ||||
|  | ||||
|             attr_statement.attribute = [] | ||||
|  | ||||
|         # start using now and for some time | ||||
|         conds = policy.conditions(sp_entity_id) | ||||
|          | ||||
|         return assertion_factory( | ||||
|             issuer=issuer, | ||||
|             attribute_statement = attr_statement, | ||||
|             authn_statement = self._authn_statement(authn_class, authn_auth,  | ||||
|                                                     authn_decl), | ||||
|             conditions = conds, | ||||
|             subject=factory( saml.Subject, | ||||
|                 name_id=name_id, | ||||
|                 subject_confirmation=factory( saml.SubjectConfirmation, | ||||
|                                 method=saml.SUBJECT_CONFIRMATION_METHOD_BEARER, | ||||
|                                 subject_confirmation_data=factory( | ||||
|                                     saml.SubjectConfirmationData, | ||||
|                                     in_response_to=in_response_to, | ||||
|                                     recipient=consumer_url, | ||||
|                                     not_on_or_after=policy.not_on_or_after( | ||||
|                                                             sp_entity_id)))), | ||||
|             ) | ||||
|      | ||||
|     def apply_policy(self, sp_entity_id, policy, metadata=None): | ||||
|         """ Apply policy to the assertion I'm representing  | ||||
|          | ||||
|         :param sp_entity_id: The SP entity ID | ||||
|         :param policy: The policy | ||||
|         :param metadata: Metadata to use | ||||
|         :return: The resulting AVA after the policy is applied | ||||
|         """ | ||||
|         return policy.restrict(self, sp_entity_id, metadata) | ||||
							
								
								
									
										346
									
								
								src/saml2/attribute_converter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								src/saml2/attribute_converter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,346 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) s2010-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| from importlib import import_module | ||||
|  | ||||
| from saml2.s_utils import factory, do_ava | ||||
| from saml2 import saml | ||||
| from saml2.saml import NAME_FORMAT_URI | ||||
|  | ||||
| class UnknownNameFormat(Exception): | ||||
|     pass | ||||
|  | ||||
| def load_maps(dirspec): | ||||
|     """ load the attribute maps | ||||
|  | ||||
|     :param dirspec: a directory specification | ||||
|     :return: a dictionary with the name of the map as key and the | ||||
|         map as value. The map itself is a dictionary with two keys: | ||||
|         "to" and "fro". The values for those keys are the actual mapping. | ||||
|     """ | ||||
|     map = {} | ||||
|     if dirspec not in sys.path: | ||||
|         sys.path.insert(0, dirspec) | ||||
|  | ||||
|     for fil in os.listdir(dirspec): | ||||
|         if fil.endswith(".py"): | ||||
|             mod = import_module(fil[:-3]) | ||||
|             for key, item in mod.__dict__.items(): | ||||
|                 if key.startswith("__"): | ||||
|                     continue | ||||
|                 if isinstance(item, dict) and "to" in item and "fro" in item: | ||||
|                     map[item["identifier"]] = item | ||||
|  | ||||
|     return map | ||||
|  | ||||
| def ac_factory(path=""): | ||||
|     """Attribute Converter factory | ||||
|  | ||||
|     :param path: The path to a directory where the attribute maps are expected | ||||
|         to reside. | ||||
|     :return: A AttributeConverter instance | ||||
|     """ | ||||
|     acs = [] | ||||
|  | ||||
|     if path: | ||||
|         if path not in sys.path: | ||||
|             sys.path.insert(0, path) | ||||
|  | ||||
|         for fil in os.listdir(path): | ||||
|             if fil.endswith(".py"): | ||||
|                 mod = import_module(fil[:-3]) | ||||
|                 for key, item in mod.__dict__.items(): | ||||
|                     if key.startswith("__"): | ||||
|                         continue | ||||
|                     if isinstance(item, dict) and "to" in item and "fro" in item: | ||||
|                         atco = AttributeConverter(item["identifier"]) | ||||
|                         atco.from_dict(item) | ||||
|                         acs.append(atco) | ||||
|     else: | ||||
|         for map in ["basic", "saml_uri", "shibboleth_uri"]: | ||||
|             mod = import_module(".%s" % map, "saml2.attributemaps") | ||||
|             for key, item in mod.__dict__.items(): | ||||
|                 if key.startswith("__"): | ||||
|                     continue | ||||
|                 if isinstance(item, dict) and "to" in item and "fro" in item: | ||||
|                     atco = AttributeConverter(item["identifier"]) | ||||
|                     atco.from_dict(item) | ||||
|                     acs.append(atco) | ||||
|  | ||||
|     return acs | ||||
|  | ||||
| def ac_factory_II(path): | ||||
|     return ac_factory(path) | ||||
|  | ||||
| #def ac_factory_old(path): | ||||
| #    acs = [] | ||||
| # | ||||
| #    for dir_name, directories, files in os.walk(path): | ||||
| #        for d in list(directories): | ||||
| #            if d.startswith('.'): | ||||
| #                directories.remove(d) | ||||
| # | ||||
| #        if files: | ||||
| #            atco = AttributeConverter(os.path.basename(dir_name)) | ||||
| #            for name in files: | ||||
| #                fname = os.path.join(dir_name, name) | ||||
| #                if name.endswith(".py"): | ||||
| #                    name = name[:-3] | ||||
| #                atco.set(name, fname) | ||||
| #            atco.adjust() | ||||
| #            acs.append(atco) | ||||
| #    return acs | ||||
|      | ||||
| def ava_fro(acs, statement): | ||||
|     """  Translates attributes according to their name_formats into the local | ||||
|      names. | ||||
|  | ||||
|     :param acs: AttributeConverter instances | ||||
|     :param statement: A SAML statement | ||||
|     :return: A dictionary with attribute names replaced with local names. | ||||
|     """ | ||||
|     if not statement: | ||||
|         return {} | ||||
|          | ||||
|     acsdic = dict([(ac.name_format, ac) for ac in acs]) | ||||
|     acsdic[None] = acsdic[NAME_FORMAT_URI]     | ||||
|     return dict([acsdic[a.name_format].ava_from(a) for a in statement]) | ||||
|  | ||||
| def to_local(acs, statement): | ||||
|     """ Replaces the attribute names in a attribute value assertion with the | ||||
|     equivalent name from a local name format. | ||||
|  | ||||
|     """ | ||||
|     if not acs: | ||||
|         acs = [AttributeConverter()] | ||||
|          | ||||
|     ava = [] | ||||
|     for aconv in acs: | ||||
|         try: | ||||
|             ava = aconv.fro(statement) | ||||
|             break | ||||
|         except UnknownNameFormat: | ||||
|             pass | ||||
|     return ava | ||||
|  | ||||
| def from_local(acs, ava, name_format): | ||||
|     for aconv in acs: | ||||
|         #print ac.format, name_format | ||||
|         if aconv.name_format == name_format: | ||||
|             #print "Found a name_form converter" | ||||
|             return aconv.to_(ava) | ||||
|              | ||||
|     return None | ||||
|      | ||||
| def from_local_name(acs, attr, name_format): | ||||
|     """ | ||||
|     :param acs: List of AttributeConverter instances | ||||
|     :param attr: attribute name as string | ||||
|     :param name_format: Which name-format it should be translated to | ||||
|     :return: An Attribute instance | ||||
|     """ | ||||
|     for aconv in acs: | ||||
|         #print ac.format, name_format | ||||
|         if aconv.name_format == name_format: | ||||
|             #print "Found a name_form converter" | ||||
|             return aconv.to_format(attr) | ||||
|     return attr | ||||
|      | ||||
| def to_local_name(acs, attr): | ||||
|     """ | ||||
|     :param acs: List of AttributeConverter instances | ||||
|     :param attr: an Attribute instance | ||||
|     :return: The local attribute name | ||||
|     """ | ||||
|     for aconv in acs: | ||||
|         lattr = aconv.from_format(attr) | ||||
|         if lattr: | ||||
|             return lattr | ||||
|  | ||||
|     return attr.friendly_name | ||||
|      | ||||
| class AttributeConverter(object): | ||||
|     """ Converts from an attribute statement to a key,value dictionary and | ||||
|         vice-versa """ | ||||
|          | ||||
|     def __init__(self, name_format=""): | ||||
|         self.name_format = name_format | ||||
|         self._to = None | ||||
|         self._fro = None | ||||
|          | ||||
| #    def set(self, name, filename): | ||||
| #        if name == "to": | ||||
| #            self.set_to(filename) | ||||
| #        elif name == "fro": | ||||
| #            self.set_fro(filename) | ||||
| #        # else ignore | ||||
| # | ||||
| #    def set_fro(self, filename): | ||||
| #        self._fro = eval(open(filename).read()) | ||||
| # | ||||
| #    def set_to(self, filename): | ||||
| #        self._to = eval(open(filename).read()) | ||||
| # | ||||
|     def adjust(self): | ||||
|         """ If one of the transformations is not defined it is expected to | ||||
|         be the mirror image of the other. | ||||
|         """ | ||||
|          | ||||
|         if self._fro is None and self._to is not None: | ||||
|             self._fro = dict([(value, key) for key, value in self._to.items()]) | ||||
|         if self._to is None and self.fro is not None: | ||||
|             self._to = dict([(value, key) for key, value in self._fro.items()]) | ||||
|  | ||||
|     def from_dict(self, mapdict): | ||||
|         """ Import the attribute map from  a dictionary | ||||
|  | ||||
|         :param mapdict: The dictionary | ||||
|         """ | ||||
|  | ||||
|         self.name_format = mapdict["identifier"] | ||||
|         try: | ||||
|             self._fro = mapdict["fro"] | ||||
|         except KeyError: | ||||
|             pass | ||||
|         try: | ||||
|             self._to = mapdict["to"] | ||||
|         except KeyError: | ||||
|             pass | ||||
|  | ||||
|         if self._fro is None and self._to is None: | ||||
|             raise Exception("Missing specifications") | ||||
|  | ||||
|         if self._fro is None or self._to is None: | ||||
|             self.adjust() | ||||
|  | ||||
|          | ||||
|     def fail_safe_fro(self, statement): | ||||
|         """ In case there is not formats defined """ | ||||
|         result = {} | ||||
|         for attribute in statement.attribute: | ||||
|             try: | ||||
|                 name = attribute.friendly_name.strip() | ||||
|             except AttributeError: | ||||
|                 name = attribute.name.strip() | ||||
|  | ||||
|             result[name] = [] | ||||
|             for value in attribute.attribute_value: | ||||
|                 if not value.text: | ||||
|                     result[name].append('') | ||||
|                 else: | ||||
|                     result[name].append(value.text.strip())     | ||||
|         return result | ||||
|          | ||||
|     def ava_from(self, attribute): | ||||
|         try: | ||||
|             attr = self._fro[attribute.name.strip()] | ||||
|         except (AttributeError, KeyError): | ||||
|             try: | ||||
|                 attr = attribute.friendly_name.strip() | ||||
|             except AttributeError: | ||||
|                 attr = attribute.name.strip() | ||||
|  | ||||
|         val = [] | ||||
|         for value in attribute.attribute_value: | ||||
|             if not value.text: | ||||
|                 val.append('') | ||||
|             else: | ||||
|                 val.append(value.text.strip()) | ||||
|  | ||||
|         return attr, val | ||||
|          | ||||
|     def fro(self, statement): | ||||
|         """ Get the attributes and the attribute values  | ||||
|          | ||||
|         :param statement: The AttributeStatement. | ||||
|         :return: A dictionary containing attributes and values | ||||
|         """ | ||||
|          | ||||
|         if not self.name_format: | ||||
|             return self.fail_safe_fro(statement) | ||||
|              | ||||
|         result = {} | ||||
|         for attribute in statement.attribute: | ||||
|             if attribute.name_format and self.name_format and \ | ||||
|                 attribute.name_format != self.name_format: | ||||
|                 raise UnknownNameFormat | ||||
|                  | ||||
|             (key, val) = self.ava_from(attribute) | ||||
|             result[key] = val | ||||
|              | ||||
|         if not result: | ||||
|             return self.fail_safe_fro(statement)  | ||||
|         else: | ||||
|             return result | ||||
|          | ||||
|     def to_format(self, attr): | ||||
|         """ Creates an Attribute instance with name, name_format and | ||||
|         friendly_name | ||||
|  | ||||
|         :param attr: The local name of the attribute | ||||
|         :return: An Attribute instance | ||||
|         """ | ||||
|         try: | ||||
|             return factory(saml.Attribute, | ||||
|                             name=self._to[attr],  | ||||
|                             name_format=self.name_format, | ||||
|                             friendly_name=attr) | ||||
|         except KeyError: | ||||
|             return factory(saml.Attribute, name=attr) | ||||
|      | ||||
|     def from_format(self, attr): | ||||
|         """ Find out the local name of an attribute | ||||
|           | ||||
|         :param attr: An saml.Attribute instance | ||||
|         :return: The local attribute name or "" if no mapping could be made | ||||
|         """ | ||||
|         if attr.name_format: | ||||
|             if self.name_format == attr.name_format: | ||||
|                 try: | ||||
|                     return self._fro[attr.name] | ||||
|                 except KeyError: | ||||
|                     pass | ||||
|         else: #don't know the name format so try all I have | ||||
|             try: | ||||
|                 return self._fro[attr.name] | ||||
|             except KeyError: | ||||
|                 pass | ||||
|  | ||||
|         return "" | ||||
|          | ||||
|     def to_(self, attrvals): | ||||
|         """ Create a list of Attribute instances. | ||||
|  | ||||
|         :param attrvals: A dictionary of attributes and values | ||||
|         :return: A list of Attribute instances | ||||
|         """ | ||||
|         attributes = [] | ||||
|         for key, value in attrvals.items(): | ||||
|             try: | ||||
|                 attributes.append(factory(saml.Attribute, | ||||
|                                             name=self._to[key], | ||||
|                                             name_format=self.name_format, | ||||
|                                             friendly_name=key, | ||||
|                                             attribute_value=do_ava(value))) | ||||
|             except KeyError: | ||||
|                 attributes.append(factory(saml.Attribute, | ||||
|                                             name=key, | ||||
|                                             attribute_value=do_ava(value))) | ||||
|          | ||||
|         return attributes | ||||
							
								
								
									
										70
									
								
								src/saml2/attribute_resolver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/saml2/attribute_resolver.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2009-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """ | ||||
| Contains classes and functions that a SAML2.0 Service Provider (SP) may use | ||||
| to do attribute aggregation. | ||||
| """ | ||||
| import saml2 | ||||
|  | ||||
| DEFAULT_BINDING = saml2.BINDING_SOAP | ||||
|  | ||||
| class AttributeResolver(object): | ||||
|  | ||||
|     def __init__(self, metadata=None, config=None, saml2client=None): | ||||
|         self.metadata = metadata | ||||
|  | ||||
|         if saml2client: | ||||
|             self.saml2client = saml2client | ||||
|             self.metadata = saml2client.config.metadata | ||||
|         else: | ||||
|             self.saml2client = saml2.client.Saml2Client(config) | ||||
|          | ||||
|     def extend(self, subject_id, issuer, vo_members, name_id_format=None, | ||||
|                 sp_name_qualifier=None, log=None, real_id=None): | ||||
|         """  | ||||
|         :param subject_id: The identifier by which the subject is know | ||||
|             among all the participents of the VO | ||||
|         :param issuer: Who am I the poses the query | ||||
|         :param vo_members: The entity IDs of the IdP who I'm going to ask | ||||
|             for extra attributes | ||||
|         :param nameid_format: Used to make the IdPs aware of what's going | ||||
|             on here | ||||
|         :param log: Where to log exciting information | ||||
|         :return: A dictionary with all the collected information about the | ||||
|             subject | ||||
|         """ | ||||
|         result = [] | ||||
|         for member in vo_members:             | ||||
|             for ass in self.metadata.attribute_services(member): | ||||
|                 for attr_serv in ass.attribute_service: | ||||
|                     if log: | ||||
|                         log.info( | ||||
|                             "Send attribute request to %s" % attr_serv.location) | ||||
|                     if attr_serv.binding != saml2.BINDING_SOAP: | ||||
|                         continue | ||||
|                     # attribute query assumes SOAP binding | ||||
|                     session_info = self.saml2client.attribute_query( | ||||
|                                         subject_id,  | ||||
|                                         attr_serv.location,  | ||||
|                                         issuer_id=issuer,  | ||||
|                                         sp_name_qualifier=sp_name_qualifier, | ||||
|                                         nameid_format=name_id_format,  | ||||
|                                         log=log, real_id=real_id) | ||||
|                     if session_info: | ||||
|                         result.append(session_info) | ||||
|         return result | ||||
							
								
								
									
										1
									
								
								src/saml2/attributemaps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/saml2/attributemaps/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| __author__ = 'rohe0002' | ||||
							
								
								
									
										326
									
								
								src/saml2/attributemaps/basic.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								src/saml2/attributemaps/basic.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,326 @@ | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", | ||||
|     "fro": { | ||||
|         'urn:mace:dir:attribute-def:aRecord': 'aRecord', | ||||
|         'urn:mace:dir:attribute-def:aliasedEntryName': 'aliasedEntryName', | ||||
|         'urn:mace:dir:attribute-def:aliasedObjectName': 'aliasedObjectName', | ||||
|         'urn:mace:dir:attribute-def:associatedDomain': 'associatedDomain', | ||||
|         'urn:mace:dir:attribute-def:associatedName': 'associatedName', | ||||
|         'urn:mace:dir:attribute-def:audio': 'audio', | ||||
|         'urn:mace:dir:attribute-def:authorityRevocationList': 'authorityRevocationList', | ||||
|         'urn:mace:dir:attribute-def:buildingName': 'buildingName', | ||||
|         'urn:mace:dir:attribute-def:businessCategory': 'businessCategory', | ||||
|         'urn:mace:dir:attribute-def:c': 'c', | ||||
|         'urn:mace:dir:attribute-def:cACertificate': 'cACertificate', | ||||
|         'urn:mace:dir:attribute-def:cNAMERecord': 'cNAMERecord', | ||||
|         'urn:mace:dir:attribute-def:carLicense': 'carLicense', | ||||
|         'urn:mace:dir:attribute-def:certificateRevocationList': 'certificateRevocationList', | ||||
|         'urn:mace:dir:attribute-def:cn': 'cn', | ||||
|         'urn:mace:dir:attribute-def:co': 'co', | ||||
|         'urn:mace:dir:attribute-def:commonName': 'commonName', | ||||
|         'urn:mace:dir:attribute-def:countryName': 'countryName', | ||||
|         'urn:mace:dir:attribute-def:crossCertificatePair': 'crossCertificatePair', | ||||
|         'urn:mace:dir:attribute-def:dITRedirect': 'dITRedirect', | ||||
|         'urn:mace:dir:attribute-def:dSAQuality': 'dSAQuality', | ||||
|         'urn:mace:dir:attribute-def:dc': 'dc', | ||||
|         'urn:mace:dir:attribute-def:deltaRevocationList': 'deltaRevocationList', | ||||
|         'urn:mace:dir:attribute-def:departmentNumber': 'departmentNumber', | ||||
|         'urn:mace:dir:attribute-def:description': 'description', | ||||
|         'urn:mace:dir:attribute-def:destinationIndicator': 'destinationIndicator', | ||||
|         'urn:mace:dir:attribute-def:displayName': 'displayName', | ||||
|         'urn:mace:dir:attribute-def:distinguishedName': 'distinguishedName', | ||||
|         'urn:mace:dir:attribute-def:dmdName': 'dmdName', | ||||
|         'urn:mace:dir:attribute-def:dnQualifier': 'dnQualifier', | ||||
|         'urn:mace:dir:attribute-def:documentAuthor': 'documentAuthor', | ||||
|         'urn:mace:dir:attribute-def:documentIdentifier': 'documentIdentifier', | ||||
|         'urn:mace:dir:attribute-def:documentLocation': 'documentLocation', | ||||
|         'urn:mace:dir:attribute-def:documentPublisher': 'documentPublisher', | ||||
|         'urn:mace:dir:attribute-def:documentTitle': 'documentTitle', | ||||
|         'urn:mace:dir:attribute-def:documentVersion': 'documentVersion', | ||||
|         'urn:mace:dir:attribute-def:domainComponent': 'domainComponent', | ||||
|         'urn:mace:dir:attribute-def:drink': 'drink', | ||||
|         'urn:mace:dir:attribute-def:eduOrgHomePageURI': 'eduOrgHomePageURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI': 'eduOrgIdentityAuthNPolicyURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgLegalName': 'eduOrgLegalName', | ||||
|         'urn:mace:dir:attribute-def:eduOrgSuperiorURI': 'eduOrgSuperiorURI', | ||||
|         'urn:mace:dir:attribute-def:eduOrgWhitePagesURI': 'eduOrgWhitePagesURI', | ||||
|         'urn:mace:dir:attribute-def:eduPersonAffiliation': 'eduPersonAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonEntitlement': 'eduPersonEntitlement', | ||||
|         'urn:mace:dir:attribute-def:eduPersonNickname': 'eduPersonNickname', | ||||
|         'urn:mace:dir:attribute-def:eduPersonOrgDN': 'eduPersonOrgDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonOrgUnitDN': 'eduPersonOrgUnitDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation': 'eduPersonPrimaryAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN': 'eduPersonPrimaryOrgUnitDN', | ||||
|         'urn:mace:dir:attribute-def:eduPersonPrincipalName': 'eduPersonPrincipalName', | ||||
|         'urn:mace:dir:attribute-def:eduPersonScopedAffiliation': 'eduPersonScopedAffiliation', | ||||
|         'urn:mace:dir:attribute-def:eduPersonTargetedID': 'eduPersonTargetedID', | ||||
|         'urn:mace:dir:attribute-def:email': 'email', | ||||
|         'urn:mace:dir:attribute-def:emailAddress': 'emailAddress', | ||||
|         'urn:mace:dir:attribute-def:employeeNumber': 'employeeNumber', | ||||
|         'urn:mace:dir:attribute-def:employeeType': 'employeeType', | ||||
|         'urn:mace:dir:attribute-def:enhancedSearchGuide': 'enhancedSearchGuide', | ||||
|         'urn:mace:dir:attribute-def:facsimileTelephoneNumber': 'facsimileTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:favouriteDrink': 'favouriteDrink', | ||||
|         'urn:mace:dir:attribute-def:fax': 'fax', | ||||
|         'urn:mace:dir:attribute-def:federationFeideSchemaVersion': 'federationFeideSchemaVersion', | ||||
|         'urn:mace:dir:attribute-def:friendlyCountryName': 'friendlyCountryName', | ||||
|         'urn:mace:dir:attribute-def:generationQualifier': 'generationQualifier', | ||||
|         'urn:mace:dir:attribute-def:givenName': 'givenName', | ||||
|         'urn:mace:dir:attribute-def:gn': 'gn', | ||||
|         'urn:mace:dir:attribute-def:homePhone': 'homePhone', | ||||
|         'urn:mace:dir:attribute-def:homePostalAddress': 'homePostalAddress', | ||||
|         'urn:mace:dir:attribute-def:homeTelephoneNumber': 'homeTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:host': 'host', | ||||
|         'urn:mace:dir:attribute-def:houseIdentifier': 'houseIdentifier', | ||||
|         'urn:mace:dir:attribute-def:info': 'info', | ||||
|         'urn:mace:dir:attribute-def:initials': 'initials', | ||||
|         'urn:mace:dir:attribute-def:internationaliSDNNumber': 'internationaliSDNNumber', | ||||
|         'urn:mace:dir:attribute-def:janetMailbox': 'janetMailbox', | ||||
|         'urn:mace:dir:attribute-def:jpegPhoto': 'jpegPhoto', | ||||
|         'urn:mace:dir:attribute-def:knowledgeInformation': 'knowledgeInformation', | ||||
|         'urn:mace:dir:attribute-def:l': 'l', | ||||
|         'urn:mace:dir:attribute-def:labeledURI': 'labeledURI', | ||||
|         'urn:mace:dir:attribute-def:localityName': 'localityName', | ||||
|         'urn:mace:dir:attribute-def:mDRecord': 'mDRecord', | ||||
|         'urn:mace:dir:attribute-def:mXRecord': 'mXRecord', | ||||
|         'urn:mace:dir:attribute-def:mail': 'mail', | ||||
|         'urn:mace:dir:attribute-def:mailPreferenceOption': 'mailPreferenceOption', | ||||
|         'urn:mace:dir:attribute-def:manager': 'manager', | ||||
|         'urn:mace:dir:attribute-def:member': 'member', | ||||
|         'urn:mace:dir:attribute-def:mobile': 'mobile', | ||||
|         'urn:mace:dir:attribute-def:mobileTelephoneNumber': 'mobileTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:nSRecord': 'nSRecord', | ||||
|         'urn:mace:dir:attribute-def:name': 'name', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgAcronym': 'norEduOrgAcronym', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgNIN': 'norEduOrgNIN', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgSchemaVersion': 'norEduOrgSchemaVersion', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier': 'norEduOrgUniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUniqueNumber': 'norEduOrgUniqueNumber', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier': 'norEduOrgUnitUniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber': 'norEduOrgUnitUniqueNumber', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonBirthDate': 'norEduPersonBirthDate', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonLIN': 'norEduPersonLIN', | ||||
|         'urn:mace:dir:attribute-def:norEduPersonNIN': 'norEduPersonNIN', | ||||
|         'urn:mace:dir:attribute-def:o': 'o', | ||||
|         'urn:mace:dir:attribute-def:objectClass': 'objectClass', | ||||
|         'urn:mace:dir:attribute-def:organizationName': 'organizationName', | ||||
|         'urn:mace:dir:attribute-def:organizationalStatus': 'organizationalStatus', | ||||
|         'urn:mace:dir:attribute-def:organizationalUnitName': 'organizationalUnitName', | ||||
|         'urn:mace:dir:attribute-def:otherMailbox': 'otherMailbox', | ||||
|         'urn:mace:dir:attribute-def:ou': 'ou', | ||||
|         'urn:mace:dir:attribute-def:owner': 'owner', | ||||
|         'urn:mace:dir:attribute-def:pager': 'pager', | ||||
|         'urn:mace:dir:attribute-def:pagerTelephoneNumber': 'pagerTelephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:personalSignature': 'personalSignature', | ||||
|         'urn:mace:dir:attribute-def:personalTitle': 'personalTitle', | ||||
|         'urn:mace:dir:attribute-def:photo': 'photo', | ||||
|         'urn:mace:dir:attribute-def:physicalDeliveryOfficeName': 'physicalDeliveryOfficeName', | ||||
|         'urn:mace:dir:attribute-def:pkcs9email': 'pkcs9email', | ||||
|         'urn:mace:dir:attribute-def:postOfficeBox': 'postOfficeBox', | ||||
|         'urn:mace:dir:attribute-def:postalAddress': 'postalAddress', | ||||
|         'urn:mace:dir:attribute-def:postalCode': 'postalCode', | ||||
|         'urn:mace:dir:attribute-def:preferredDeliveryMethod': 'preferredDeliveryMethod', | ||||
|         'urn:mace:dir:attribute-def:preferredLanguage': 'preferredLanguage', | ||||
|         'urn:mace:dir:attribute-def:presentationAddress': 'presentationAddress', | ||||
|         'urn:mace:dir:attribute-def:protocolInformation': 'protocolInformation', | ||||
|         'urn:mace:dir:attribute-def:pseudonym': 'pseudonym', | ||||
|         'urn:mace:dir:attribute-def:registeredAddress': 'registeredAddress', | ||||
|         'urn:mace:dir:attribute-def:rfc822Mailbox': 'rfc822Mailbox', | ||||
|         'urn:mace:dir:attribute-def:roleOccupant': 'roleOccupant', | ||||
|         'urn:mace:dir:attribute-def:roomNumber': 'roomNumber', | ||||
|         'urn:mace:dir:attribute-def:sOARecord': 'sOARecord', | ||||
|         'urn:mace:dir:attribute-def:searchGuide': 'searchGuide', | ||||
|         'urn:mace:dir:attribute-def:secretary': 'secretary', | ||||
|         'urn:mace:dir:attribute-def:seeAlso': 'seeAlso', | ||||
|         'urn:mace:dir:attribute-def:serialNumber': 'serialNumber', | ||||
|         'urn:mace:dir:attribute-def:singleLevelQuality': 'singleLevelQuality', | ||||
|         'urn:mace:dir:attribute-def:sn': 'sn', | ||||
|         'urn:mace:dir:attribute-def:st': 'st', | ||||
|         'urn:mace:dir:attribute-def:stateOrProvinceName': 'stateOrProvinceName', | ||||
|         'urn:mace:dir:attribute-def:street': 'street', | ||||
|         'urn:mace:dir:attribute-def:streetAddress': 'streetAddress', | ||||
|         'urn:mace:dir:attribute-def:subtreeMaximumQuality': 'subtreeMaximumQuality', | ||||
|         'urn:mace:dir:attribute-def:subtreeMinimumQuality': 'subtreeMinimumQuality', | ||||
|         'urn:mace:dir:attribute-def:supportedAlgorithms': 'supportedAlgorithms', | ||||
|         'urn:mace:dir:attribute-def:supportedApplicationContext': 'supportedApplicationContext', | ||||
|         'urn:mace:dir:attribute-def:surname': 'surname', | ||||
|         'urn:mace:dir:attribute-def:telephoneNumber': 'telephoneNumber', | ||||
|         'urn:mace:dir:attribute-def:teletexTerminalIdentifier': 'teletexTerminalIdentifier', | ||||
|         'urn:mace:dir:attribute-def:telexNumber': 'telexNumber', | ||||
|         'urn:mace:dir:attribute-def:textEncodedORAddress': 'textEncodedORAddress', | ||||
|         'urn:mace:dir:attribute-def:title': 'title', | ||||
|         'urn:mace:dir:attribute-def:uid': 'uid', | ||||
|         'urn:mace:dir:attribute-def:uniqueIdentifier': 'uniqueIdentifier', | ||||
|         'urn:mace:dir:attribute-def:uniqueMember': 'uniqueMember', | ||||
|         'urn:mace:dir:attribute-def:userCertificate': 'userCertificate', | ||||
|         'urn:mace:dir:attribute-def:userClass': 'userClass', | ||||
|         'urn:mace:dir:attribute-def:userPKCS12': 'userPKCS12', | ||||
|         'urn:mace:dir:attribute-def:userPassword': 'userPassword', | ||||
|         'urn:mace:dir:attribute-def:userSMIMECertificate': 'userSMIMECertificate', | ||||
|         'urn:mace:dir:attribute-def:userid': 'userid', | ||||
|         'urn:mace:dir:attribute-def:x121Address': 'x121Address', | ||||
|         'urn:mace:dir:attribute-def:x500UniqueIdentifier': 'x500UniqueIdentifier', | ||||
|         }, | ||||
|     "to": { | ||||
|         'aRecord': 'urn:mace:dir:attribute-def:aRecord', | ||||
|         'aliasedEntryName': 'urn:mace:dir:attribute-def:aliasedEntryName', | ||||
|         'aliasedObjectName': 'urn:mace:dir:attribute-def:aliasedObjectName', | ||||
|         'associatedDomain': 'urn:mace:dir:attribute-def:associatedDomain', | ||||
|         'associatedName': 'urn:mace:dir:attribute-def:associatedName', | ||||
|         'audio': 'urn:mace:dir:attribute-def:audio', | ||||
|         'authorityRevocationList': 'urn:mace:dir:attribute-def:authorityRevocationList', | ||||
|         'buildingName': 'urn:mace:dir:attribute-def:buildingName', | ||||
|         'businessCategory': 'urn:mace:dir:attribute-def:businessCategory', | ||||
|         'c': 'urn:mace:dir:attribute-def:c', | ||||
|         'cACertificate': 'urn:mace:dir:attribute-def:cACertificate', | ||||
|         'cNAMERecord': 'urn:mace:dir:attribute-def:cNAMERecord', | ||||
|         'carLicense': 'urn:mace:dir:attribute-def:carLicense', | ||||
|         'certificateRevocationList': 'urn:mace:dir:attribute-def:certificateRevocationList', | ||||
|         'cn': 'urn:mace:dir:attribute-def:cn', | ||||
|         'co': 'urn:mace:dir:attribute-def:co', | ||||
|         'commonName': 'urn:mace:dir:attribute-def:commonName', | ||||
|         'countryName': 'urn:mace:dir:attribute-def:countryName', | ||||
|         'crossCertificatePair': 'urn:mace:dir:attribute-def:crossCertificatePair', | ||||
|         'dITRedirect': 'urn:mace:dir:attribute-def:dITRedirect', | ||||
|         'dSAQuality': 'urn:mace:dir:attribute-def:dSAQuality', | ||||
|         'dc': 'urn:mace:dir:attribute-def:dc', | ||||
|         'deltaRevocationList': 'urn:mace:dir:attribute-def:deltaRevocationList', | ||||
|         'departmentNumber': 'urn:mace:dir:attribute-def:departmentNumber', | ||||
|         'description': 'urn:mace:dir:attribute-def:description', | ||||
|         'destinationIndicator': 'urn:mace:dir:attribute-def:destinationIndicator', | ||||
|         'displayName': 'urn:mace:dir:attribute-def:displayName', | ||||
|         'distinguishedName': 'urn:mace:dir:attribute-def:distinguishedName', | ||||
|         'dmdName': 'urn:mace:dir:attribute-def:dmdName', | ||||
|         'dnQualifier': 'urn:mace:dir:attribute-def:dnQualifier', | ||||
|         'documentAuthor': 'urn:mace:dir:attribute-def:documentAuthor', | ||||
|         'documentIdentifier': 'urn:mace:dir:attribute-def:documentIdentifier', | ||||
|         'documentLocation': 'urn:mace:dir:attribute-def:documentLocation', | ||||
|         'documentPublisher': 'urn:mace:dir:attribute-def:documentPublisher', | ||||
|         'documentTitle': 'urn:mace:dir:attribute-def:documentTitle', | ||||
|         'documentVersion': 'urn:mace:dir:attribute-def:documentVersion', | ||||
|         'domainComponent': 'urn:mace:dir:attribute-def:domainComponent', | ||||
|         'drink': 'urn:mace:dir:attribute-def:drink', | ||||
|         'eduOrgHomePageURI': 'urn:mace:dir:attribute-def:eduOrgHomePageURI', | ||||
|         'eduOrgIdentityAuthNPolicyURI': 'urn:mace:dir:attribute-def:eduOrgIdentityAuthNPolicyURI', | ||||
|         'eduOrgLegalName': 'urn:mace:dir:attribute-def:eduOrgLegalName', | ||||
|         'eduOrgSuperiorURI': 'urn:mace:dir:attribute-def:eduOrgSuperiorURI', | ||||
|         'eduOrgWhitePagesURI': 'urn:mace:dir:attribute-def:eduOrgWhitePagesURI', | ||||
|         'eduPersonAffiliation': 'urn:mace:dir:attribute-def:eduPersonAffiliation', | ||||
|         'eduPersonEntitlement': 'urn:mace:dir:attribute-def:eduPersonEntitlement', | ||||
|         'eduPersonNickname': 'urn:mace:dir:attribute-def:eduPersonNickname', | ||||
|         'eduPersonOrgDN': 'urn:mace:dir:attribute-def:eduPersonOrgDN', | ||||
|         'eduPersonOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonOrgUnitDN', | ||||
|         'eduPersonPrimaryAffiliation': 'urn:mace:dir:attribute-def:eduPersonPrimaryAffiliation', | ||||
|         'eduPersonPrimaryOrgUnitDN': 'urn:mace:dir:attribute-def:eduPersonPrimaryOrgUnitDN', | ||||
|         'eduPersonPrincipalName': 'urn:mace:dir:attribute-def:eduPersonPrincipalName', | ||||
|         'eduPersonScopedAffiliation': 'urn:mace:dir:attribute-def:eduPersonScopedAffiliation', | ||||
|         'eduPersonTargetedID': 'urn:mace:dir:attribute-def:eduPersonTargetedID', | ||||
|         'email': 'urn:mace:dir:attribute-def:email', | ||||
|         'emailAddress': 'urn:mace:dir:attribute-def:emailAddress', | ||||
|         'employeeNumber': 'urn:mace:dir:attribute-def:employeeNumber', | ||||
|         'employeeType': 'urn:mace:dir:attribute-def:employeeType', | ||||
|         'enhancedSearchGuide': 'urn:mace:dir:attribute-def:enhancedSearchGuide', | ||||
|         'facsimileTelephoneNumber': 'urn:mace:dir:attribute-def:facsimileTelephoneNumber', | ||||
|         'favouriteDrink': 'urn:mace:dir:attribute-def:favouriteDrink', | ||||
|         'fax': 'urn:mace:dir:attribute-def:fax', | ||||
|         'federationFeideSchemaVersion': 'urn:mace:dir:attribute-def:federationFeideSchemaVersion', | ||||
|         'friendlyCountryName': 'urn:mace:dir:attribute-def:friendlyCountryName', | ||||
|         'generationQualifier': 'urn:mace:dir:attribute-def:generationQualifier', | ||||
|         'givenName': 'urn:mace:dir:attribute-def:givenName', | ||||
|         'gn': 'urn:mace:dir:attribute-def:gn', | ||||
|         'homePhone': 'urn:mace:dir:attribute-def:homePhone', | ||||
|         'homePostalAddress': 'urn:mace:dir:attribute-def:homePostalAddress', | ||||
|         'homeTelephoneNumber': 'urn:mace:dir:attribute-def:homeTelephoneNumber', | ||||
|         'host': 'urn:mace:dir:attribute-def:host', | ||||
|         'houseIdentifier': 'urn:mace:dir:attribute-def:houseIdentifier', | ||||
|         'info': 'urn:mace:dir:attribute-def:info', | ||||
|         'initials': 'urn:mace:dir:attribute-def:initials', | ||||
|         'internationaliSDNNumber': 'urn:mace:dir:attribute-def:internationaliSDNNumber', | ||||
|         'janetMailbox': 'urn:mace:dir:attribute-def:janetMailbox', | ||||
|         'jpegPhoto': 'urn:mace:dir:attribute-def:jpegPhoto', | ||||
|         'knowledgeInformation': 'urn:mace:dir:attribute-def:knowledgeInformation', | ||||
|         'l': 'urn:mace:dir:attribute-def:l', | ||||
|         'labeledURI': 'urn:mace:dir:attribute-def:labeledURI', | ||||
|         'localityName': 'urn:mace:dir:attribute-def:localityName', | ||||
|         'mDRecord': 'urn:mace:dir:attribute-def:mDRecord', | ||||
|         'mXRecord': 'urn:mace:dir:attribute-def:mXRecord', | ||||
|         'mail': 'urn:mace:dir:attribute-def:mail', | ||||
|         'mailPreferenceOption': 'urn:mace:dir:attribute-def:mailPreferenceOption', | ||||
|         'manager': 'urn:mace:dir:attribute-def:manager', | ||||
|         'member': 'urn:mace:dir:attribute-def:member', | ||||
|         'mobile': 'urn:mace:dir:attribute-def:mobile', | ||||
|         'mobileTelephoneNumber': 'urn:mace:dir:attribute-def:mobileTelephoneNumber', | ||||
|         'nSRecord': 'urn:mace:dir:attribute-def:nSRecord', | ||||
|         'name': 'urn:mace:dir:attribute-def:name', | ||||
|         'norEduOrgAcronym': 'urn:mace:dir:attribute-def:norEduOrgAcronym', | ||||
|         'norEduOrgNIN': 'urn:mace:dir:attribute-def:norEduOrgNIN', | ||||
|         'norEduOrgSchemaVersion': 'urn:mace:dir:attribute-def:norEduOrgSchemaVersion', | ||||
|         'norEduOrgUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUniqueIdentifier', | ||||
|         'norEduOrgUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUniqueNumber', | ||||
|         'norEduOrgUnitUniqueIdentifier': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueIdentifier', | ||||
|         'norEduOrgUnitUniqueNumber': 'urn:mace:dir:attribute-def:norEduOrgUnitUniqueNumber', | ||||
|         'norEduPersonBirthDate': 'urn:mace:dir:attribute-def:norEduPersonBirthDate', | ||||
|         'norEduPersonLIN': 'urn:mace:dir:attribute-def:norEduPersonLIN', | ||||
|         'norEduPersonNIN': 'urn:mace:dir:attribute-def:norEduPersonNIN', | ||||
|         'o': 'urn:mace:dir:attribute-def:o', | ||||
|         'objectClass': 'urn:mace:dir:attribute-def:objectClass', | ||||
|         'organizationName': 'urn:mace:dir:attribute-def:organizationName', | ||||
|         'organizationalStatus': 'urn:mace:dir:attribute-def:organizationalStatus', | ||||
|         'organizationalUnitName': 'urn:mace:dir:attribute-def:organizationalUnitName', | ||||
|         'otherMailbox': 'urn:mace:dir:attribute-def:otherMailbox', | ||||
|         'ou': 'urn:mace:dir:attribute-def:ou', | ||||
|         'owner': 'urn:mace:dir:attribute-def:owner', | ||||
|         'pager': 'urn:mace:dir:attribute-def:pager', | ||||
|         'pagerTelephoneNumber': 'urn:mace:dir:attribute-def:pagerTelephoneNumber', | ||||
|         'personalSignature': 'urn:mace:dir:attribute-def:personalSignature', | ||||
|         'personalTitle': 'urn:mace:dir:attribute-def:personalTitle', | ||||
|         'photo': 'urn:mace:dir:attribute-def:photo', | ||||
|         'physicalDeliveryOfficeName': 'urn:mace:dir:attribute-def:physicalDeliveryOfficeName', | ||||
|         'pkcs9email': 'urn:mace:dir:attribute-def:pkcs9email', | ||||
|         'postOfficeBox': 'urn:mace:dir:attribute-def:postOfficeBox', | ||||
|         'postalAddress': 'urn:mace:dir:attribute-def:postalAddress', | ||||
|         'postalCode': 'urn:mace:dir:attribute-def:postalCode', | ||||
|         'preferredDeliveryMethod': 'urn:mace:dir:attribute-def:preferredDeliveryMethod', | ||||
|         'preferredLanguage': 'urn:mace:dir:attribute-def:preferredLanguage', | ||||
|         'presentationAddress': 'urn:mace:dir:attribute-def:presentationAddress', | ||||
|         'protocolInformation': 'urn:mace:dir:attribute-def:protocolInformation', | ||||
|         'pseudonym': 'urn:mace:dir:attribute-def:pseudonym', | ||||
|         'registeredAddress': 'urn:mace:dir:attribute-def:registeredAddress', | ||||
|         'rfc822Mailbox': 'urn:mace:dir:attribute-def:rfc822Mailbox', | ||||
|         'roleOccupant': 'urn:mace:dir:attribute-def:roleOccupant', | ||||
|         'roomNumber': 'urn:mace:dir:attribute-def:roomNumber', | ||||
|         'sOARecord': 'urn:mace:dir:attribute-def:sOARecord', | ||||
|         'searchGuide': 'urn:mace:dir:attribute-def:searchGuide', | ||||
|         'secretary': 'urn:mace:dir:attribute-def:secretary', | ||||
|         'seeAlso': 'urn:mace:dir:attribute-def:seeAlso', | ||||
|         'serialNumber': 'urn:mace:dir:attribute-def:serialNumber', | ||||
|         'singleLevelQuality': 'urn:mace:dir:attribute-def:singleLevelQuality', | ||||
|         'sn': 'urn:mace:dir:attribute-def:sn', | ||||
|         'st': 'urn:mace:dir:attribute-def:st', | ||||
|         'stateOrProvinceName': 'urn:mace:dir:attribute-def:stateOrProvinceName', | ||||
|         'street': 'urn:mace:dir:attribute-def:street', | ||||
|         'streetAddress': 'urn:mace:dir:attribute-def:streetAddress', | ||||
|         'subtreeMaximumQuality': 'urn:mace:dir:attribute-def:subtreeMaximumQuality', | ||||
|         'subtreeMinimumQuality': 'urn:mace:dir:attribute-def:subtreeMinimumQuality', | ||||
|         'supportedAlgorithms': 'urn:mace:dir:attribute-def:supportedAlgorithms', | ||||
|         'supportedApplicationContext': 'urn:mace:dir:attribute-def:supportedApplicationContext', | ||||
|         'surname': 'urn:mace:dir:attribute-def:surname', | ||||
|         'telephoneNumber': 'urn:mace:dir:attribute-def:telephoneNumber', | ||||
|         'teletexTerminalIdentifier': 'urn:mace:dir:attribute-def:teletexTerminalIdentifier', | ||||
|         'telexNumber': 'urn:mace:dir:attribute-def:telexNumber', | ||||
|         'textEncodedORAddress': 'urn:mace:dir:attribute-def:textEncodedORAddress', | ||||
|         'title': 'urn:mace:dir:attribute-def:title', | ||||
|         'uid': 'urn:mace:dir:attribute-def:uid', | ||||
|         'uniqueIdentifier': 'urn:mace:dir:attribute-def:uniqueIdentifier', | ||||
|         'uniqueMember': 'urn:mace:dir:attribute-def:uniqueMember', | ||||
|         'userCertificate': 'urn:mace:dir:attribute-def:userCertificate', | ||||
|         'userClass': 'urn:mace:dir:attribute-def:userClass', | ||||
|         'userPKCS12': 'urn:mace:dir:attribute-def:userPKCS12', | ||||
|         'userPassword': 'urn:mace:dir:attribute-def:userPassword', | ||||
|         'userSMIMECertificate': 'urn:mace:dir:attribute-def:userSMIMECertificate', | ||||
|         'userid': 'urn:mace:dir:attribute-def:userid', | ||||
|         'x121Address': 'urn:mace:dir:attribute-def:x121Address', | ||||
|         'x500UniqueIdentifier': 'urn:mace:dir:attribute-def:x500UniqueIdentifier', | ||||
|     } | ||||
| } | ||||
							
								
								
									
										199
									
								
								src/saml2/attributemaps/saml_uri.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								src/saml2/attributemaps/saml_uri.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| __author__ = 'rolandh' | ||||
|  | ||||
| EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." | ||||
| X500ATTR_OID = "urn:oid:2.5.4." | ||||
| NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." | ||||
| NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.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." | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", | ||||
|     "fro": { | ||||
|         EDUPERSON_OID+'2': 'eduPersonNickname', | ||||
|         EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', | ||||
|         EDUPERSON_OID+'11': 'eduPersonAssurance', | ||||
|         EDUPERSON_OID+'10': 'eduPersonTargetedID', | ||||
|         EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', | ||||
|         NOREDUPERSON_OID+'6': 'norEduOrgAcronym', | ||||
|         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+'9': 'federationFeideSchemaVersion', | ||||
|         X500ATTR_OID+'53': 'deltaRevocationList', | ||||
|         X500ATTR_OID+'52': 'supportedAlgorithms', | ||||
|         X500ATTR_OID+'51': 'houseIdentifier', | ||||
|         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', | ||||
|         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', | ||||
|         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', | ||||
|     }, | ||||
|     "to": { | ||||
|         'roleOccupant': X500ATTR_OID+'33', | ||||
|         'gn': X500ATTR_OID+'42', | ||||
|         'norEduPersonNIN': NOREDUPERSON_OID+'5', | ||||
|         'title': X500ATTR_OID+'12', | ||||
|         'facsimileTelephoneNumber': X500ATTR_OID+'23', | ||||
|         'mail': UCL_DIR_PILOT+'3', | ||||
|         'postOfficeBox': X500ATTR_OID+'18', | ||||
|         'fax': X500ATTR_OID+'23', | ||||
|         'telephoneNumber': X500ATTR_OID+'20', | ||||
|         'norEduPersonBirthDate': NOREDUPERSON_OID+'3', | ||||
|         'rfc822Mailbox': UCL_DIR_PILOT+'3', | ||||
|         'dc': UCL_DIR_PILOT+'25', | ||||
|         'countryName': X500ATTR_OID+'6', | ||||
|         'emailAddress': PKCS_9+'1', | ||||
|         'employeeNumber': NETSCAPE_LDAP+'3', | ||||
|         'organizationName': X500ATTR_OID+'10', | ||||
|         'eduPersonAssurance': EDUPERSON_OID+'11', | ||||
|         'norEduOrgAcronym': NOREDUPERSON_OID+'6', | ||||
|         'registeredAddress': X500ATTR_OID+'26', | ||||
|         'physicalDeliveryOfficeName': X500ATTR_OID+'19', | ||||
|         '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', | ||||
|         'eduPersonPrincipalName': EDUPERSON_OID+'6', | ||||
|         'edupersonprincipalname': 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', | ||||
|         'displayName': NETSCAPE_LDAP+'241', | ||||
|         'businessCategory': X500ATTR_OID+'15', | ||||
|         'serialNumber': X500ATTR_OID+'5', | ||||
|         'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7', | ||||
|         'st': X500ATTR_OID+'8', | ||||
|         'carLicense': NETSCAPE_LDAP+'1', | ||||
|         'presentationAddress': X500ATTR_OID+'29', | ||||
|         'sn': X500ATTR_OID+'4', | ||||
|         'domainComponent': UCL_DIR_PILOT+'25', | ||||
|         'labeledURI': UMICH+'57', | ||||
|         'uid': UCL_DIR_PILOT+'1' | ||||
|     } | ||||
| }   | ||||
							
								
								
									
										190
									
								
								src/saml2/attributemaps/shibboleth_uri.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/saml2/attributemaps/shibboleth_uri.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1." | ||||
| X500ATTR = "urn:oid:2.5.4." | ||||
| NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1." | ||||
| NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.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." | ||||
|  | ||||
| MAP = { | ||||
|     "identifier": "urn:mace:shibboleth:1.0:attributeNamespace:uri", | ||||
|     "fro": { | ||||
|         EDUPERSON_OID+'2': 'eduPersonNickname', | ||||
|         EDUPERSON_OID+'9': 'eduPersonScopedAffiliation', | ||||
|         EDUPERSON_OID+'11': 'eduPersonAssurance', | ||||
|         EDUPERSON_OID+'10': 'eduPersonTargetedID', | ||||
|         EDUPERSON_OID+'4': 'eduPersonOrgUnitDN', | ||||
|         NOREDUPERSON_OID+'6': 'norEduOrgAcronym', | ||||
|         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+'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', | ||||
|         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+'25': 'dc', | ||||
|         X500ATTR+'40': 'crossCertificatePair', | ||||
|         X500ATTR+'42': 'givenName', | ||||
|         X500ATTR+'43': 'initials', | ||||
|         X500ATTR+'44': 'generationQualifier', | ||||
|         X500ATTR+'45': 'x500UniqueIdentifier', | ||||
|         X500ATTR+'46': 'dnQualifier', | ||||
|         X500ATTR+'47': 'enhancedSearchGuide', | ||||
|         X500ATTR+'48': 'protocolInformation', | ||||
|         X500ATTR+'54': 'dmdName', | ||||
|         NETSCAPE_LDAP+'4': 'employeeType', | ||||
|         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":{ | ||||
|         'roleOccupant': X500ATTR+'33', | ||||
|         'gn': X500ATTR+'42', | ||||
|         'norEduPersonNIN': NOREDUPERSON_OID+'5', | ||||
|         'title': X500ATTR+'12', | ||||
|         'facsimileTelephoneNumber': X500ATTR+'23', | ||||
|         'mail': UCL_DIR_PILOT+'3', | ||||
|         'postOfficeBox': X500ATTR+'18', | ||||
|         'fax': X500ATTR+'23', | ||||
|         'telephoneNumber': X500ATTR+'20', | ||||
|         'norEduPersonBirthDate': NOREDUPERSON_OID+'3', | ||||
|         'rfc822Mailbox': UCL_DIR_PILOT+'3', | ||||
|         'dc': UCL_DIR_PILOT+'25', | ||||
|         'countryName': X500ATTR+'6', | ||||
|         'emailAddress': PKCS_9+'1', | ||||
|         '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', | ||||
|         'userSMIMECertificate': NETSCAPE_LDAP+'40', | ||||
|         'member': X500ATTR+'31', | ||||
|         'streetAddress': X500ATTR+'9', | ||||
|         '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', | ||||
|         'st': X500ATTR+'8', | ||||
|         'carLicense': NETSCAPE_LDAP+'1', | ||||
|         'presentationAddress': X500ATTR+'29', | ||||
|         'sn': X500ATTR+'4', | ||||
|         'domainComponent': UCL_DIR_PILOT+'25', | ||||
|     } | ||||
| } | ||||
							
								
								
									
										252
									
								
								src/saml2/binding.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								src/saml2/binding.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2010-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """Contains classes and functions that are necessary to implement  | ||||
| different bindings. | ||||
|  | ||||
| Bindings normally consists of three parts: | ||||
| - rules about what to send  | ||||
| - how to package the information | ||||
| - which protocol to use | ||||
| """ | ||||
| import saml2 | ||||
| import base64 | ||||
| import urllib | ||||
| from saml2.s_utils import deflate_and_base64_encode | ||||
| from saml2.soap import SOAPClient, HTTPClient | ||||
|  | ||||
| try: | ||||
|     from xml.etree import cElementTree as ElementTree | ||||
| except ImportError: | ||||
|     try: | ||||
|         import cElementTree as ElementTree | ||||
|     except ImportError: | ||||
|         from elementtree import ElementTree | ||||
|  | ||||
| NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/" | ||||
| FORM_SPEC = """<form method="post" action="%s"> | ||||
|    <input type="hidden" name="%s" value="%s" /> | ||||
|    <input type="hidden" name="RelayState" value="%s" /> | ||||
|    <input type="submit" value="Submit" /> | ||||
| </form>""" | ||||
|  | ||||
| def http_post_message(message, location, relay_state="", typ="SAMLRequest"): | ||||
|     """The HTTP POST binding defines a mechanism by which SAML protocol  | ||||
|     messages may be transmitted within the base64-encoded content of a | ||||
|     HTML form control. | ||||
|      | ||||
|     :param message: The message | ||||
|     :param location: Where the form should be posted to | ||||
|     :param relay_state: for preserving and conveying state information | ||||
|     :return: A tuple containing header information and a HTML message. | ||||
|     """ | ||||
|     response = ["<head>", """<title>SAML 2.0 POST</title>""", "</head><body>"] | ||||
|  | ||||
|     if not isinstance(message, basestring): | ||||
|         message = "%s" % (message,) | ||||
|          | ||||
|     response.append(FORM_SPEC % (location, typ, base64.b64encode(message), | ||||
|                                 relay_state)) | ||||
|                                  | ||||
|     response.append("""<script type="text/javascript">""") | ||||
|     response.append("     window.onload = function ()") | ||||
|     response.append(" { document.forms[0].submit(); ") | ||||
|     response.append("""</script>""") | ||||
|     response.append("</body>") | ||||
|      | ||||
|     return [("Content-type", "text/html")], response | ||||
|      | ||||
| def http_redirect_message(message, location, relay_state="",  | ||||
|                             typ="SAMLRequest"): | ||||
|     """The HTTP Redirect binding defines a mechanism by which SAML protocol  | ||||
|     messages can be transmitted within URL parameters. | ||||
|     Messages are encoded for use with this binding using a URL encoding  | ||||
|     technique, and transmitted using the HTTP GET method.  | ||||
|      | ||||
|     The DEFLATE Encoding is used in this function. | ||||
|      | ||||
|     :param message: The message | ||||
|     :param location: Where the message should be posted to | ||||
|     :param relay_state: for preserving and conveying state information | ||||
|     :return: A tuple containing header information and a HTML message. | ||||
|     """ | ||||
|      | ||||
|     if not isinstance(message, basestring): | ||||
|         message = "%s" % (message,) | ||||
|          | ||||
|     args = {typ: deflate_and_base64_encode(message)} | ||||
|      | ||||
|     if relay_state: | ||||
|         args["RelayState"] = relay_state | ||||
|          | ||||
|     login_url = "?".join([location, urllib.urlencode(args)]) | ||||
|     headers = [('Location', login_url)] | ||||
|     body = [""] | ||||
|      | ||||
|     return headers, body | ||||
|  | ||||
| def make_soap_enveloped_saml_thingy(thingy, header_parts=None): | ||||
|     """ Returns a soap envelope containing a SAML request | ||||
|     as a text string. | ||||
|  | ||||
|     :param thingy: The SAML thingy | ||||
|     :return: The SOAP envelope as a string | ||||
|     """ | ||||
|     envelope = ElementTree.Element('') | ||||
|     envelope.tag = '{%s}Envelope' % NAMESPACE | ||||
|  | ||||
|     if header_parts: | ||||
|         header = ElementTree.Element('') | ||||
|         header.tag = '{%s}Header' % NAMESPACE | ||||
|         envelope.append(header) | ||||
|         for part in header_parts: | ||||
|             part.become_child_element_of(header) | ||||
|  | ||||
|     body = ElementTree.Element('') | ||||
|     body.tag = '{%s}Body' % NAMESPACE | ||||
|     envelope.append(body) | ||||
|  | ||||
|     thingy.become_child_element_of(body) | ||||
|  | ||||
|     return ElementTree.tostring(envelope, encoding="UTF-8") | ||||
|  | ||||
| def http_soap_message(message): | ||||
|     return ([("Content-type", "application/soap+xml")], | ||||
|             make_soap_enveloped_saml_thingy(message)) | ||||
|      | ||||
| def http_paos(message, extra=None): | ||||
|     return ([("Content-type", "application/soap+xml")], | ||||
|             make_soap_enveloped_saml_thingy(message, extra)) | ||||
|      | ||||
| def parse_soap_enveloped_saml(text, body_class, header_class=None): | ||||
|     """Parses a SOAP enveloped SAML thing and returns header parts and body | ||||
|  | ||||
|     :param text: The SOAP object as XML  | ||||
|     :return: header parts and body as saml.samlbase instances | ||||
|     """ | ||||
|     envelope = ElementTree.fromstring(text) | ||||
|     assert envelope.tag == '{%s}Envelope' % NAMESPACE | ||||
|  | ||||
|     #print len(envelope) | ||||
|     body = None | ||||
|     header = {} | ||||
|     for part in envelope: | ||||
|         #print ">",part.tag | ||||
|         if part.tag == '{%s}Body' % NAMESPACE: | ||||
|             for sub in part: | ||||
|                 try: | ||||
|                     body = saml2.create_class_from_element_tree(body_class, sub) | ||||
|                 except Exception: | ||||
|                     raise Exception( | ||||
|                             "Wrong body type (%s) in SOAP envelope" % sub.tag) | ||||
|         elif part.tag == '{%s}Header' % NAMESPACE: | ||||
|             if not header_class: | ||||
|                 raise Exception("Header where I didn't expect one") | ||||
|             #print "--- HEADER ---" | ||||
|             for sub in part: | ||||
|                 #print ">>",sub.tag | ||||
|                 for klass in header_class: | ||||
|                     #print "?{%s}%s" % (klass.c_namespace,klass.c_tag) | ||||
|                     if sub.tag == "{%s}%s" % (klass.c_namespace, klass.c_tag): | ||||
|                         header[sub.tag] = \ | ||||
|                             saml2.create_class_from_element_tree(klass, sub) | ||||
|                         break | ||||
|                          | ||||
|     return body, header | ||||
|  | ||||
| # ----------------------------------------------------------------------------- | ||||
| # def send_using_http_get(request, destination, key_file=None, cert_file=None,  | ||||
| #                     log=None): | ||||
| #  | ||||
| #      | ||||
| #     http = HTTPClient(destination, key_file, cert_file, log) | ||||
| #     if log: log.info("HTTP client initiated") | ||||
| #  | ||||
| #     try: | ||||
| #         response = http.get() | ||||
| #     except Exception, exc: | ||||
| #         if log: log.info("HTTPClient exception: %s" % (exc,)) | ||||
| #         return None | ||||
| #  | ||||
| #     if log: log.info("HTTP request sent and got response: %s" % response) | ||||
| #  | ||||
| #     return response | ||||
|  | ||||
| def send_using_http_post(request, destination, relay_state, key_file=None,  | ||||
|                         cert_file=None, log=None, ca_certs=""): | ||||
|  | ||||
|     http = HTTPClient(destination, key_file, cert_file, log, ca_certs) | ||||
|     if log: | ||||
|         log.info("HTTP client initiated") | ||||
|  | ||||
|     if not isinstance(request, basestring): | ||||
|         request = "%s" % (request,) | ||||
|          | ||||
|     (headers, message) = http_post_message(request, destination, relay_state) | ||||
|     try: | ||||
|         response = http.post(message, headers) | ||||
|     except Exception, exc: | ||||
|         if log: | ||||
|             log.info("HTTPClient exception: %s" % (exc,)) | ||||
|         return None | ||||
|  | ||||
|     if log: | ||||
|         log.info("HTTP request sent and got response: %s" % response) | ||||
|  | ||||
|     return response | ||||
|  | ||||
| def send_using_soap(message, destination, key_file=None, cert_file=None,  | ||||
|                     log=None, ca_certs=""): | ||||
|     """  | ||||
|     Actual construction of the SOAP message is done by the SOAPClient | ||||
|      | ||||
|     :param message: The SAML message to send | ||||
|     :param destination: Where to send the message | ||||
|     :param key_file: If HTTPS this is the client certificate | ||||
|     :param cert_file: If HTTPS this a certificates file  | ||||
|     :param log: A log function to use for logging | ||||
|     :param ca_certs: CA certificates to use when verifying server certificates | ||||
|     :return: The response gotten from the other side interpreted by the  | ||||
|         SOAPClient | ||||
|     """ | ||||
|     soapclient = SOAPClient(destination, key_file, cert_file, log, ca_certs) | ||||
|     if log: | ||||
|         log.info("SOAP client initiated") | ||||
|     try: | ||||
|         response = soapclient.send(message) | ||||
|     except Exception, exc: | ||||
|         if log: | ||||
|             log.info("SoapClient exception: %s" % (exc,)) | ||||
|         return None | ||||
|  | ||||
|     if log: | ||||
|         log.info("SOAP request sent and got response: %s" % response) | ||||
|  | ||||
|     return response | ||||
|  | ||||
| # ----------------------------------------------------------------------------- | ||||
|  | ||||
| PACKING = { | ||||
|     saml2.BINDING_HTTP_REDIRECT: http_redirect_message, | ||||
|     saml2.BINDING_HTTP_POST: http_post_message, | ||||
|     } | ||||
|      | ||||
| def packager( identifier ): | ||||
|     try: | ||||
|         return PACKING[identifier] | ||||
|     except KeyError: | ||||
|         raise Exception("Unkown binding type: %s" % identifier) | ||||
							
								
								
									
										151
									
								
								src/saml2/cache.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/saml2/cache.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import shelve | ||||
| from saml2 import time_util | ||||
|  | ||||
| # The assumption is that any subject may consist of data  | ||||
| # gathered from several different sources, all with their own | ||||
| # timeout time. | ||||
|  | ||||
| class ToOld(Exception): | ||||
|     pass | ||||
|  | ||||
| class CacheError(Exception): | ||||
|     pass | ||||
|  | ||||
| class Cache(object): | ||||
|     def __init__(self, filename=None): | ||||
|         if filename: | ||||
|             self._db = shelve.open(filename, writeback=True) | ||||
|             self._sync = True | ||||
|         else: | ||||
|             self._db = {} | ||||
|             self._sync = False | ||||
|          | ||||
|     def delete(self, subject_id): | ||||
|         del self._db[subject_id] | ||||
|  | ||||
|         if self._sync: | ||||
|             self._db.sync() | ||||
|          | ||||
|     def get_identity(self, subject_id, entities=None, | ||||
|                      check_not_on_or_after=True): | ||||
|         """ Get all the identity information that has been received and  | ||||
|         are still valid about the subject. | ||||
|          | ||||
|         :param subject_id: The identifier of the subject | ||||
|         :param entities: The identifiers of the entities whoes assertions are | ||||
|             interesting. If the list is empty all entities are interesting. | ||||
|         :return: A 2-tuple consisting of the identity information (a | ||||
|             dictionary of attributes and values) and the list of entities  | ||||
|             whoes information has timed out. | ||||
|         """ | ||||
|         if not entities: | ||||
|             try: | ||||
|                 entities = self._db[subject_id].keys() | ||||
|             except KeyError: | ||||
|                 return {}, [] | ||||
|              | ||||
|         res = {} | ||||
|         oldees = [] | ||||
|         for entity_id in entities: | ||||
|             try: | ||||
|                 info = self.get(subject_id, entity_id, check_not_on_or_after) | ||||
|             except ToOld: | ||||
|                 oldees.append(entity_id) | ||||
|                 continue | ||||
|  | ||||
|             if not info: | ||||
|                 oldees.append(entity_id) | ||||
|                 continue | ||||
|                  | ||||
|             for key, vals in info["ava"].items():             | ||||
|                 try: | ||||
|                     tmp = set(res[key]).union(set(vals)) | ||||
|                     res[key] = list(tmp) | ||||
|                 except KeyError: | ||||
|                     res[key] = vals | ||||
|         return res, oldees | ||||
|          | ||||
|     def get(self, subject_id, entity_id, check_not_on_or_after=True): | ||||
|         """ Get session information about a subject gotten from a | ||||
|         specified IdP/AA. | ||||
|          | ||||
|         :param subject_id: The identifier of the subject | ||||
|         :param entity_id: The identifier of the entity_id | ||||
|         :param check_not_on_or_after: if True it will check if this | ||||
|              subject is still valid or if it is too old. Otherwise it | ||||
|              will not check this. True by default. | ||||
|         :return: The session information | ||||
|         """ | ||||
|         (timestamp, info) = self._db[subject_id][entity_id] | ||||
|         if check_not_on_or_after and time_util.after(timestamp): | ||||
|             raise ToOld("past %s" % timestamp) | ||||
|  | ||||
|         return info or None | ||||
|      | ||||
|     def set(self, subject_id, entity_id, info, not_on_or_after=0): | ||||
|         """ Stores session information in the cache. Assumes that the subject_id | ||||
|         is unique within the context of the Service Provider. | ||||
|          | ||||
|         :param subject_id: The subject identifier | ||||
|         :param entity_id: The identifier of the entity_id/receiver of an  | ||||
|             assertion | ||||
|         :param info: The session info, the assertion is part of this | ||||
|         :param not_on_or_after: A time after which the assertion is not valid. | ||||
|         """ | ||||
|         if subject_id not in self._db: | ||||
|             self._db[subject_id] = {} | ||||
|  | ||||
|         self._db[subject_id][entity_id] = (not_on_or_after, info) | ||||
|         if self._sync: | ||||
|             self._db.sync() | ||||
|              | ||||
|     def reset(self, subject_id, entity_id): | ||||
|         """ Scrap the assertions received from a IdP or an AA about a special | ||||
|         subject. | ||||
|          | ||||
|         :param subject_id: The subjects identifier | ||||
|         :param entity_id: The identifier of the entity_id of the assertion | ||||
|         :return: | ||||
|         """ | ||||
|         self.set(subject_id, entity_id, {}, 0) | ||||
|              | ||||
|     def entities(self, subject_id): | ||||
|         """ Returns all the entities of assertions for a subject, disregarding | ||||
|         whether the assertion still is valid or not. | ||||
|          | ||||
|         :param subject_id: The identifier of the subject | ||||
|         :return: A possibly empty list of entity identifiers | ||||
|         """ | ||||
|         return self._db[subject_id].keys() | ||||
|          | ||||
|     def receivers(self, subject_id): | ||||
|         """ Another name for entities() just to make it more logic in the IdP  | ||||
|             scenario """ | ||||
|         return self.entities(subject_id) | ||||
|          | ||||
|     def active(self, subject_id, entity_id): | ||||
|         """ Returns the status of assertions from a specific entity_id. | ||||
|          | ||||
|         :param subject_id: The ID of the subject | ||||
|         :param entity_id: The entity ID of the entity_id of the assertion | ||||
|         :return: True or False depending on if the assertion is still | ||||
|             valid or not. | ||||
|         """ | ||||
|         try: | ||||
|             (timestamp, info) = self._db[subject_id][entity_id] | ||||
|         except KeyError: | ||||
|             return False | ||||
|  | ||||
|         if not info: | ||||
|             return False | ||||
|         else: | ||||
|             return time_util.not_on_or_after(timestamp) | ||||
|          | ||||
|     def subjects(self): | ||||
|         """ Return identifiers for all the subjects that are in the cache. | ||||
|          | ||||
|         :return: list of subject identifiers | ||||
|         """ | ||||
|         return self._db.keys() | ||||
							
								
								
									
										1133
									
								
								src/saml2/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1133
									
								
								src/saml2/client.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										472
									
								
								src/saml2/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								src/saml2/config.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,472 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| __author__ = 'rolandh' | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| import re | ||||
| import logging | ||||
| import logging.handlers | ||||
|  | ||||
| from importlib import import_module | ||||
|  | ||||
| from saml2 import BINDING_SOAP, BINDING_HTTP_REDIRECT | ||||
| from saml2 import metadata | ||||
| from saml2 import root_logger | ||||
|  | ||||
| from saml2.attribute_converter import ac_factory | ||||
| from saml2.assertion import Policy | ||||
| from saml2.sigver import get_xmlsec_binary | ||||
|  | ||||
| COMMON_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file", | ||||
|                 "secret", "accepted_time_diff", "name", "ca_certs", | ||||
|                 "description", | ||||
|                 "organization", | ||||
|                 "contact_person", | ||||
|                 "name_form", | ||||
|                 "virtual_organization", | ||||
|                 "logger", | ||||
|                 "only_use_keys_in_metadata", | ||||
|                 "logout_requests_signed", | ||||
|                 ] | ||||
|  | ||||
| SP_ARGS = [ | ||||
|             "required_attributes", | ||||
|             "optional_attributes", | ||||
|             "idp", | ||||
|             "aa", | ||||
|             "subject_data", | ||||
|             "want_assertions_signed", | ||||
|             "authn_requests_signed", | ||||
|             "name_form", | ||||
|             "endpoints", | ||||
|             "ui_info", | ||||
|             "discovery_response", | ||||
|             "allow_unsolicited", | ||||
|             "ecp" | ||||
|             ] | ||||
|  | ||||
| AA_IDP_ARGS = ["want_authn_requests_signed", | ||||
|                "provided_attributes", | ||||
|                "subject_data", | ||||
|                "sp", | ||||
|                "scope", | ||||
|                "endpoints", | ||||
|                "metadata", | ||||
|                "ui_info"] | ||||
|  | ||||
| PDP_ARGS = ["endpoints", "name_form"] | ||||
|  | ||||
| COMPLEX_ARGS = ["attribute_converters", "metadata", "policy"] | ||||
| ALL = COMMON_ARGS + SP_ARGS + AA_IDP_ARGS + PDP_ARGS + COMPLEX_ARGS | ||||
|  | ||||
|  | ||||
| SPEC = { | ||||
|     "": COMMON_ARGS + COMPLEX_ARGS, | ||||
|     "sp": COMMON_ARGS + COMPLEX_ARGS + SP_ARGS, | ||||
|     "idp": COMMON_ARGS + COMPLEX_ARGS + AA_IDP_ARGS, | ||||
|     "aa": COMMON_ARGS + COMPLEX_ARGS + AA_IDP_ARGS, | ||||
|     "pdp": COMMON_ARGS + COMPLEX_ARGS + PDP_ARGS, | ||||
| } | ||||
|  | ||||
| # --------------- Logging stuff --------------- | ||||
|  | ||||
| LOG_LEVEL = {'debug': logging.DEBUG, | ||||
|     'info': logging.INFO, | ||||
|     'warning': logging.WARNING, | ||||
|     'error': logging.ERROR, | ||||
|     'critical': logging.CRITICAL} | ||||
|  | ||||
| LOG_HANDLER = { | ||||
|     "rotating": logging.handlers.RotatingFileHandler, | ||||
|     "syslog": logging.handlers.SysLogHandler, | ||||
|     "timerotate": logging.handlers.TimedRotatingFileHandler, | ||||
| } | ||||
|  | ||||
| LOG_FORMAT = "%(asctime)s %(name)s: %(levelname)s %(message)s" | ||||
| #LOG_FORMAT = "%(asctime)s %(name)s: %(levelname)s [%(sid)s][%(func)s] % | ||||
| # (message)s" | ||||
|  | ||||
| class ConfigurationError(Exception): | ||||
|     pass | ||||
|  | ||||
| # ----------------------------------------------------------------- | ||||
|  | ||||
| class Config(object): | ||||
|     def_context = "" | ||||
|  | ||||
|     def __init__(self): | ||||
|         self._attr = {"": {}, "sp": {}, "idp": {}, "aa": {}, "pdp": {}} | ||||
|         self.context = "" | ||||
|  | ||||
|     def serves(self): | ||||
|         return [t for t in ["sp", "idp", "aa", "pdp"] if self._attr[t]] | ||||
|  | ||||
|     def copy_into(self, typ=""): | ||||
|         if typ == "sp": | ||||
|             copy = SPConfig() | ||||
|         elif typ in ["idp", "aa"]: | ||||
|             copy = IdPConfig() | ||||
|         else: | ||||
|             copy = Config() | ||||
|         copy.context = typ | ||||
|         copy._attr = self._attr.copy() | ||||
|         return copy | ||||
|      | ||||
|     def __getattribute__(self, item): | ||||
|         if item == "context": | ||||
|             return object.__getattribute__(self, item) | ||||
|  | ||||
|         _context = self.context | ||||
|         if item in ALL: | ||||
|             try: | ||||
|                 return self._attr[_context][item] | ||||
|             except KeyError: | ||||
|                 if _context: | ||||
|                     try: | ||||
|                         return self._attr[""][item] | ||||
|                     except KeyError: | ||||
|                         pass | ||||
|                 return None | ||||
|         else: | ||||
|             return object.__getattribute__(self, item) | ||||
|  | ||||
|     def setattr(self, context, attr, val): | ||||
|         self._attr[context][attr] = val | ||||
|  | ||||
|     def load_special(self, cnf, typ, metadata_construction=False): | ||||
|         for arg in SPEC[typ]: | ||||
|             try: | ||||
|                 self._attr[typ][arg] = cnf[arg] | ||||
|             except KeyError: | ||||
|                 pass | ||||
|  | ||||
|         self.context = typ | ||||
|         self.load_complex(cnf, typ, metadata_construction=metadata_construction) | ||||
|         self.context = self.def_context | ||||
|  | ||||
|     def load_complex(self, cnf, typ="", metadata_construction=False): | ||||
|         _attr_typ = self._attr[typ] | ||||
|         try: | ||||
|             _attr_typ["policy"] = Policy(cnf["policy"]) | ||||
|         except KeyError: | ||||
|             pass | ||||
|  | ||||
|         try: | ||||
|             try: | ||||
|                 acs = ac_factory(cnf["attribute_map_dir"]) | ||||
|             except KeyError: | ||||
|                 acs = ac_factory() | ||||
|  | ||||
|             if not acs: | ||||
|                 raise Exception(("No attribute converters, ", | ||||
|                                     "something is wrong!!")) | ||||
|             try: | ||||
|                 _attr_typ["attribute_converters"].extend(acs) | ||||
|             except KeyError: | ||||
|                 _attr_typ["attribute_converters"] = acs | ||||
|         except KeyError: | ||||
|             pass | ||||
|  | ||||
|         if not metadata_construction: | ||||
|             try: | ||||
|                 _attr_typ["metadata"] = self.load_metadata(cnf["metadata"]) | ||||
|             except KeyError: | ||||
|                 pass | ||||
|  | ||||
|     def load(self, cnf, metadata_construction=False): | ||||
|         """ The base load method, loads the configuration | ||||
|  | ||||
|         :param cnf: The configuration as a dictionary | ||||
|         :param metadata_construction: Is this only to be able to construct | ||||
|             metadata. If so some things can be left out. | ||||
|         :return: The Configuration instance | ||||
|         """ | ||||
|         for arg in COMMON_ARGS: | ||||
|             try: | ||||
|                 self._attr[""][arg] = cnf[arg] | ||||
|             except KeyError: | ||||
|                 pass | ||||
|  | ||||
|         if "service" in cnf: | ||||
|             for typ in ["aa", "idp", "sp", "pdp"]: | ||||
|                 try: | ||||
|                     self.load_special(cnf["service"][typ], typ, | ||||
|                                     metadata_construction=metadata_construction) | ||||
|  | ||||
|                 except KeyError: | ||||
|                     pass | ||||
|  | ||||
|         if not metadata_construction: | ||||
|             if "xmlsec_binary" not in self._attr[""]: | ||||
|                 self._attr[""]["xmlsec_binary"] = get_xmlsec_binary() | ||||
|             # verify that xmlsec is where it's supposed to be | ||||
|             if not os.access(self._attr[""]["xmlsec_binary"], os.F_OK): | ||||
|                 raise Exception("xmlsec binary not in '%s' !" % ( | ||||
|                                             self._attr[""]["xmlsec_binary"])) | ||||
|  | ||||
|         self.load_complex(cnf, metadata_construction=metadata_construction) | ||||
|         self.context = self.def_context | ||||
|  | ||||
|         return self | ||||
|  | ||||
|     def load_file(self, config_file, metadata_construction=False): | ||||
|         if sys.path[0] != ".": | ||||
|             sys.path.insert(0, ".") | ||||
|  | ||||
|         if config_file.endswith(".py"): | ||||
|             config_file = config_file[:-3] | ||||
|  | ||||
|         mod = import_module(config_file) | ||||
|         #return self.load(eval(open(config_file).read())) | ||||
|         return self.load(mod.CONFIG, metadata_construction) | ||||
|  | ||||
|     def load_metadata(self, metadata_conf): | ||||
|         """ Loads metadata into an internal structure """ | ||||
|  | ||||
|         xmlsec_binary = self.xmlsec_binary | ||||
|         acs = self.attribute_converters | ||||
|  | ||||
|         if xmlsec_binary is None: | ||||
|             raise Exception("Missing xmlsec1 specification") | ||||
|         if acs is None: | ||||
|             raise Exception("Missing attribute converter specification") | ||||
|  | ||||
|         metad = metadata.MetaData(xmlsec_binary, acs) | ||||
|         if "local" in metadata_conf: | ||||
|             for mdfile in metadata_conf["local"]: | ||||
|                 metad.import_metadata(open(mdfile).read(), mdfile) | ||||
|         if "remote" in metadata_conf: | ||||
|             for spec in metadata_conf["remote"]: | ||||
|                 try: | ||||
|                     cert = spec["cert"] | ||||
|                 except KeyError: | ||||
|                     cert = None | ||||
|                 metad.import_external_metadata(spec["url"], cert) | ||||
|         return metad | ||||
|  | ||||
|     def endpoint(self, service, binding=None): | ||||
|         """ Goes through the list of endpoint specifications for the | ||||
|         given type of service and returnes the first endpoint that matches | ||||
|         the given binding. If no binding is given any endpoint for that | ||||
|         service will be returned. | ||||
|  | ||||
|         :param service: The service the endpoint should support | ||||
|         :param binding: The expected binding | ||||
|         :return: All the endpoints that matches the given restrictions | ||||
|         """ | ||||
|         spec = [] | ||||
|         unspec = [] | ||||
|         for endpspec in self.endpoints[service]: | ||||
|             try: | ||||
|                 endp, bind = endpspec | ||||
|                 if binding is None or bind == binding: | ||||
|                     spec.append(endp) | ||||
|             except ValueError: | ||||
|                 unspec.append(endpspec) | ||||
|  | ||||
|         if spec: | ||||
|             return spec | ||||
|         else: | ||||
|             return unspec | ||||
|  | ||||
|     def log_handler(self): | ||||
|         try: | ||||
|             _logconf = self.logger | ||||
|         except KeyError: | ||||
|             return None | ||||
|  | ||||
|         handler = None | ||||
|         for htyp in LOG_HANDLER: | ||||
|             if htyp in _logconf: | ||||
|                 if htyp == "syslog": | ||||
|                     args = _logconf[htyp] | ||||
|                     if "socktype" in args: | ||||
|                         import socket | ||||
|                         if args["socktype"] == "dgram": | ||||
|                             args["socktype"] = socket.SOCK_DGRAM | ||||
|                         elif args["socktype"] == "stream": | ||||
|                             args["socktype"] = socket.SOCK_STREAM | ||||
|                         else: | ||||
|                             raise Exception("Unknown socktype!") | ||||
|                     try: | ||||
|                         handler = LOG_HANDLER[htyp](**args) | ||||
|                     except TypeError: # difference between 2.6 and 2.7 | ||||
|                         del args["socktype"] | ||||
|                         handler = LOG_HANDLER[htyp](**args) | ||||
|                 else: | ||||
|                     handler = LOG_HANDLER[htyp](**_logconf[htyp]) | ||||
|                 break | ||||
|  | ||||
|         if handler is None: | ||||
|             # default if rotating logger | ||||
|             handler = LOG_HANDLER["rotating"]() | ||||
|  | ||||
|         if "format" in _logconf: | ||||
|             formatter = logging.Formatter(_logconf["format"]) | ||||
|         else: | ||||
|             formatter = logging.Formatter(LOG_FORMAT) | ||||
|  | ||||
|         handler.setFormatter(formatter) | ||||
|         return handler | ||||
|      | ||||
|     def setup_logger(self): | ||||
|         try: | ||||
|             _logconf = self.logger | ||||
|         except KeyError: | ||||
|             return None | ||||
|  | ||||
|         if root_logger.level != logging.NOTSET: # Someone got there before me | ||||
|             return root_logger | ||||
|  | ||||
|         if _logconf is None: | ||||
|             return None | ||||
|  | ||||
|         try: | ||||
|             root_logger.setLevel(LOG_LEVEL[_logconf["loglevel"].lower()]) | ||||
|         except KeyError: # reasonable default | ||||
|             root_logger.setLevel(logging.WARNING) | ||||
|  | ||||
|         root_logger.addHandler(self.log_handler()) | ||||
|         root_logger.info("Logging started") | ||||
|         return root_logger | ||||
|      | ||||
|     def keys(self): | ||||
|         keys = [] | ||||
|  | ||||
|         for dir in ["", "sp", "idp", "aa"]: | ||||
|             keys.extend(self._attr[dir].keys()) | ||||
|  | ||||
|         return list(set(keys)) | ||||
|  | ||||
|     def __contains__(self, item): | ||||
|         for dir in ["", "sp", "idp", "aa"]: | ||||
|             if item in self._attr[dir]: | ||||
|                 return True | ||||
|         return False | ||||
|          | ||||
| class SPConfig(Config): | ||||
|     def_context = "sp" | ||||
|  | ||||
|     def __init__(self): | ||||
|         Config.__init__(self) | ||||
|  | ||||
|     def single_logout_services(self, entity_id, binding=BINDING_SOAP): | ||||
|         """ returns a list of endpoints to use for sending logout requests to | ||||
|  | ||||
|         :param entity_id: The entity ID of the service | ||||
|         :param binding: The preferred binding (which for logout by default is | ||||
|             the SOAP binding) | ||||
|         :return: list of endpoints | ||||
|         """ | ||||
|         return self.metadata.single_logout_services(entity_id, "idp", | ||||
|                                                      binding=binding) | ||||
|  | ||||
|     def single_sign_on_services(self, entity_id, | ||||
|                                 binding=BINDING_HTTP_REDIRECT): | ||||
|         """ returns a list of endpoints to use for sending login requests to | ||||
|  | ||||
|         :param entity_id: The entity ID of the service | ||||
|         :param binding: The preferred binding  | ||||
|         :return: list of endpoints | ||||
|         """ | ||||
|         return self.metadata.single_sign_on_services(entity_id, | ||||
|                                                      binding=binding) | ||||
|  | ||||
|     def attribute_services(self, entity_id, binding=BINDING_SOAP): | ||||
|         """ returns a list of endpoints to use for attribute requests to | ||||
|  | ||||
|         :param entity_id: The entity ID of the service | ||||
|         :param binding: The preferred binding (which for logout by default is | ||||
|             the SOAP binding) | ||||
|         :return: list of endpoints | ||||
|         """ | ||||
|  | ||||
|         res = [] | ||||
|         if self.aa is None or entity_id in self.aa: | ||||
|             for aad in self.metadata.attribute_authority(entity_id): | ||||
|                 for attrserv in aad.attribute_service: | ||||
|                     if attrserv.binding == binding: | ||||
|                         res.append(attrserv) | ||||
|  | ||||
|         return res | ||||
|  | ||||
|     def idps(self, langpref=None): | ||||
|         """ Returns a dictionary of usefull IdPs, the keys being the | ||||
|         entity ID of the service and the names of the services as values | ||||
|  | ||||
|         :param langpref: The preferred languages of the name, the first match | ||||
|             is used. | ||||
|         :return: Dictionary | ||||
|         """ | ||||
|         if langpref is None: | ||||
|             langpref = ["en"] | ||||
|              | ||||
|         if self.idp: | ||||
|             return dict([(e, nd[0]) for (e, | ||||
|                 nd) in self.metadata.idps(langpref).items() if e in self.idp]) | ||||
|         else: | ||||
|             return self.metadata.idps() | ||||
|  | ||||
|     def vo_conf(self, vo_name): | ||||
|         try: | ||||
|             return self.virtual_organization[vo_name] | ||||
|         except KeyError: | ||||
|             return None | ||||
|  | ||||
|     def ecp_endpoint(self, ipaddress): | ||||
|         """ | ||||
|         Returns the entity ID of the IdP which the ECP client should talk to | ||||
|  | ||||
|         :param ipaddress: The IP address of the user client | ||||
|         :return: IdP entity ID or None | ||||
|         """ | ||||
|         if "ecp" in self._attr["sp"]: | ||||
|             for key, eid in self._attr["sp"]["ecp"].items(): | ||||
|                 if re.match(key, ipaddress): | ||||
|                     return eid | ||||
|  | ||||
|         return None | ||||
|  | ||||
| class IdPConfig(Config): | ||||
|     def_context = "idp" | ||||
|      | ||||
|     def __init__(self): | ||||
|         Config.__init__(self) | ||||
|          | ||||
|     def single_logout_services(self, entity_id, binding=BINDING_SOAP): | ||||
|         """ returns a list of endpoints to use for sending logout requests to | ||||
|  | ||||
|         :param entity_id: The entity ID of the service | ||||
|         :param binding: The preferred binding (which for logout by default is | ||||
|             the SOAP binding) | ||||
|         :return: list of endpoints | ||||
|         """ | ||||
|      | ||||
|         return self.metadata.single_logout_services(entity_id, "sp", | ||||
|                                                      binding=binding) | ||||
|  | ||||
|     def assertion_consumer_services(self, entity_id, binding): | ||||
|         typ = "assertion_consumer_service" | ||||
|         if self.sp is None or entity_id in self.sp: | ||||
|             acs = self.metadata.sp_services(entity_id, typ, binding=binding) | ||||
|             if acs: | ||||
|                 return [s[binding] for s in acs] | ||||
|  | ||||
|         return [] | ||||
|  | ||||
|     def authz_services(self, entity_id, binding=BINDING_SOAP): | ||||
|         return self.metadata.authz_services(entity_id, "pdp", | ||||
|                                                      binding=binding) | ||||
|  | ||||
| def config_factory(typ, file): | ||||
|     if typ == "sp": | ||||
|         conf = SPConfig().load_file(file) | ||||
|         conf.context = typ | ||||
|     elif typ in ["aa", "idp", "pdp"]: | ||||
|         conf = IdPConfig().load_file(file) | ||||
|         conf.context = typ | ||||
|     else: | ||||
|         conf = Config().load_file(file) | ||||
|         conf.context = typ | ||||
|     return conf | ||||
							
								
								
									
										95
									
								
								src/saml2/country_codes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/saml2/country_codes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| #!/usr/bin/env python | ||||
| # This Python file uses the following encoding: utf-8 | ||||
| # ISO 3166-1 country names and codes from http://opencountrycodes.appspot.com/python | ||||
|  | ||||
| COUNTRIES = ( | ||||
|     ("AF", "Afghanistan"),("AX", "Aland Islands"),("AL", "Albania"), | ||||
|     ("DZ", "Algeria"),("AS", "American Samoa"),("AD", "Andorra"), | ||||
|     ("AO", "Angola"),("AI", "Anguilla"),("AQ", "Antarctica"), | ||||
|     ("AG", "Antigua and Barbuda"),("AR", "Argentina"),("AM", "Armenia"), | ||||
|     ("AW", "Aruba"),("AU", "Australia"),("AT", "Austria"), | ||||
|     ("AZ", "Azerbaijan"),("BS", "Bahamas"),("BH", "Bahrain"), | ||||
|     ("BD", "Bangladesh"),("BB", "Barbados"),("BY", "Belarus"),("BE", "Belgium"), | ||||
|     ("BZ", "Belize"),("BJ", "Benin"),("BM", "Bermuda"),("BT", "Bhutan"), | ||||
|     ("BO", "Bolivia, Plurinational State of"), | ||||
|     ("BQ", "Bonaire, Sint Eustatius and Saba"),("BA", "Bosnia and Herzegovina"), | ||||
|     ("BW", "Botswana"),("BV", "Bouvet Island"),("BR", "Brazil"), | ||||
|     ("IO", "British Indian Ocean Territory"),("BN", "Brunei Darussalam"), | ||||
|     ("BG", "Bulgaria"),("BF", "Burkina Faso"),("BI", "Burundi"), | ||||
|     ("KH", "Cambodia"),("CM", "Cameroon"),("CA", "Canada"),("CV", "Cape Verde"), | ||||
|     ("KY", "Cayman Islands"),("CF", "Central African Republic"),("TD", "Chad"), | ||||
|     ("CL", "Chile"),("CN", "China"),("CX", "Christmas Island"), | ||||
|     ("CC", "Cocos (Keeling) Islands"),("CO", "Colombia"),("KM", "Comoros"), | ||||
|     ("CG", "Congo"),("CD", "Congo, The Democratic Republic of the"), | ||||
|     ("CK", "Cook Islands"),("CR", "Costa Rica"),("CI", "Cote D'ivoire"), | ||||
|     ("HR", "Croatia"),("CU", "Cuba"),("CW", "Curacao"),("CY", "Cyprus"), | ||||
|     ("CZ", "Czech Republic"),("DK", "Denmark"),("DJ", "Djibouti"), | ||||
|     ("DM", "Dominica"),("DO", "Dominican Republic"),("EC", "Ecuador"), | ||||
|     ("EG", "Egypt"),("SV", "El Salvador"),("GQ", "Equatorial Guinea"), | ||||
|     ("ER", "Eritrea"),("EE", "Estonia"),("ET", "Ethiopia"), | ||||
|     ("FK", "Falkland Islands (Malvinas)"),("FO", "Faroe Islands"), | ||||
|     ("FJ", "Fiji"),("FI", "Finland"),("FR", "France"),("GF", "French Guiana"), | ||||
|     ("PF", "French Polynesia"),("TF", "French Southern Territories"), | ||||
|     ("GA", "Gabon"),("GM", "Gambia"),("GE", "Georgia"),("DE", "Germany"), | ||||
|     ("GH", "Ghana"),("GI", "Gibraltar"),("GR", "Greece"),("GL", "Greenland"), | ||||
|     ("GD", "Grenada"),("GP", "Guadeloupe"),("GU", "Guam"),("GT", "Guatemala"), | ||||
|     ("GG", "Guernsey"),("GN", "Guinea"),("GW", "Guinea-Bissau"),("GY", "Guyana"), | ||||
|     ("HT", "Haiti"),("HM", "Heard Island and McDonald Islands"), | ||||
|     ("VA", "Holy See (Vatican City State)"),("HN", "Honduras"), | ||||
|     ("HK", "Hong Kong"),("HU", "Hungary"),("IS", "Iceland"),("IN", "India"), | ||||
|     ("ID", "Indonesia"),("IR", "Iran, Islamic Republic of"),("IQ", "Iraq"), | ||||
|     ("IE", "Ireland"),("IM", "Isle of Man"),("IL", "Israel"),("IT", "Italy"), | ||||
|     ("JM", "Jamaica"),("JP", "Japan"),("JE", "Jersey"),("JO", "Jordan"), | ||||
|     ("KZ", "Kazakhstan"),("KE", "Kenya"),("KI", "Kiribati"), | ||||
|     ("KP", "Korea, Democratic People's Republic of"), | ||||
|     ("KR", "Korea, Republic of"),("KW", "Kuwait"),("KG", "Kyrgyzstan"), | ||||
|     ("LA", "Lao People's Democratic Republic"),("LV", "Latvia"), | ||||
|     ("LB", "Lebanon"),("LS", "Lesotho"),("LR", "Liberia"), | ||||
|     ("LY", "Libyan Arab Jamahiriya"),("LI", "Liechtenstein"), | ||||
|     ("LT", "Lithuania"),("LU", "Luxembourg"),("MO", "Macao"), | ||||
|     ("MK", "Macedonia, The Former Yugoslav Republic of"),("MG", "Madagascar"), | ||||
|     ("MW", "Malawi"),("MY", "Malaysia"),("MV", "Maldives"),("ML", "Mali"), | ||||
|     ("MT", "Malta"),("MH", "Marshall Islands"),("MQ", "Martinique"), | ||||
|     ("MR", "Mauritania"),("MU", "Mauritius"),("YT", "Mayotte"),("MX", "Mexico"), | ||||
|     ("FM", "Micronesia, Federated States of"),("MD", "Moldova, Republic of"), | ||||
|     ("MC", "Monaco"),("MN", "Mongolia"),("ME", "Montenegro"), | ||||
|     ("MS", "Montserrat"),("MA", "Morocco"),("MZ", "Mozambique"), | ||||
|     ("MM", "Myanmar"),("NA", "Namibia"),("NR", "Nauru"),("NP", "Nepal"), | ||||
|     ("NL", "Netherlands"),("NC", "New Caledonia"),("NZ", "New Zealand"), | ||||
|     ("NI", "Nicaragua"),("NE", "Niger"),("NG", "Nigeria"),("NU", "Niue"), | ||||
|     ("NF", "Norfolk Island"),("MP", "Northern Mariana Islands"), | ||||
|     ("NO", "Norway"),("OM", "Oman"),("PK", "Pakistan"),("PW", "Palau"), | ||||
|     ("PS", "Palestinian Territory, Occupied"),("PA", "Panama"), | ||||
|     ("PG", "Papua New Guinea"),("PY", "Paraguay"),("PE", "Peru"), | ||||
|     ("PH", "Philippines"),("PN", "Pitcairn"),("PL", "Poland"), | ||||
|     ("PT", "Portugal"),("PR", "Puerto Rico"),("QA", "Qatar"),("RE", "Reunion"), | ||||
|     ("RO", "Romania"),("RU", "Russian Federation"),("RW", "Rwanda"), | ||||
|     ("BL", "Saint Barthelemy"), | ||||
|     ("SH", "Saint Helena, Ascension and Tristan Da Cunha"), | ||||
|     ("KN", "Saint Kitts and Nevis"),("LC", "Saint Lucia"), | ||||
|     ("MF", "Saint Martin (French Part)"),("PM", "Saint Pierre and Miquelon"), | ||||
|     ("VC", "Saint Vincent and the Grenadines"),("WS", "Samoa"), | ||||
|     ("SM", "San Marino"),("ST", "Sao Tome and Principe"),("SA", "Saudi Arabia"), | ||||
|     ("SN", "Senegal"),("RS", "Serbia"),("SC", "Seychelles"), | ||||
|     ("SL", "Sierra Leone"),("SG", "Singapore"), | ||||
|     ("SX", "Sint Maarten (Dutch Part)"),("SK", "Slovakia"),("SI", "Slovenia"), | ||||
|     ("SB", "Solomon Islands"),("SO", "Somalia"),("ZA", "South Africa"), | ||||
|     ("GS", "South Georgia and the South Sandwich Islands"),("ES", "Spain"), | ||||
|     ("LK", "Sri Lanka"),("SD", "Sudan"),("SR", "Suriname"), | ||||
|     ("SJ", "Svalbard and Jan Mayen"),("SZ", "Swaziland"),("SE", "Sweden"), | ||||
|     ("CH", "Switzerland"),("SY", "Syrian Arab Republic"), | ||||
|     ("TW", "Taiwan, Province of China"),("TJ", "Tajikistan"), | ||||
|     ("TZ", "Tanzania, United Republic of"),("TH", "Thailand"), | ||||
|     ("TL", "Timor-Leste"),("TG", "Togo"),("TK", "Tokelau"),("TO", "Tonga"), | ||||
|     ("TT", "Trinidad and Tobago"),("TN", "Tunisia"),("TR", "Turkey"), | ||||
|     ("TM", "Turkmenistan"),("TC", "Turks and Caicos Islands"),("TV", "Tuvalu"), | ||||
|     ("UG", "Uganda"),("UA", "Ukraine"),("AE", "United Arab Emirates"), | ||||
|     ("GB", "United Kingdom"),("US", "United States"), | ||||
|     ("UM", "United States Minor Outlying Islands"),("UY", "Uruguay"), | ||||
|     ("UZ", "Uzbekistan"),("VU", "Vanuatu"), | ||||
|     ("VE", "Venezuela, Bolivarian Republic of"),("VN", "Viet Nam"), | ||||
|     ("VG", "Virgin Islands, British"),("VI", "Virgin Islands, U.S."), | ||||
|     ("WF", "Wallis and Futuna"),("EH", "Western Sahara"),("YE", "Yemen"), | ||||
|     ("ZM", "Zambia"),("ZW", "Zimbabwe"),) | ||||
|  | ||||
| D_COUNTRIES = dict(COUNTRIES) | ||||
							
								
								
									
										213
									
								
								src/saml2/ecp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/saml2/ecp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2010-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """ | ||||
| Contains classes used in the SAML ECP profile | ||||
| """ | ||||
|  | ||||
| from saml2 import element_to_extension_element | ||||
| from saml2 import samlp | ||||
| from saml2 import soap | ||||
| from saml2 import BINDING_SOAP, BINDING_PAOS | ||||
|  | ||||
| from saml2.profile import paos | ||||
| from saml2.profile import ecp | ||||
|  | ||||
| #from saml2.client import Saml2Client | ||||
| from saml2.server import Server | ||||
|  | ||||
| from saml2.schema import soapenv | ||||
| from saml2.s_utils import sid | ||||
|  | ||||
| from saml2.response import authn_response | ||||
|  | ||||
| SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp" | ||||
|  | ||||
| def ecp_capable(headers): | ||||
|     if "application/vnd.paos+xml" in headers["Accept"]: | ||||
|         if "PAOS" in headers: | ||||
|             if 'ver="%s";"%s"' % (paos.NAMESPACE, | ||||
|                                   SERVICE) in headers["PAOS"]: | ||||
|                 return True | ||||
|  | ||||
|     return False | ||||
|  | ||||
| ACTOR = "http://schemas.xmlsoap.org/soap/actor/next" | ||||
|  | ||||
| #noinspection PyUnusedLocal | ||||
| def ecp_auth_request(cls, entityid=None, relay_state="", | ||||
|                      log=None, sign=False): | ||||
|     """ Makes an authentication request. | ||||
|  | ||||
|     :param entityid: The entity ID of the IdP to send the request to | ||||
|     :param relay_state: To where the user should be returned after | ||||
|         successfull log in. | ||||
|     :param log: Where to write log messages | ||||
|     :param sign: Whether the request should be signed or not. | ||||
|     :return: AuthnRequest response | ||||
|     """ | ||||
|  | ||||
|     eelist = [] | ||||
|  | ||||
|     # ---------------------------------------- | ||||
|     # <paos:Request> | ||||
|     # ---------------------------------------- | ||||
|     my_url = cls.service_url(BINDING_PAOS) | ||||
|  | ||||
|     # must_understan and actor according to the standard | ||||
|     # | ||||
|     paos_request = paos.Request(must_understand="1", actor=ACTOR, | ||||
|                                 response_consumer_url=my_url, | ||||
|                                 service = SERVICE) | ||||
|  | ||||
|     eelist.append(element_to_extension_element(paos_request)) | ||||
|  | ||||
|     # ---------------------------------------- | ||||
|     # <ecp:Request> | ||||
|     # ---------------------------------------- | ||||
|  | ||||
| #        idp = samlp.IDPEntry( | ||||
| #            provider_id = "https://idp.example.org/entity", | ||||
| #            name = "Example identity provider", | ||||
| #            loc = "https://idp.example.org/saml2/sso", | ||||
| #            ) | ||||
| # | ||||
| #        idp_list = samlp.IDPList(idp_entry= [idp]) | ||||
| # | ||||
| #        ecp_request = ecp.Request(actor = ACTOR, must_understand = "1", | ||||
| #                        provider_name = "Example Service Provider", | ||||
| #                        issuer=saml.Issuer(text="https://sp.example.org/entity"), | ||||
| #                        idp_list = idp_list) | ||||
| # | ||||
| #        eelist.append(element_to_extension_element(ecp_request)) | ||||
|  | ||||
|     # ---------------------------------------- | ||||
|     # <ecp:RelayState> | ||||
|     # ---------------------------------------- | ||||
|  | ||||
|     relay_state = ecp.RelayState(actor=ACTOR, must_understand="1", | ||||
|                                  text=relay_state) | ||||
|  | ||||
|     eelist.append(element_to_extension_element(relay_state)) | ||||
|  | ||||
|     header = soapenv.Header() | ||||
|     header.extension_elements = eelist | ||||
|  | ||||
|     # ---------------------------------------- | ||||
|     # <samlp:AuthnRequest> | ||||
|     # ---------------------------------------- | ||||
|  | ||||
|     if log: | ||||
|         log.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP)) | ||||
|          | ||||
|     location = cls._sso_location(entityid, binding=BINDING_SOAP) | ||||
|     session_id = sid() | ||||
|     authn_req = cls.authn(location, session_id, log=log, | ||||
|                           binding=BINDING_PAOS, | ||||
|                           service_url_binding=BINDING_PAOS) | ||||
|  | ||||
|     body = soapenv.Body() | ||||
|     body.extension_elements = [element_to_extension_element(authn_req)] | ||||
|  | ||||
|     # ---------------------------------------- | ||||
|     # The SOAP envelope | ||||
|     # ---------------------------------------- | ||||
|  | ||||
|     soap_envelope = soapenv.Envelope(header=header, body=body) | ||||
|  | ||||
|     return session_id, "%s" % soap_envelope | ||||
|  | ||||
|  | ||||
| def handle_ecp_authn_response(cls, soap_message, outstanding=None): | ||||
|     rdict = soap.class_instances_from_soap_enveloped_saml_thingies( | ||||
|                                                             soap_message, | ||||
|                                                             [paos, ecp, | ||||
|                                                              samlp]) | ||||
|  | ||||
|     _relay_state = None | ||||
|     for item in rdict["header"]: | ||||
|         if item.c_tag == "RelayState" and \ | ||||
|            item.c_namespace == ecp.NAMESPACE: | ||||
|             _relay_state = item | ||||
|  | ||||
|     response = authn_response(cls.config, cls.service_url(), | ||||
|                               outstanding, log=cls.logger, | ||||
|                               debug=cls.debug, | ||||
|                               allow_unsolicited=True) | ||||
|  | ||||
|     response.loads("%s" % rdict["body"], False, soap_message) | ||||
|     response.verify() | ||||
|     cls.users.add_information_about_person(response.session_info()) | ||||
|  | ||||
|     return response, _relay_state | ||||
|          | ||||
|  | ||||
| def ecp_response(target_url, response): | ||||
|  | ||||
|     # ---------------------------------------- | ||||
|     # <ecp:Response | ||||
|     # ---------------------------------------- | ||||
|  | ||||
|     ecp_response = ecp.Response(assertion_consumer_service_url=target_url) | ||||
|     header = soapenv.Header() | ||||
|     header.extension_elements = [element_to_extension_element(ecp_response)] | ||||
|  | ||||
|     # ---------------------------------------- | ||||
|     # <samlp:Response | ||||
|     # ---------------------------------------- | ||||
|  | ||||
|     body = soapenv.Body() | ||||
|     body.extension_elements = [element_to_extension_element(response)] | ||||
|  | ||||
|     soap_envelope = soapenv.Envelope(header=header, body=body) | ||||
|  | ||||
|     return "%s" % soap_envelope | ||||
|  | ||||
| class ECPServer(Server): | ||||
|     """ This deals with what the IdP has to do | ||||
|  | ||||
|     TODO: Still tentative | ||||
|     """ | ||||
|     def __init__(self, config_file="", config=None, _cache="", | ||||
|                     log=None, debug=0): | ||||
|         Server.__init__(self, config_file, config, _cache, log, debug) | ||||
|  | ||||
|     def parse_ecp_authn_query(self): | ||||
|         pass | ||||
|      | ||||
|     def ecp_response(self): | ||||
|  | ||||
|         # ---------------------------------------- | ||||
|         # <ecp:Response | ||||
|         # ---------------------------------------- | ||||
|         target_url = "" | ||||
|  | ||||
|         ecp_response = ecp.Response(assertion_consumer_service_url=target_url) | ||||
|         header = soapenv.Body() | ||||
|         header.extension_elements = [element_to_extension_element(ecp_response)] | ||||
|  | ||||
|         # ---------------------------------------- | ||||
|         # <samlp:Response | ||||
|         # ---------------------------------------- | ||||
|  | ||||
|         response = samlp.Response() | ||||
|         body = soapenv.Body() | ||||
|         body.extension_elements = [element_to_extension_element(response)] | ||||
|  | ||||
|         soap_envelope = soapenv.Envelope(header=header, body=body) | ||||
|  | ||||
|         return "%s" % soap_envelope | ||||
							
								
								
									
										332
									
								
								src/saml2/ecp_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								src/saml2/ecp_client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,332 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2010-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """ | ||||
| Contains a class that can be used handle all the ECP handling for other python | ||||
| programs. | ||||
| """ | ||||
|  | ||||
| import cookielib | ||||
| import sys | ||||
|  | ||||
| from saml2 import soap | ||||
| from saml2 import samlp | ||||
| from saml2 import BINDING_PAOS | ||||
| from saml2 import BINDING_SOAP | ||||
| from saml2 import class_name | ||||
|  | ||||
| from saml2.profile import paos | ||||
| from saml2.profile import ecp | ||||
|  | ||||
| from saml2.metadata import MetaData | ||||
|  | ||||
| SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp" | ||||
| PAOS_HEADER_INFO = 'ver="%s";"%s"' % (paos.NAMESPACE, SERVICE) | ||||
|  | ||||
| class Client(object): | ||||
|     def __init__(self, user, passwd, sp="", idp=None, metadata_file=None, | ||||
|                  xmlsec_binary=None, verbose=0, ca_certs="", | ||||
|                  disable_ssl_certificate_validation=True, logger=None, | ||||
|                  debug=False): | ||||
|         """ | ||||
|         :param user: user name | ||||
|         :param passwd: user password | ||||
|         :param sp: The SP URL | ||||
|         :param idp: The IdP PAOS endpoint | ||||
|         :param metadata_file: Where the metadata file is if used | ||||
|         :param xmlsec_binary: Where the xmlsec1 binary can be found | ||||
|         :param verbose: Chatty or not | ||||
|         :param ca_certs: is the path of a file containing root CA certificates | ||||
|             for SSL server certificate validation. | ||||
|         :param disable_ssl_certificate_validation: If | ||||
|             disable_ssl_certificate_validation is true, SSL cert validation | ||||
|             will not be performed. | ||||
|         :param logger: Somewhere to write logs to | ||||
|         :param debug: Whether debug output is needed | ||||
|         """ | ||||
|         self._idp = idp | ||||
|         self._sp = sp | ||||
|         self.user = user | ||||
|         self.passwd = passwd | ||||
|         self.log = logger | ||||
|         self.debug = debug | ||||
|         self._verbose = verbose | ||||
|  | ||||
|         if metadata_file: | ||||
|             self._metadata = MetaData() | ||||
|             self._metadata.import_metadata(open(metadata_file).read(), | ||||
|                                            xmlsec_binary) | ||||
|             self._debug_info("Loaded metadata from '%s'" % metadata_file) | ||||
|         else: | ||||
|             self._metadata = None | ||||
|  | ||||
|         self.cookie_handler = None | ||||
|  | ||||
|         self.done_ecp = False | ||||
|         self.cookie_jar = cookielib.LWPCookieJar() | ||||
|         self.http = soap.HTTPClient(self._sp, cookiejar=self.cookie_jar, | ||||
|             ca_certs=ca_certs, | ||||
|             disable_ssl_certificate_validation=disable_ssl_certificate_validation) | ||||
|  | ||||
|     def _debug_info(self, text): | ||||
|         if self.debug: | ||||
|             if self.log: | ||||
|                 self.log.debug(text) | ||||
|  | ||||
|         if self._verbose: | ||||
|             print >> sys.stderr, text | ||||
|  | ||||
|     def find_idp_endpoint(self, idp_entity_id): | ||||
|         if self._idp: | ||||
|             return self._idp | ||||
|  | ||||
|         if idp_entity_id and not self._metadata: | ||||
|             raise Exception( | ||||
|                 "Can't handle IdP entity ID if I don't have metadata") | ||||
|  | ||||
|         if idp_entity_id: | ||||
|             for binding in [BINDING_PAOS, BINDING_SOAP]: | ||||
|                 ssos = self._metadata.single_sign_on_services(idp_entity_id, | ||||
|                                                               binding=binding) | ||||
|                 if ssos: | ||||
|                     self._idp = ssos[0] | ||||
|                     if self.debug: | ||||
|                         self.log.debug("IdP endpoint: '%s'" % self._idp) | ||||
|                     return self._idp | ||||
|  | ||||
|             raise Exception("No suitable endpoint found for entity id '%s'" % ( | ||||
|                             idp_entity_id,)) | ||||
|         else: | ||||
|             raise Exception("No entity ID -> no endpoint") | ||||
|  | ||||
|     def phase2(self, authn_request, rc_url, idp_entity_id, headers=None, | ||||
|                idp_endpoint=None, sign=False, sec=""): | ||||
|         """ | ||||
|         Doing the second phase of the ECP conversation | ||||
|  | ||||
|         :param authn_request: The AuthenticationRequest | ||||
|         :param rc_url: The assertion consumer service url | ||||
|         :param idp_entity_id: The EntityID of the IdP | ||||
|         :param headers: Possible extra headers | ||||
|         :param idp_endpoint: Where to send it all | ||||
|         :param sign: If the message should be signed | ||||
|         :param sec: security context | ||||
|         :return: The response from the IdP | ||||
|         """ | ||||
|         idp_request = soap.make_soap_enveloped_saml_thingy(authn_request) | ||||
|         if sign: | ||||
|             _signed = sec.sign_statement_using_xmlsec(idp_request, | ||||
|                                                       class_name(authn_request), | ||||
|                                                       nodeid=authn_request.id) | ||||
|             idp_request = _signed | ||||
|  | ||||
|         if not idp_endpoint: | ||||
|             idp_endpoint = self.find_idp_endpoint(idp_entity_id) | ||||
|  | ||||
|         if self.user and self.passwd: | ||||
|             self.http.add_credentials(self.user, self.passwd) | ||||
|  | ||||
|         self._debug_info("[P2] Sending request: %s" % idp_request) | ||||
|              | ||||
|         # POST the request to the IdP | ||||
|         response = self.http.post(idp_request, headers=headers, | ||||
|                                   path=idp_endpoint) | ||||
|  | ||||
|         self._debug_info("[P2] Got IdP response: %s" % response) | ||||
|  | ||||
|         if response is None or response is False: | ||||
|             raise Exception( | ||||
|                 "Request to IdP failed (%s): %s" % (self.http.response.status, | ||||
|                                                 self.http.error_description)) | ||||
|  | ||||
|         # SAMLP response in a SOAP envelope body, ecp response in headers | ||||
|         respdict = soap.class_instances_from_soap_enveloped_saml_thingies( | ||||
|                                                 response, [paos, ecp,samlp]) | ||||
|  | ||||
|         if respdict is None: | ||||
|             raise Exception("Unexpected reply from the IdP") | ||||
|  | ||||
|         self._debug_info("[P2] IdP response dict: %s" % respdict) | ||||
|  | ||||
|         idp_response = respdict["body"] | ||||
|         assert idp_response.c_tag == "Response" | ||||
|  | ||||
|         self._debug_info("[P2] IdP AUTHN response: %s" % idp_response) | ||||
|  | ||||
|         _ecp_response = None | ||||
|         for item in respdict["header"]: | ||||
|             if item.c_tag == "Response" and\ | ||||
|                item.c_namespace == ecp.NAMESPACE: | ||||
|                 _ecp_response = item | ||||
|  | ||||
|         _acs_url = _ecp_response.assertion_consumer_service_url | ||||
|         if rc_url != _acs_url: | ||||
|             error = ("response_consumer_url '%s' does not match" % rc_url, | ||||
|                      "assertion_consumer_service_url '%s" % _acs_url) | ||||
|             # Send an error message to the SP | ||||
|             fault_text = soap.soap_fault(error) | ||||
|             _ = self.http.post(fault_text, path=rc_url) | ||||
|             # Raise an exception so the user knows something went wrong | ||||
|             raise Exception(error) | ||||
|          | ||||
|         return idp_response | ||||
|  | ||||
|     #noinspection PyUnusedLocal | ||||
|     def ecp_conversation(self, respdict, idp_entity_id=None): | ||||
|         """  """ | ||||
|  | ||||
|         if respdict is None: | ||||
|             raise Exception("Unexpected reply from the SP") | ||||
|  | ||||
|         self._debug_info("[P1] SP response dict: %s" % respdict) | ||||
|  | ||||
|         # AuthnRequest in the body or not | ||||
|         authn_request = respdict["body"] | ||||
|         assert authn_request.c_tag == "AuthnRequest" | ||||
|  | ||||
|         # ecp.RelayState among headers | ||||
|         _relay_state = None | ||||
|         _paos_request = None | ||||
|         for item in respdict["header"]: | ||||
|             if item.c_tag == "RelayState" and\ | ||||
|                item.c_namespace == ecp.NAMESPACE: | ||||
|                 _relay_state = item | ||||
|             if item.c_tag == "Request" and\ | ||||
|                item.c_namespace == paos.NAMESPACE: | ||||
|                 _paos_request = item | ||||
|  | ||||
|         _rc_url = _paos_request.response_consumer_url | ||||
|  | ||||
|         # ********************** | ||||
|         # Phase 2 - talk to the IdP | ||||
|         # ********************** | ||||
|  | ||||
|         idp_response = self.phase2(authn_request, _rc_url, idp_entity_id) | ||||
|  | ||||
|         # ********************************** | ||||
|         # Phase 3 - back to the SP | ||||
|         # ********************************** | ||||
|  | ||||
|         sp_response = soap.make_soap_enveloped_saml_thingy(idp_response, | ||||
|             [_relay_state]) | ||||
|  | ||||
|         self._debug_info("[P3] Post to SP: %s" % sp_response) | ||||
|  | ||||
|         headers = {'Content-Type': 'application/vnd.paos+xml', } | ||||
|  | ||||
|         # POST the package from the IdP to the SP | ||||
|         response = self.http.post(sp_response, headers, _rc_url) | ||||
|  | ||||
|         if not response: | ||||
|             if self.http.response.status == 302: | ||||
|                 # ignore where the SP is redirecting us to and go for the | ||||
|                 # url I started off with. | ||||
|                 pass | ||||
|             else: | ||||
|                 print self.http.error_description | ||||
|                 raise Exception( | ||||
|                     "Error POSTing package to SP: %s" % self.http.response.reason) | ||||
|  | ||||
|         self._debug_info("[P3] IdP response: %s" % response) | ||||
|  | ||||
|         self.done_ecp = True | ||||
|         if self.debug: | ||||
|             self.log.debug("Done ECP") | ||||
|              | ||||
|         return None | ||||
|  | ||||
|  | ||||
|     def operation(self, idp_entity_id, op, **opargs): | ||||
|         if "path" not in opargs: | ||||
|             opargs["path"] = self._sp | ||||
|  | ||||
|         # ******************************************** | ||||
|         # Phase 1 - First conversation with the SP | ||||
|         # ******************************************** | ||||
|         # headers needed to indicate to the SP that I'm ECP enabled | ||||
|  | ||||
|         if "headers" in opargs and opargs["headers"]: | ||||
|             opargs["headers"]["PAOS"] = PAOS_HEADER_INFO | ||||
|             if "Accept" in opargs["headers"]: | ||||
|                 opargs["headers"]["Accept"] += ";application/vnd.paos+xml" | ||||
|             elif "accept" in opargs["headers"]: | ||||
|                 opargs["headers"]["Accept"] = opargs["headers"]["accept"] | ||||
|                 opargs["headers"]["Accept"] += ";application/vnd.paos+xml" | ||||
|                 del opargs["headers"]["accept"] | ||||
|         else: | ||||
|             opargs["headers"] = { | ||||
|                 'Accept': 'text/html; application/vnd.paos+xml', | ||||
|                 'PAOS': PAOS_HEADER_INFO | ||||
|             } | ||||
|  | ||||
|         # request target from SP | ||||
|         # can remove the PAOS header now | ||||
| #        try: | ||||
| #            del opargs["headers"]["PAOS"] | ||||
| #        except KeyError: | ||||
| #            pass | ||||
|          | ||||
|         response = op(**opargs) | ||||
|         self._debug_info("[Op] SP response: %s" % response) | ||||
|  | ||||
|         if not response: | ||||
|             raise Exception( | ||||
|                 "Request to SP failed: %s" % self.http.error_description) | ||||
|  | ||||
|         # The response might be a AuthnRequest instance in a SOAP envelope | ||||
|         # body. If so it's the start of the ECP conversation | ||||
|         # Two SOAP header blocks; paos:Request and ecp:Request | ||||
|         # may also contain a ecp:RelayState SOAP header block | ||||
|         # If channel-binding was part of the PAOS header any number of | ||||
|         # <cb:ChannelBindings> header blocks may also be present | ||||
|         # if 'holder-of-key' option then one or more <ecp:SubjectConfirmation> | ||||
|         # header blocks may also be present | ||||
|         try: | ||||
|             respdict = soap.class_instances_from_soap_enveloped_saml_thingies( | ||||
|                 response, | ||||
|                 [paos, ecp, | ||||
|                  samlp]) | ||||
|             self.ecp_conversation(respdict, idp_entity_id) | ||||
|             # should by now be authenticated so this should go smoothly | ||||
|             response = op(**opargs) | ||||
|         except (soap.XmlParseError, AssertionError, KeyError): | ||||
|             pass | ||||
|  | ||||
|         #print "RESP",response, self.http.response | ||||
|  | ||||
|         if not response: | ||||
|             if  self.http.response.status != 404: | ||||
|                 raise Exception("Error performing operation: %s" % ( | ||||
|                     self.http.error_description,)) | ||||
|  | ||||
|         return response | ||||
|  | ||||
|     def delete(self, path=None, idp_entity_id=None): | ||||
|         return self.operation(idp_entity_id, self.http.delete, path=path) | ||||
|  | ||||
|     def get(self, path=None, idp_entity_id=None, headers=None): | ||||
|         return self.operation(idp_entity_id, self.http.get, path=path, | ||||
|                               headers=headers) | ||||
|  | ||||
|     def post(self, path=None, data="", idp_entity_id=None, headers=None): | ||||
|         return self.operation(idp_entity_id, self.http.post, data=data, | ||||
|                               path=path, headers=headers) | ||||
|  | ||||
|     def put(self, path=None, data="", idp_entity_id=None, headers=None): | ||||
|         return self.operation(idp_entity_id, self.http.put, data=data, | ||||
|                               path=path, headers=headers) | ||||
|  | ||||
							
								
								
									
										3
									
								
								src/saml2/extension/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/saml2/extension/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # metadata extensions mainly | ||||
| __author__ = 'rolandh' | ||||
| __all__ = ["dri", "mdrpi", "mdui", "shibmd", "idpdisc"] | ||||
							
								
								
									
										338
									
								
								src/saml2/extension/dri.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								src/saml2/extension/dri.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,338 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Mon Oct 25 16:19:28 2010 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| from saml2 import md | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:metadata:dri' | ||||
|  | ||||
| class CreationInstant(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:CreationInstant element """ | ||||
|  | ||||
|     c_tag = 'CreationInstant' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'datetime'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def creation_instant_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(CreationInstant, xml_string) | ||||
|  | ||||
|  | ||||
| class SerialNumber(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:SerialNumber element """ | ||||
|  | ||||
|     c_tag = 'SerialNumber' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def serial_number_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(SerialNumber, xml_string) | ||||
|  | ||||
|  | ||||
| class UsagePolicy(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:UsagePolicy element """ | ||||
|  | ||||
|     c_tag = 'UsagePolicy' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'anyURI'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def usage_policy_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(UsagePolicy, xml_string) | ||||
|  | ||||
|  | ||||
| class PublisherType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:PublisherType element """ | ||||
|  | ||||
|     c_tag = 'PublisherType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['PublisherID'] = ('publisher_id', 'md:entityIDType', True) | ||||
|     c_attributes['CreationInstant'] = ('creation_instant', 'datetime', False) | ||||
|     c_attributes['SerialNumber'] = ('serial_number', 'string', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             publisher_id=None, | ||||
|             creation_instant=None, | ||||
|             serial_number=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.publisher_id=publisher_id | ||||
|         self.creation_instant=creation_instant | ||||
|         self.serial_number=serial_number | ||||
|  | ||||
| def publisher_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PublisherType_, xml_string) | ||||
|  | ||||
|  | ||||
| class RegistrationAuthority(md.EntityIDType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:RegistrationAuthority element """ | ||||
|  | ||||
|     c_tag = 'RegistrationAuthority' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.EntityIDType_.c_children.copy() | ||||
|     c_attributes = md.EntityIDType_.c_attributes.copy() | ||||
|     c_child_order = md.EntityIDType_.c_child_order[:] | ||||
|     c_cardinality = md.EntityIDType_.c_cardinality.copy() | ||||
|  | ||||
| def registration_authority_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationAuthority, xml_string) | ||||
|  | ||||
|  | ||||
| class RegistrationInstant(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:RegistrationInstant element """ | ||||
|  | ||||
|     c_tag = 'RegistrationInstant' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'datetime'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def registration_instant_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationInstant, xml_string) | ||||
|  | ||||
|  | ||||
| class RegistrationPolicy(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:RegistrationPolicy element """ | ||||
|  | ||||
|     c_tag = 'RegistrationPolicy' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'anyURI'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def registration_policy_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationPolicy, xml_string) | ||||
|  | ||||
|  | ||||
| class Publisher(PublisherType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:Publisher element """ | ||||
|  | ||||
|     c_tag = 'Publisher' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = PublisherType_.c_children.copy() | ||||
|     c_attributes = PublisherType_.c_attributes.copy() | ||||
|     c_child_order = PublisherType_.c_child_order[:] | ||||
|     c_cardinality = PublisherType_.c_cardinality.copy() | ||||
|  | ||||
| def publisher_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Publisher, xml_string) | ||||
|  | ||||
|  | ||||
| class RegistrationInfoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:RegistrationInfoType element """ | ||||
|  | ||||
|     c_tag = 'RegistrationInfoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}RegistrationAuthority'] = ('registration_authority', RegistrationAuthority) | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}RegistrationInstant'] = ('registration_instant', RegistrationInstant) | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}RegistrationPolicy'] = ('registration_policy', RegistrationPolicy) | ||||
|     c_cardinality['registration_policy'] = {"min":0, "max":1} | ||||
|     c_child_order.extend(['registration_authority', 'registration_instant', 'registration_policy']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             registration_authority=None, | ||||
|             registration_instant=None, | ||||
|             registration_policy=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.registration_authority=registration_authority | ||||
|         self.registration_instant=registration_instant | ||||
|         self.registration_policy=registration_policy | ||||
|  | ||||
| def registration_info_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationInfoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class PublishersType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:PublishersType element """ | ||||
|  | ||||
|     c_tag = 'PublishersType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}Publisher'] = ('publisher', [Publisher]) | ||||
|     c_cardinality['publisher'] = {"min":0} | ||||
|     c_child_order.extend(['publisher']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             publisher=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.publisher=publisher or [] | ||||
|  | ||||
| def publishers_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PublishersType_, xml_string) | ||||
|  | ||||
|  | ||||
| class RegistrationInfo(RegistrationInfoType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:RegistrationInfo element """ | ||||
|  | ||||
|     c_tag = 'RegistrationInfo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = RegistrationInfoType_.c_children.copy() | ||||
|     c_attributes = RegistrationInfoType_.c_attributes.copy() | ||||
|     c_child_order = RegistrationInfoType_.c_child_order[:] | ||||
|     c_cardinality = RegistrationInfoType_.c_cardinality.copy() | ||||
|  | ||||
| def registration_info_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationInfo, xml_string) | ||||
|  | ||||
|  | ||||
| class Publishers(PublishersType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:Publishers element """ | ||||
|  | ||||
|     c_tag = 'Publishers' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = PublishersType_.c_children.copy() | ||||
|     c_attributes = PublishersType_.c_attributes.copy() | ||||
|     c_child_order = PublishersType_.c_child_order[:] | ||||
|     c_cardinality = PublishersType_.c_cardinality.copy() | ||||
|  | ||||
| def publishers_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Publishers, xml_string) | ||||
|  | ||||
|  | ||||
| class DocumentInfoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:DocumentInfoType element """ | ||||
|  | ||||
|     c_tag = 'DocumentInfoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}CreationInstant'] = ('creation_instant', CreationInstant) | ||||
|     c_cardinality['creation_instant'] = {"min":0, "max":1} | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}SerialNumber'] = ('serial_number', SerialNumber) | ||||
|     c_cardinality['serial_number'] = {"min":0, "max":1} | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}UsagePolicy'] = ('usage_policy', UsagePolicy) | ||||
|     c_cardinality['usage_policy'] = {"min":0, "max":1} | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:metadata:dri}Publishers'] = ('publishers', Publishers) | ||||
|     c_cardinality['publishers'] = {"min":0, "max":1} | ||||
|     c_child_order.extend(['creation_instant', 'serial_number', 'usage_policy', 'publishers']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             creation_instant=None, | ||||
|             serial_number=None, | ||||
|             usage_policy=None, | ||||
|             publishers=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.creation_instant=creation_instant | ||||
|         self.serial_number=serial_number | ||||
|         self.usage_policy=usage_policy | ||||
|         self.publishers=publishers | ||||
|  | ||||
| def document_info_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DocumentInfoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class DocumentInfo(DocumentInfoType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:metadata:dri:DocumentInfo element """ | ||||
|  | ||||
|     c_tag = 'DocumentInfo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = DocumentInfoType_.c_children.copy() | ||||
|     c_attributes = DocumentInfoType_.c_attributes.copy() | ||||
|     c_child_order = DocumentInfoType_.c_child_order[:] | ||||
|     c_cardinality = DocumentInfoType_.c_cardinality.copy() | ||||
|  | ||||
| def document_info_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DocumentInfo, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     DocumentInfo.c_tag: document_info_from_string, | ||||
|     DocumentInfoType_.c_tag: document_info_type__from_string, | ||||
|     CreationInstant.c_tag: creation_instant_from_string, | ||||
|     SerialNumber.c_tag: serial_number_from_string, | ||||
|     UsagePolicy.c_tag: usage_policy_from_string, | ||||
|     Publishers.c_tag: publishers_from_string, | ||||
|     PublishersType_.c_tag: publishers_type__from_string, | ||||
|     Publisher.c_tag: publisher_from_string, | ||||
|     PublisherType_.c_tag: publisher_type__from_string, | ||||
|     RegistrationInfo.c_tag: registration_info_from_string, | ||||
|     RegistrationInfoType_.c_tag: registration_info_type__from_string, | ||||
|     RegistrationAuthority.c_tag: registration_authority_from_string, | ||||
|     RegistrationInstant.c_tag: registration_instant_from_string, | ||||
|     RegistrationPolicy.c_tag: registration_policy_from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'DocumentInfo': DocumentInfo, | ||||
|     'DocumentInfoType': DocumentInfoType_, | ||||
|     'CreationInstant': CreationInstant, | ||||
|     'SerialNumber': SerialNumber, | ||||
|     'UsagePolicy': UsagePolicy, | ||||
|     'Publishers': Publishers, | ||||
|     'PublishersType': PublishersType_, | ||||
|     'Publisher': Publisher, | ||||
|     'PublisherType': PublisherType_, | ||||
|     'RegistrationInfo': RegistrationInfo, | ||||
|     'RegistrationInfoType': RegistrationInfoType_, | ||||
|     'RegistrationAuthority': RegistrationAuthority, | ||||
|     'RegistrationInstant': RegistrationInstant, | ||||
|     'RegistrationPolicy': RegistrationPolicy, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										37
									
								
								src/saml2/extension/idpdisc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/saml2/extension/idpdisc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Thu Jun 23 09:01:47 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import md | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol' | ||||
|  | ||||
| class DiscoveryResponse(md.IndexedEndpointType_): | ||||
|     """The urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:DiscoveryResponse element """ | ||||
|  | ||||
|     c_tag = 'DiscoveryResponse' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.IndexedEndpointType_.c_children.copy() | ||||
|     c_attributes = md.IndexedEndpointType_.c_attributes.copy() | ||||
|     c_child_order = md.IndexedEndpointType_.c_child_order[:] | ||||
|     c_cardinality = md.IndexedEndpointType_.c_cardinality.copy() | ||||
|  | ||||
| def discovery_response_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DiscoveryResponse, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     DiscoveryResponse.c_tag: discovery_response_from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'DiscoveryResponse': DiscoveryResponse, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										75
									
								
								src/saml2/extension/mdattr.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/saml2/extension/mdattr.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Mon May  2 14:23:34 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| from saml2 import saml | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:metadata:attribute' | ||||
|  | ||||
| class EntityAttributesType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:attribute:EntityAttributesType element """ | ||||
|  | ||||
|     c_tag = 'EntityAttributesType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:assertion}Attribute'] = ('attribute', [saml.Attribute]) | ||||
|     c_cardinality['attribute'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:assertion}Assertion'] = ('assertion', [saml.Assertion]) | ||||
|     c_cardinality['assertion'] = {"min":0} | ||||
|     c_child_order.extend(['attribute', 'assertion']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             attribute=None, | ||||
|             assertion=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.attribute=attribute or [] | ||||
|         self.assertion=assertion or [] | ||||
|  | ||||
| def entity_attributes_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(EntityAttributesType_, xml_string) | ||||
|  | ||||
|  | ||||
| class EntityAttributes(EntityAttributesType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:attribute:EntityAttributes element """ | ||||
|  | ||||
|     c_tag = 'EntityAttributes' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = EntityAttributesType_.c_children.copy() | ||||
|     c_attributes = EntityAttributesType_.c_attributes.copy() | ||||
|     c_child_order = EntityAttributesType_.c_child_order[:] | ||||
|     c_cardinality = EntityAttributesType_.c_cardinality.copy() | ||||
|  | ||||
| def entity_attributes_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(EntityAttributes, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     EntityAttributes.c_tag: entity_attributes_from_string, | ||||
|     EntityAttributesType_.c_tag: entity_attributes_type__from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'EntityAttributes': EntityAttributes, | ||||
|     'EntityAttributesType': EntityAttributesType_, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										266
									
								
								src/saml2/extension/mdrpi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								src/saml2/extension/mdrpi.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,266 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Mon Jun 27 09:54:22 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| from saml2 import md | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:metadata:rpi' | ||||
|  | ||||
| class RegistrationPolicy(md.LocalizedURIType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:RegistrationPolicy element """ | ||||
|  | ||||
|     c_tag = 'RegistrationPolicy' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedURIType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedURIType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedURIType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedURIType_.c_cardinality.copy() | ||||
|  | ||||
| def registration_policy_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationPolicy, xml_string) | ||||
|  | ||||
|  | ||||
| class UsagePolicy(md.LocalizedURIType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:UsagePolicy element """ | ||||
|  | ||||
|     c_tag = 'UsagePolicy' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedURIType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedURIType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedURIType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedURIType_.c_cardinality.copy() | ||||
|  | ||||
| def usage_policy_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(UsagePolicy, xml_string) | ||||
|  | ||||
|  | ||||
| class PublicationType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:PublicationType element """ | ||||
|  | ||||
|     c_tag = 'PublicationType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['publisher'] = ('publisher', 'string', True) | ||||
|     c_attributes['creationInstant'] = ('creation_instant', 'dateTime', False) | ||||
|     c_attributes['publicationId'] = ('publication_id', 'string', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             publisher=None, | ||||
|             creation_instant=None, | ||||
|             publication_id=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.publisher=publisher | ||||
|         self.creation_instant=creation_instant | ||||
|         self.publication_id=publication_id | ||||
|  | ||||
| def publication_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PublicationType_, xml_string) | ||||
|  | ||||
|  | ||||
| class RegistrationInfoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:RegistrationInfoType element """ | ||||
|  | ||||
|     c_tag = 'RegistrationInfoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:rpi}RegistrationPolicy'] = ('registration_policy', [RegistrationPolicy]) | ||||
|     c_cardinality['registration_policy'] = {"min":0} | ||||
|     c_attributes['registrationAuthority'] = ('registration_authority', 'string', True) | ||||
|     c_attributes['registrationInstant'] = ('registration_instant', 'dateTime', False) | ||||
|     c_child_order.extend(['registration_policy']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             registration_policy=None, | ||||
|             registration_authority=None, | ||||
|             registration_instant=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.registration_policy=registration_policy or [] | ||||
|         self.registration_authority=registration_authority | ||||
|         self.registration_instant=registration_instant | ||||
|  | ||||
| def registration_info_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationInfoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class PublicationInfoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:PublicationInfoType element """ | ||||
|  | ||||
|     c_tag = 'PublicationInfoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:rpi}UsagePolicy'] = ('usage_policy', [UsagePolicy]) | ||||
|     c_cardinality['usage_policy'] = {"min":0} | ||||
|     c_attributes['publisher'] = ('publisher', 'string', True) | ||||
|     c_attributes['creationInstant'] = ('creation_instant', 'dateTime', False) | ||||
|     c_attributes['publicationId'] = ('publication_id', 'string', False) | ||||
|     c_child_order.extend(['usage_policy']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             usage_policy=None, | ||||
|             publisher=None, | ||||
|             creation_instant=None, | ||||
|             publication_id=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.usage_policy=usage_policy or [] | ||||
|         self.publisher=publisher | ||||
|         self.creation_instant=creation_instant | ||||
|         self.publication_id=publication_id | ||||
|  | ||||
| def publication_info_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PublicationInfoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class Publication(PublicationType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:Publication element """ | ||||
|  | ||||
|     c_tag = 'Publication' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = PublicationType_.c_children.copy() | ||||
|     c_attributes = PublicationType_.c_attributes.copy() | ||||
|     c_child_order = PublicationType_.c_child_order[:] | ||||
|     c_cardinality = PublicationType_.c_cardinality.copy() | ||||
|  | ||||
| def publication_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Publication, xml_string) | ||||
|  | ||||
|  | ||||
| class RegistrationInfo(RegistrationInfoType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:RegistrationInfo element """ | ||||
|  | ||||
|     c_tag = 'RegistrationInfo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = RegistrationInfoType_.c_children.copy() | ||||
|     c_attributes = RegistrationInfoType_.c_attributes.copy() | ||||
|     c_child_order = RegistrationInfoType_.c_child_order[:] | ||||
|     c_cardinality = RegistrationInfoType_.c_cardinality.copy() | ||||
|  | ||||
| def registration_info_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RegistrationInfo, xml_string) | ||||
|  | ||||
|  | ||||
| class PublicationInfo(PublicationInfoType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:PublicationInfo element """ | ||||
|  | ||||
|     c_tag = 'PublicationInfo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = PublicationInfoType_.c_children.copy() | ||||
|     c_attributes = PublicationInfoType_.c_attributes.copy() | ||||
|     c_child_order = PublicationInfoType_.c_child_order[:] | ||||
|     c_cardinality = PublicationInfoType_.c_cardinality.copy() | ||||
|  | ||||
| def publication_info_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PublicationInfo, xml_string) | ||||
|  | ||||
|  | ||||
| class PublicationPathType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:PublicationPathType element """ | ||||
|  | ||||
|     c_tag = 'PublicationPathType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:rpi}Publication'] = ('publication', [Publication]) | ||||
|     c_cardinality['publication'] = {"min":0} | ||||
|     c_child_order.extend(['publication']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             publication=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.publication=publication or [] | ||||
|  | ||||
| def publication_path_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PublicationPathType_, xml_string) | ||||
|  | ||||
|  | ||||
| class PublicationPath(PublicationPathType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:rpi:PublicationPath element """ | ||||
|  | ||||
|     c_tag = 'PublicationPath' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = PublicationPathType_.c_children.copy() | ||||
|     c_attributes = PublicationPathType_.c_attributes.copy() | ||||
|     c_child_order = PublicationPathType_.c_child_order[:] | ||||
|     c_cardinality = PublicationPathType_.c_cardinality.copy() | ||||
|  | ||||
| def publication_path_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PublicationPath, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     RegistrationInfo.c_tag: registration_info_from_string, | ||||
|     RegistrationInfoType_.c_tag: registration_info_type__from_string, | ||||
|     RegistrationPolicy.c_tag: registration_policy_from_string, | ||||
|     PublicationInfo.c_tag: publication_info_from_string, | ||||
|     PublicationInfoType_.c_tag: publication_info_type__from_string, | ||||
|     UsagePolicy.c_tag: usage_policy_from_string, | ||||
|     PublicationPath.c_tag: publication_path_from_string, | ||||
|     PublicationPathType_.c_tag: publication_path_type__from_string, | ||||
|     Publication.c_tag: publication_from_string, | ||||
|     PublicationType_.c_tag: publication_type__from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'RegistrationInfo': RegistrationInfo, | ||||
|     'RegistrationInfoType': RegistrationInfoType_, | ||||
|     'RegistrationPolicy': RegistrationPolicy, | ||||
|     'PublicationInfo': PublicationInfo, | ||||
|     'PublicationInfoType': PublicationInfoType_, | ||||
|     'UsagePolicy': UsagePolicy, | ||||
|     'PublicationPath': PublicationPath, | ||||
|     'PublicationPathType': PublicationPathType_, | ||||
|     'Publication': Publication, | ||||
|     'PublicationType': PublicationType_, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										378
									
								
								src/saml2/extension/mdui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										378
									
								
								src/saml2/extension/mdui.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,378 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Mon May  2 14:23:33 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| from saml2 import md | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:metadata:ui' | ||||
|  | ||||
| class DisplayName(md.LocalizedNameType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DisplayName element """ | ||||
|  | ||||
|     c_tag = 'DisplayName' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedNameType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedNameType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedNameType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedNameType_.c_cardinality.copy() | ||||
|  | ||||
| def display_name_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DisplayName, xml_string) | ||||
|  | ||||
|  | ||||
| class Description(md.LocalizedNameType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:Description element """ | ||||
|  | ||||
|     c_tag = 'Description' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedNameType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedNameType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedNameType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedNameType_.c_cardinality.copy() | ||||
|  | ||||
| def description_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Description, xml_string) | ||||
|  | ||||
|  | ||||
| class InformationURL(md.LocalizedURIType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:InformationURL element """ | ||||
|  | ||||
|     c_tag = 'InformationURL' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedURIType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedURIType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedURIType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedURIType_.c_cardinality.copy() | ||||
|  | ||||
| def information_url_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(InformationURL, xml_string) | ||||
|  | ||||
|  | ||||
| class PrivacyStatementURL(md.LocalizedURIType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:PrivacyStatementURL element """ | ||||
|  | ||||
|     c_tag = 'PrivacyStatementURL' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedURIType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedURIType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedURIType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedURIType_.c_cardinality.copy() | ||||
|  | ||||
| def privacy_statement_url_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PrivacyStatementURL, xml_string) | ||||
|  | ||||
|  | ||||
| class ListOfStrings_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:listOfStrings element """ | ||||
|  | ||||
|     c_tag = 'listOfStrings' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'member': 'string', 'base': 'list'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def list_of_strings__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(ListOfStrings_, xml_string) | ||||
|  | ||||
|  | ||||
| class KeywordsType_(ListOfStrings_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:KeywordsType element """ | ||||
|  | ||||
|     c_tag = 'KeywordsType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = ListOfStrings_.c_children.copy() | ||||
|     c_attributes = ListOfStrings_.c_attributes.copy() | ||||
|     c_child_order = ListOfStrings_.c_child_order[:] | ||||
|     c_cardinality = ListOfStrings_.c_cardinality.copy() | ||||
|     c_attributes['{http://www.w3.org/XML/1998/namespace}lang'] = ('lang', 'mdui:listOfStrings', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             lang=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         ListOfStrings_.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.lang=lang | ||||
|  | ||||
| def keywords_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(KeywordsType_, xml_string) | ||||
|  | ||||
|  | ||||
| class LogoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:LogoType element """ | ||||
|  | ||||
|     c_tag = 'LogoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'anyURI'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['height'] = ('height', 'positiveInteger', True) | ||||
|     c_attributes['width'] = ('width', 'positiveInteger', True) | ||||
|     c_attributes['{http://www.w3.org/XML/1998/namespace}lang'] = ('lang', 'anyURI', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             height=None, | ||||
|             width=None, | ||||
|             lang=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.height=height | ||||
|         self.width=width | ||||
|         self.lang=lang | ||||
|  | ||||
| def logo_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(LogoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class IPHint(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:IPHint element """ | ||||
|  | ||||
|     c_tag = 'IPHint' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def ip_hint_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(IPHint, xml_string) | ||||
|  | ||||
|  | ||||
| class DomainHint(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DomainHint element """ | ||||
|  | ||||
|     c_tag = 'DomainHint' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def domain_hint_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DomainHint, xml_string) | ||||
|  | ||||
|  | ||||
| class GeolocationHint(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:GeolocationHint element """ | ||||
|  | ||||
|     c_tag = 'GeolocationHint' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'anyURI'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def geolocation_hint_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(GeolocationHint, xml_string) | ||||
|  | ||||
|  | ||||
| class Keywords(KeywordsType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:Keywords element """ | ||||
|  | ||||
|     c_tag = 'Keywords' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = KeywordsType_.c_children.copy() | ||||
|     c_attributes = KeywordsType_.c_attributes.copy() | ||||
|     c_child_order = KeywordsType_.c_child_order[:] | ||||
|     c_cardinality = KeywordsType_.c_cardinality.copy() | ||||
|  | ||||
| def keywords_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Keywords, xml_string) | ||||
|  | ||||
|  | ||||
| class Logo(LogoType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:Logo element """ | ||||
|  | ||||
|     c_tag = 'Logo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = LogoType_.c_children.copy() | ||||
|     c_attributes = LogoType_.c_attributes.copy() | ||||
|     c_child_order = LogoType_.c_child_order[:] | ||||
|     c_cardinality = LogoType_.c_cardinality.copy() | ||||
|  | ||||
| def logo_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Logo, xml_string) | ||||
|  | ||||
|  | ||||
| class DiscoHintsType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DiscoHintsType element """ | ||||
|  | ||||
|     c_tag = 'DiscoHintsType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}IPHint'] = ('ip_hint', [IPHint]) | ||||
|     c_cardinality['ip_hint'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}DomainHint'] = ('domain_hint', [DomainHint]) | ||||
|     c_cardinality['domain_hint'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}GeolocationHint'] = ('geolocation_hint', [GeolocationHint]) | ||||
|     c_cardinality['geolocation_hint'] = {"min":0} | ||||
|     c_child_order.extend(['ip_hint', 'domain_hint', 'geolocation_hint']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             ip_hint=None, | ||||
|             domain_hint=None, | ||||
|             geolocation_hint=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.ip_hint=ip_hint or [] | ||||
|         self.domain_hint=domain_hint or [] | ||||
|         self.geolocation_hint=geolocation_hint or [] | ||||
|  | ||||
| def disco_hints_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DiscoHintsType_, xml_string) | ||||
|  | ||||
|  | ||||
| class UIInfoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:UIInfoType element """ | ||||
|  | ||||
|     c_tag = 'UIInfoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}DisplayName'] = ('display_name', [DisplayName]) | ||||
|     c_cardinality['display_name'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}Description'] = ('description', [Description]) | ||||
|     c_cardinality['description'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}Keywords'] = ('keywords', [Keywords]) | ||||
|     c_cardinality['keywords'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}Logo'] = ('logo', [Logo]) | ||||
|     c_cardinality['logo'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}InformationURL'] = ('information_url', [InformationURL]) | ||||
|     c_cardinality['information_url'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}PrivacyStatementURL'] = ('privacy_statement_url', [PrivacyStatementURL]) | ||||
|     c_cardinality['privacy_statement_url'] = {"min":0} | ||||
|     c_child_order.extend(['display_name', 'description', 'keywords', 'logo', 'information_url', 'privacy_statement_url']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             display_name=None, | ||||
|             description=None, | ||||
|             keywords=None, | ||||
|             logo=None, | ||||
|             information_url=None, | ||||
|             privacy_statement_url=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.display_name=display_name or [] | ||||
|         self.description=description or [] | ||||
|         self.keywords=keywords or [] | ||||
|         self.logo=logo or [] | ||||
|         self.information_url=information_url or [] | ||||
|         self.privacy_statement_url=privacy_statement_url or [] | ||||
|  | ||||
| def ui_info_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(UIInfoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class DiscoHints(DiscoHintsType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DiscoHints element """ | ||||
|  | ||||
|     c_tag = 'DiscoHints' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = DiscoHintsType_.c_children.copy() | ||||
|     c_attributes = DiscoHintsType_.c_attributes.copy() | ||||
|     c_child_order = DiscoHintsType_.c_child_order[:] | ||||
|     c_cardinality = DiscoHintsType_.c_cardinality.copy() | ||||
|  | ||||
| def disco_hints_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DiscoHints, xml_string) | ||||
|  | ||||
|  | ||||
| class UIInfo(UIInfoType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:UIInfo element """ | ||||
|  | ||||
|     c_tag = 'UIInfo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = UIInfoType_.c_children.copy() | ||||
|     c_attributes = UIInfoType_.c_attributes.copy() | ||||
|     c_child_order = UIInfoType_.c_child_order[:] | ||||
|     c_cardinality = UIInfoType_.c_cardinality.copy() | ||||
|  | ||||
| def ui_info_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(UIInfo, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     UIInfo.c_tag: ui_info_from_string, | ||||
|     UIInfoType_.c_tag: ui_info_type__from_string, | ||||
|     DisplayName.c_tag: display_name_from_string, | ||||
|     Description.c_tag: description_from_string, | ||||
|     InformationURL.c_tag: information_url_from_string, | ||||
|     PrivacyStatementURL.c_tag: privacy_statement_url_from_string, | ||||
|     Keywords.c_tag: keywords_from_string, | ||||
|     KeywordsType_.c_tag: keywords_type__from_string, | ||||
|     ListOfStrings_.c_tag: list_of_strings__from_string, | ||||
|     Logo.c_tag: logo_from_string, | ||||
|     LogoType_.c_tag: logo_type__from_string, | ||||
|     DiscoHints.c_tag: disco_hints_from_string, | ||||
|     DiscoHintsType_.c_tag: disco_hints_type__from_string, | ||||
|     IPHint.c_tag: ip_hint_from_string, | ||||
|     DomainHint.c_tag: domain_hint_from_string, | ||||
|     GeolocationHint.c_tag: geolocation_hint_from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'UIInfo': UIInfo, | ||||
|     'UIInfoType': UIInfoType_, | ||||
|     'DisplayName': DisplayName, | ||||
|     'Description': Description, | ||||
|     'InformationURL': InformationURL, | ||||
|     'PrivacyStatementURL': PrivacyStatementURL, | ||||
|     'Keywords': Keywords, | ||||
|     'KeywordsType': KeywordsType_, | ||||
|     'listOfStrings': ListOfStrings_, | ||||
|     'Logo': Logo, | ||||
|     'LogoType': LogoType_, | ||||
|     'DiscoHints': DiscoHints, | ||||
|     'DiscoHintsType': DiscoHintsType_, | ||||
|     'IPHint': IPHint, | ||||
|     'DomainHint': DomainHint, | ||||
|     'GeolocationHint': GeolocationHint, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										89
									
								
								src/saml2/extension/shibmd.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/saml2/extension/shibmd.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Sun Mar 20 18:06:44 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| import xmldsig as ds | ||||
|  | ||||
| NAMESPACE = 'urn:mace:shibboleth:metadata:1.0' | ||||
|  | ||||
| class Scope(SamlBase): | ||||
|     """The urn:mace:shibboleth:metadata:1.0:Scope element """ | ||||
|  | ||||
|     c_tag = 'Scope' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['regexp'] = ('regexp', 'boolean', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             regexp='false', | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.regexp=regexp | ||||
|  | ||||
| def scope_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Scope, xml_string) | ||||
|  | ||||
|  | ||||
| class KeyAuthority(SamlBase): | ||||
|     """The urn:mace:shibboleth:metadata:1.0:KeyAuthority element """ | ||||
|  | ||||
|     c_tag = 'KeyAuthority' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{http://www.w3.org/2000/09/xmldsig#}KeyInfo'] = ('key_info', [ds.KeyInfo]) | ||||
|     c_cardinality['key_info'] = {"min":1} | ||||
|     c_attributes['VerifyDepth'] = ('verify_depth', 'unsignedByte', False) | ||||
|     c_child_order.extend(['key_info']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             key_info=None, | ||||
|             verify_depth='1', | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.key_info=key_info or [] | ||||
|         self.verify_depth=verify_depth | ||||
|  | ||||
| def key_authority_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(KeyAuthority, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     Scope.c_tag: scope_from_string, | ||||
|     KeyAuthority.c_tag: key_authority_from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'Scope': Scope, | ||||
|     'KeyAuthority': KeyAuthority, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										311
									
								
								src/saml2/extension/ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								src/saml2/extension/ui.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Mon Oct 25 16:17:51 2010 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| from saml2 import md | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:metadata:ui' | ||||
|  | ||||
| class DisplayName(md.LocalizedNameType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DisplayName element """ | ||||
|  | ||||
|     c_tag = 'DisplayName' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedNameType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedNameType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedNameType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedNameType_.c_cardinality.copy() | ||||
|  | ||||
| def display_name_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DisplayName, xml_string) | ||||
|  | ||||
|  | ||||
| class Description(md.LocalizedNameType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:Description element """ | ||||
|  | ||||
|     c_tag = 'Description' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedNameType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedNameType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedNameType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedNameType_.c_cardinality.copy() | ||||
|  | ||||
| def description_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Description, xml_string) | ||||
|  | ||||
|  | ||||
| class InformationURL(md.LocalizedURIType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:InformationURL element """ | ||||
|  | ||||
|     c_tag = 'InformationURL' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedURIType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedURIType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedURIType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedURIType_.c_cardinality.copy() | ||||
|  | ||||
| def information_url_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(InformationURL, xml_string) | ||||
|  | ||||
|  | ||||
| class PrivacyStatementURL(md.LocalizedURIType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:PrivacyStatementURL element """ | ||||
|  | ||||
|     c_tag = 'PrivacyStatementURL' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = md.LocalizedURIType_.c_children.copy() | ||||
|     c_attributes = md.LocalizedURIType_.c_attributes.copy() | ||||
|     c_child_order = md.LocalizedURIType_.c_child_order[:] | ||||
|     c_cardinality = md.LocalizedURIType_.c_cardinality.copy() | ||||
|  | ||||
| def privacy_statement_url_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PrivacyStatementURL, xml_string) | ||||
|  | ||||
|  | ||||
| class LogoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:LogoType element """ | ||||
|  | ||||
|     c_tag = 'LogoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'anyURI'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['height'] = ('height', 'positiveInteger', True) | ||||
|     c_attributes['width'] = ('width', 'positiveInteger', True) | ||||
|     c_attributes['{http://www.w3.org/XML/1998/namespace}lang'] = ('lang', 'anyURI', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             height=None, | ||||
|             width=None, | ||||
|             lang=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.height=height | ||||
|         self.width=width | ||||
|         self.lang=lang | ||||
|  | ||||
| def logo_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(LogoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class IPHint(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:IPHint element """ | ||||
|  | ||||
|     c_tag = 'IPHint' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def ip_hint_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(IPHint, xml_string) | ||||
|  | ||||
|  | ||||
| class DomainHint(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DomainHint element """ | ||||
|  | ||||
|     c_tag = 'DomainHint' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def domain_hint_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DomainHint, xml_string) | ||||
|  | ||||
|  | ||||
| class GeolocationHint(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:GeolocationHint element """ | ||||
|  | ||||
|     c_tag = 'GeolocationHint' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'anyURI'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def geolocation_hint_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(GeolocationHint, xml_string) | ||||
|  | ||||
|  | ||||
| class Logo(LogoType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:Logo element """ | ||||
|  | ||||
|     c_tag = 'Logo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = LogoType_.c_children.copy() | ||||
|     c_attributes = LogoType_.c_attributes.copy() | ||||
|     c_child_order = LogoType_.c_child_order[:] | ||||
|     c_cardinality = LogoType_.c_cardinality.copy() | ||||
|  | ||||
| def logo_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Logo, xml_string) | ||||
|  | ||||
|  | ||||
| class DiscoHintsType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DiscoHintsType element """ | ||||
|  | ||||
|     c_tag = 'DiscoHintsType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}IPHint'] = ('ip_hint', [IPHint]) | ||||
|     c_cardinality['ip_hint'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}DomainHint'] = ('domain_hint', [DomainHint]) | ||||
|     c_cardinality['domain_hint'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}GeolocationHint'] = ('geolocation_hint', [GeolocationHint]) | ||||
|     c_cardinality['geolocation_hint'] = {"min":0} | ||||
|     c_child_order.extend(['ip_hint', 'domain_hint', 'geolocation_hint']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             ip_hint=None, | ||||
|             domain_hint=None, | ||||
|             geolocation_hint=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.ip_hint=ip_hint or [] | ||||
|         self.domain_hint=domain_hint or [] | ||||
|         self.geolocation_hint=geolocation_hint or [] | ||||
|  | ||||
| def disco_hints_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DiscoHintsType_, xml_string) | ||||
|  | ||||
|  | ||||
| class UIInfoType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:UIInfoType element """ | ||||
|  | ||||
|     c_tag = 'UIInfoType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}DisplayName'] = ('display_name', [DisplayName]) | ||||
|     c_cardinality['display_name'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}Description'] = ('description', [Description]) | ||||
|     c_cardinality['description'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}Logo'] = ('logo', [Logo]) | ||||
|     c_cardinality['logo'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}InformationURL'] = ('information_url', [InformationURL]) | ||||
|     c_cardinality['information_url'] = {"min":0} | ||||
|     c_children['{urn:oasis:names:tc:SAML:metadata:ui}PrivacyStatementURL'] = ('privacy_statement_url', [PrivacyStatementURL]) | ||||
|     c_cardinality['privacy_statement_url'] = {"min":0} | ||||
|     c_child_order.extend(['display_name', 'description', 'logo', 'information_url', 'privacy_statement_url']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             display_name=None, | ||||
|             description=None, | ||||
|             logo=None, | ||||
|             information_url=None, | ||||
|             privacy_statement_url=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.display_name=display_name or [] | ||||
|         self.description=description or [] | ||||
|         self.logo=logo or [] | ||||
|         self.information_url=information_url or [] | ||||
|         self.privacy_statement_url=privacy_statement_url or [] | ||||
|  | ||||
| def ui_info_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(UIInfoType_, xml_string) | ||||
|  | ||||
|  | ||||
| class DiscoHints(DiscoHintsType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:DiscoHints element """ | ||||
|  | ||||
|     c_tag = 'DiscoHints' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = DiscoHintsType_.c_children.copy() | ||||
|     c_attributes = DiscoHintsType_.c_attributes.copy() | ||||
|     c_child_order = DiscoHintsType_.c_child_order[:] | ||||
|     c_cardinality = DiscoHintsType_.c_cardinality.copy() | ||||
|  | ||||
| def disco_hints_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(DiscoHints, xml_string) | ||||
|  | ||||
|  | ||||
| class UIInfo(UIInfoType_): | ||||
|     """The urn:oasis:names:tc:SAML:metadata:ui:UIInfo element """ | ||||
|  | ||||
|     c_tag = 'UIInfo' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = UIInfoType_.c_children.copy() | ||||
|     c_attributes = UIInfoType_.c_attributes.copy() | ||||
|     c_child_order = UIInfoType_.c_child_order[:] | ||||
|     c_cardinality = UIInfoType_.c_cardinality.copy() | ||||
|  | ||||
| def ui_info_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(UIInfo, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     UIInfo.c_tag: ui_info_from_string, | ||||
|     UIInfoType_.c_tag: ui_info_type__from_string, | ||||
|     DisplayName.c_tag: display_name_from_string, | ||||
|     Description.c_tag: description_from_string, | ||||
|     InformationURL.c_tag: information_url_from_string, | ||||
|     PrivacyStatementURL.c_tag: privacy_statement_url_from_string, | ||||
|     Logo.c_tag: logo_from_string, | ||||
|     LogoType_.c_tag: logo_type__from_string, | ||||
|     DiscoHints.c_tag: disco_hints_from_string, | ||||
|     DiscoHintsType_.c_tag: disco_hints_type__from_string, | ||||
|     IPHint.c_tag: ip_hint_from_string, | ||||
|     DomainHint.c_tag: domain_hint_from_string, | ||||
|     GeolocationHint.c_tag: geolocation_hint_from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'UIInfo': UIInfo, | ||||
|     'UIInfoType': UIInfoType_, | ||||
|     'DisplayName': DisplayName, | ||||
|     'Description': Description, | ||||
|     'InformationURL': InformationURL, | ||||
|     'PrivacyStatementURL': PrivacyStatementURL, | ||||
|     'Logo': Logo, | ||||
|     'LogoType': LogoType_, | ||||
|     'DiscoHints': DiscoHints, | ||||
|     'DiscoHintsType': DiscoHintsType_, | ||||
|     'IPHint': IPHint, | ||||
|     'DomainHint': DomainHint, | ||||
|     'GeolocationHint': GeolocationHint, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										149
									
								
								src/saml2/httplib2cookie.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/saml2/httplib2cookie.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| # ======================================================================== | ||||
| # Copyright (c) 2007, Metaweb Technologies, Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions | ||||
| # are met: | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| #       notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| #       copyright notice, this list of conditions and the following | ||||
| #       disclaimer in the documentation and/or other materials provided | ||||
| #       with the distribution. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY METAWEB TECHNOLOGIES AND CONTRIBUTORS | ||||
| # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||
| # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL METAWEB | ||||
| # TECHNOLOGIES OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||
| # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | ||||
| # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||
| # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||||
| # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | ||||
| # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||||
| # POSSIBILITY OF SUCH DAMAGE. | ||||
| # ======================================================================== | ||||
|  | ||||
| # | ||||
| # | ||||
| #  httplib2cookie.py allows you to use python's standard | ||||
| #   CookieJar class with httplib2. | ||||
| # | ||||
| # | ||||
|  | ||||
| import re | ||||
| import cookielib | ||||
| from httplib2 import Http | ||||
|  | ||||
| import urllib | ||||
| import urllib2 | ||||
|  | ||||
| class DummyRequest(object): | ||||
|     """Simulated urllib2.Request object for httplib2 | ||||
|  | ||||
|        implements only what's necessary for cookielib.CookieJar to work | ||||
|     """ | ||||
|     def __init__(self, url, headers=None): | ||||
|         self.url = url | ||||
|         self.headers = headers | ||||
|         self.origin_req_host = urllib2.request_host(self) | ||||
|         self.type, r = urllib.splittype(url) | ||||
|         self.host, r = urllib.splithost(r) | ||||
|         if self.host: | ||||
|             self.host = urllib.unquote(self.host) | ||||
|  | ||||
|     def get_full_url(self): | ||||
|         return self.url | ||||
|  | ||||
|     def get_origin_req_host(self): | ||||
|         # TODO to match urllib2 this should be different for redirects | ||||
|         return self.origin_req_host | ||||
|  | ||||
|     def get_type(self): | ||||
|         return self.type | ||||
|  | ||||
|     def get_host(self): | ||||
|         return self.host | ||||
|  | ||||
|     def get_header(self, key, default=None): | ||||
|         return self.headers.get(key.lower(), default) | ||||
|  | ||||
|     def has_header(self, key): | ||||
|         return key in self.headers | ||||
|  | ||||
|     def add_unredirected_header(self, key, val): | ||||
|         # TODO this header should not be sent on redirect | ||||
|         self.headers[key.lower()] = val | ||||
|  | ||||
|     def is_unverifiable(self): | ||||
|         # TODO to match urllib2, this should be set to True when the | ||||
|         #  request is the result of a redirect | ||||
|         return False | ||||
|  | ||||
|  | ||||
| class DummyResponse(object): | ||||
|     """Simulated urllib2.Request object for httplib2 | ||||
|  | ||||
|        implements only what's necessary for cookielib.CookieJar to work | ||||
|     """ | ||||
|     def __init__(self, response): | ||||
|         self.response = response | ||||
|  | ||||
|     def info(self): | ||||
|         return DummyMessage(self.response) | ||||
|  | ||||
|  | ||||
| class DummyMessage(object): | ||||
|     """Simulated mimetools.Message object for httplib2 | ||||
|  | ||||
|        implements only what's necessary for cookielib.CookieJar to work | ||||
|     """ | ||||
|     def __init__(self, response): | ||||
|         self.response = response | ||||
|  | ||||
|     def getheaders(self, k): | ||||
|         k = k.lower() | ||||
|         v = self.response.get(k.lower(), None) | ||||
|         if k not in self.response: | ||||
|             return [] | ||||
|         #return self.response[k].split(re.compile(',\\s*')) | ||||
|  | ||||
|         # httplib2 joins multiple values for the same header | ||||
|         #  using ','.  but the netscape cookie format uses ',' | ||||
|         #  as part of the expires= date format.  so we have | ||||
|         #  to split carefully here - header.split(',') won't do it. | ||||
|         HEADERVAL= re.compile(r'\s*(([^,]|(,\s*\d))+)') | ||||
|         return [h[0] for h in HEADERVAL.findall(self.response[k])] | ||||
|  | ||||
| class CookiefulHttp(Http): | ||||
|     """Subclass of httplib2.Http that keeps cookie state | ||||
|  | ||||
|        constructor takes an optional cookiejar=cookielib.CookieJar | ||||
|  | ||||
|        currently this does not handle redirects completely correctly: | ||||
|        if the server redirects to a different host the original | ||||
|        cookies will still be sent to that host. | ||||
|     """ | ||||
|     def __init__(self, cookiejar=None, **kws): | ||||
|         # note that httplib2.Http is not a new-style-class | ||||
|         Http.__init__(self, **kws) | ||||
|         if cookiejar is None: | ||||
|             cookiejar = cookielib.CookieJar() | ||||
|         self.cookiejar = cookiejar | ||||
|  | ||||
|     def crequest(self, uri, **kws): | ||||
|         """ crequest so it's not messing up the 'real' request method | ||||
|         """ | ||||
|         headers = kws.pop('headers', None) | ||||
|         req = DummyRequest(uri, headers) | ||||
|         self.cookiejar.add_cookie_header(req) | ||||
|         headers = req.headers | ||||
|  | ||||
|         (r, body) = Http.request(self, uri, headers=headers, **kws) | ||||
|  | ||||
|         resp = DummyResponse(r) | ||||
|         self.cookiejar.extract_cookies(resp, req) | ||||
|  | ||||
|         return r, body | ||||
							
								
								
									
										128
									
								
								src/saml2/httputil.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/saml2/httputil.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| __author__ = 'rohe0002' | ||||
|  | ||||
| import cgi | ||||
| from urllib import quote | ||||
|  | ||||
| class Response(object): | ||||
|     _template = None | ||||
|     _status = '200 OK' | ||||
|     _content_type = 'text/html' | ||||
|     _mako_template = None | ||||
|     _mako_lookup = None | ||||
|  | ||||
|     def __init__(self, message=None, **kwargs): | ||||
|         self.status = kwargs.get('status', self._status) | ||||
|         self.response = kwargs.get('response', self._response) | ||||
|         self.template = kwargs.get('template', self._template) | ||||
|         self.mako_template = kwargs.get('mako_template', self._mako_template) | ||||
|         self.mako_lookup = kwargs.get('template_lookup', self._mako_lookup) | ||||
|  | ||||
|         self.message = message | ||||
|  | ||||
|         self.headers = kwargs.get('headers', []) | ||||
|         _content_type = kwargs.get('content', self._content_type) | ||||
|         self.headers.append(('Content-type', _content_type)) | ||||
|  | ||||
|     def __call__(self, environ, start_response, **kwargs): | ||||
|         start_response(self.status, self.headers) | ||||
|         return self.response(self.message or geturl(environ), **kwargs) | ||||
|  | ||||
|     def _response(self, message="", **argv): | ||||
|         if self.template: | ||||
|             return [self.template % message] | ||||
|         elif self.mako_lookup and self.mako_template: | ||||
|             argv["message"] = message | ||||
|             mte = self.mako_lookup.get_template(self.mako_template) | ||||
|             return [mte.render(**argv)] | ||||
|         else: | ||||
|             return [message] | ||||
|  | ||||
| class Created(Response): | ||||
|     _status = "201 Created" | ||||
|  | ||||
| class Redirect(Response): | ||||
|     _template = '<html>\n<head><title>Redirecting to %s</title></head>\n' \ | ||||
|         '<body>\nYou are being redirected to <a href="%s">%s</a>\n' \ | ||||
|         '</body>\n</html>' | ||||
|     _status = '302 Found' | ||||
|  | ||||
|     def __call__(self, environ, start_response): | ||||
|         location = self.message | ||||
|         self.headers.append(('location', location)) | ||||
|         start_response(self.status, self.headers) | ||||
|         return self.response((location, location, location)) | ||||
|  | ||||
| class SeeOther(Response): | ||||
|     _template = '<html>\n<head><title>Redirecting to %s</title></head>\n' \ | ||||
|         '<body>\nYou are being redirected to <a href="%s">%s</a>\n' \ | ||||
|         '</body>\n</html>' | ||||
|     _status = '303 See Other' | ||||
|  | ||||
|     def __call__(self, environ, start_response): | ||||
|         location = self.message | ||||
|         self.headers.append(('location', location)) | ||||
|         start_response(self.status, self.headers) | ||||
|         return self.response((location, location, location)) | ||||
|  | ||||
| class Forbidden(Response): | ||||
|     _status = '403 Forbidden' | ||||
|     _template = "<html>Not allowed to mess with: '%s'</html>" | ||||
|  | ||||
| class BadRequest(Response): | ||||
|     _status = "400 Bad Request" | ||||
|     _template = "<html>%s</html>" | ||||
|  | ||||
| class Unauthorized(Response): | ||||
|     _status = "401 Unauthorized" | ||||
|     _template = "<html>%s</html>" | ||||
|  | ||||
| class NotFound(Response): | ||||
|     _status = '404 NOT FOUND' | ||||
|  | ||||
| class NotAcceptable(Response): | ||||
|     _status = '406 Not Acceptable' | ||||
|  | ||||
| class ServiceError(Response): | ||||
|     _status = '500 Internal Service Error' | ||||
|  | ||||
| def extract(environ, empty=False, err=False): | ||||
|     """Extracts strings in form data and returns a dict. | ||||
|  | ||||
|     :param environ: WSGI environ | ||||
|     :param empty: Stops on empty fields (default: Fault) | ||||
|     :param err: Stops on errors in fields (default: Fault) | ||||
|     """ | ||||
|     formdata = cgi.parse(environ['wsgi.input'], environ, empty, err) | ||||
|     # Remove single entries from lists | ||||
|     for key, value in formdata.iteritems(): | ||||
|         if len(value) == 1: | ||||
|             formdata[key] = value[0] | ||||
|     return formdata | ||||
|  | ||||
| def geturl(environ, query=True, path=True): | ||||
|     """Rebuilds a request URL (from PEP 333). | ||||
|  | ||||
|     :param query: Is QUERY_STRING included in URI (default: True) | ||||
|     :param path: Is path included in URI (default: True) | ||||
|     """ | ||||
|     url = [environ['wsgi.url_scheme'] + '://'] | ||||
|     if environ.get('HTTP_HOST'): | ||||
|         url.append(environ['HTTP_HOST']) | ||||
|     else: | ||||
|         url.append(environ['SERVER_NAME']) | ||||
|         if environ['wsgi.url_scheme'] == 'https': | ||||
|             if environ['SERVER_PORT'] != '443': | ||||
|                 url.append(':' + environ['SERVER_PORT']) | ||||
|         else: | ||||
|             if environ['SERVER_PORT'] != '80': | ||||
|                 url.append(':' + environ['SERVER_PORT']) | ||||
|     if path: | ||||
|         url.append(getpath(environ)) | ||||
|     if query and environ.get('QUERY_STRING'): | ||||
|         url.append('?' + environ['QUERY_STRING']) | ||||
|     return ''.join(url) | ||||
|  | ||||
| def getpath(environ): | ||||
|     """Builds a path.""" | ||||
|     return ''.join([quote(environ.get('SCRIPT_NAME', '')), | ||||
|         quote(environ.get('PATH_INFO', ''))]) | ||||
							
								
								
									
										200
									
								
								src/saml2/mcache.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/saml2/mcache.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import memcache | ||||
| from saml2 import time_util | ||||
| from saml2.cache import ToOld, CacheError | ||||
|  | ||||
| # The assumption is that any subject may consist of data  | ||||
| # gathered from several different sources, all with their own | ||||
| # timeout time. | ||||
|  | ||||
|  | ||||
| def _key(prefix, name): | ||||
|     return "%s_%s" % (prefix, name) | ||||
|              | ||||
| class Cache(object): | ||||
|     def __init__(self, servers, debug=0): | ||||
|         self._cache = memcache.Client(servers, debug) | ||||
|          | ||||
|     def delete(self, subject_id): | ||||
|         entities = self.entities(subject_id) | ||||
|         if entities: | ||||
|             for entity_id in entities: | ||||
|                 if not self._cache.delete(_key(subject_id, entity_id)): | ||||
|                     raise CacheError("Delete failed") | ||||
|      | ||||
|         if not self._cache.delete(subject_id): | ||||
|             raise CacheError("Delete failed") | ||||
|  | ||||
|         subjects = self._cache.get("subjects") | ||||
|         if subjects and subject_id in subjects: | ||||
|             subjects.remove(subject_id) | ||||
|             if not self._cache.set("subjects", subjects): | ||||
|                 raise CacheError("Set operation failed") | ||||
|          | ||||
|     def get_identity(self, subject_id, entities=None): | ||||
|         """ Get all the identity information that has been received and  | ||||
|         are still valid about the subject. | ||||
|          | ||||
|         :param subject_id: The identifier of the subject | ||||
|         :param entities: The identifiers of the entities whoes assertions are | ||||
|             interesting. If the list is empty all entities are interesting. | ||||
|         :return: A 2-tuple consisting of the identity information (a | ||||
|             dictionary of attributes and values) and the list of entities  | ||||
|             whoes information has timed out. | ||||
|         """ | ||||
|         if not entities: | ||||
|             entities = self.entities(subject_id) | ||||
|             if not entities: | ||||
|                 return {}, [] | ||||
|              | ||||
|         res = {} | ||||
|         oldees = [] | ||||
|         for (entity_id, item) in self._cache.get_multi(entities,  | ||||
|                                                     subject_id+'_').items(): | ||||
|             try: | ||||
|                 info = self.get_info(item) | ||||
|             except ToOld: | ||||
|                 oldees.append(entity_id) | ||||
|                 continue | ||||
|             for key, vals in info["ava"].items():             | ||||
|                 try: | ||||
|                     tmp = set(res[key]).union(set(vals)) | ||||
|                     res[key] = list(tmp) | ||||
|                 except KeyError: | ||||
|                     res[key] = vals | ||||
|         return res, oldees | ||||
|  | ||||
|     def get_info(self, item, check_not_on_or_after=True): | ||||
|         """ Get session information about a subject gotten from a | ||||
|         specified IdP/AA. | ||||
|  | ||||
|         :param item: Information stored | ||||
|         :return: The session information as a dictionary | ||||
|         """ | ||||
|         try: | ||||
|             (timestamp, info) = item | ||||
|         except ValueError: | ||||
|             raise ToOld() | ||||
|              | ||||
|         if check_not_on_or_after and not time_util.not_on_or_after(timestamp): | ||||
|             raise ToOld() | ||||
|  | ||||
|         return info or None | ||||
|  | ||||
|     def get(self, subject_id, entity_id, check_not_on_or_after=True): | ||||
|         res = self._cache.get(_key(subject_id, entity_id)) | ||||
|         if not res: | ||||
|             return {} | ||||
|         else: | ||||
|             return self.get_info(res) | ||||
|          | ||||
|     def set(self, subject_id, entity_id, info, timestamp=0): | ||||
|         """ Stores session information in the cache. Assumes that the subject_id | ||||
|         is unique within the context of the Service Provider. | ||||
|          | ||||
|         :param subject_id: The subject identifier | ||||
|         :param entity_id: The identifier of the entity_id/receiver of an  | ||||
|             assertion | ||||
|         :param info: The session info, the assertion is part of this | ||||
|         :param timestamp: A time after which the assertion is not valid. | ||||
|         """ | ||||
|         entities = self._cache.get(subject_id) | ||||
|         if not entities: | ||||
|             entities = [] | ||||
|             subjects = self._cache.get("subjects") | ||||
|             if not subjects: | ||||
|                 subjects = [] | ||||
|             if subject_id not in subjects: | ||||
|                 subjects.append(subject_id) | ||||
|                 if not self._cache.set("subjects", subjects): | ||||
|                     raise CacheError("set failed") | ||||
|          | ||||
|         if entity_id not in entities: | ||||
|             entities.append(entity_id) | ||||
|             if not self._cache.set(subject_id, entities): | ||||
|                 raise CacheError("set failed") | ||||
|            | ||||
|         # Should use memcache's expire | ||||
|         if not self._cache.set(_key(subject_id, entity_id), (timestamp, info)): | ||||
|             raise CacheError("set failed") | ||||
|              | ||||
|     def reset(self, subject_id, entity_id): | ||||
|         """ Scrap the assertions received from a IdP or an AA about a special | ||||
|         subject. | ||||
|          | ||||
|         :param subject_id: The subjects identifier | ||||
|         :param entity_id: The identifier of the entity_id of the assertion | ||||
|         :return: | ||||
|         """ | ||||
|         if not self._cache.set(_key(subject_id, entity_id), {}, 0): | ||||
|             raise CacheError("reset failed") | ||||
|              | ||||
|     def entities(self, subject_id): | ||||
|         """ Returns all the entities of assertions for a subject, disregarding | ||||
|         whether the assertion still is valid or not. | ||||
|          | ||||
|         :param subject_id: The identifier of the subject | ||||
|         :return: A possibly empty list of entity identifiers | ||||
|         """ | ||||
|         res = self._cache.get(subject_id) | ||||
|         if not res: | ||||
|             raise KeyError("No such subject") | ||||
|         else: | ||||
|             return res | ||||
|          | ||||
|     def receivers(self, subject_id): | ||||
|         """ Another name for entities() just to make it more logic in the IdP  | ||||
|             scenario """ | ||||
|         return self.entities(subject_id) | ||||
|          | ||||
|     def active(self, subject_id, entity_id): | ||||
|         """ Returns the status of assertions from a specific entity_id. | ||||
|          | ||||
|         :param subject_id: The ID of the subject | ||||
|         :param entity_id: The entity ID of the entity_id of the assertion | ||||
|         :return: True or False depending on if the assertion is still | ||||
|             valid or not. | ||||
|         """ | ||||
|         try: | ||||
|             (timestamp, info) = self._cache.get(_key(subject_id, entity_id)) | ||||
|         except ValueError: | ||||
|             return False | ||||
|         except TypeError: | ||||
|             return False | ||||
|              | ||||
|         # if not info: | ||||
|         #     return False | ||||
|              | ||||
|         try: | ||||
|             return time_util.not_on_or_after(timestamp) | ||||
|         except ToOld: | ||||
|             return False | ||||
|          | ||||
|     def subjects(self): | ||||
|         """ Return identifiers for all the subjects that are in the cache. | ||||
|          | ||||
|         :return: list of subject identifiers | ||||
|         """ | ||||
|         return self._cache.get("subjects") | ||||
|  | ||||
|     def update(self, subject_id, entity_id, ava): | ||||
|         res = self._cache.get(_key(subject_id, entity_id)) | ||||
|         if res is None: | ||||
|             raise KeyError("No such subject") | ||||
|         else: | ||||
|             info = self.get_info(res) | ||||
|             if info: | ||||
|                 info.update(ava) | ||||
|                 self.set(subject_id, entity_id, info, res[0]) | ||||
|                  | ||||
|     def valid_to(self, subject_id, entity_id, newtime): | ||||
|         try: | ||||
|             (timestamp, info) = self._cache.get(_key(subject_id, entity_id)) | ||||
|         except ValueError: | ||||
|             return False | ||||
|         except TypeError: | ||||
|             info = {} | ||||
|              | ||||
|         if not self._cache.set(_key(subject_id, entity_id), (newtime, info)): | ||||
|             raise CacheError("valid_to failed") | ||||
							
								
								
									
										1807
									
								
								src/saml2/md.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1807
									
								
								src/saml2/md.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										200
									
								
								src/saml2/mdbcache.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/saml2/mdbcache.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| __author__ = 'rolandh' | ||||
|  | ||||
| from pymongo import Connection | ||||
| #import cjson | ||||
| import time | ||||
| from datetime import datetime | ||||
|  | ||||
| from saml2 import time_util | ||||
| from saml2.cache import ToOld | ||||
| from saml2.time_util import TIME_FORMAT | ||||
|  | ||||
| class Cache(object): | ||||
|     def __init__(self, server=None, debug=0, db=None): | ||||
|         if server: | ||||
|             connection = Connection(server) | ||||
|         else: | ||||
|             connection = Connection() | ||||
|  | ||||
|         if db: | ||||
|             self._db = connection[db] | ||||
|         else: | ||||
|             self._db = connection.pysaml2 | ||||
|  | ||||
|         self._cache = self._db.collection | ||||
|         self.debug = debug | ||||
|  | ||||
|     def delete(self, subject_id): | ||||
|         self._cache.remove({"subject_id": subject_id}) | ||||
|  | ||||
|     def get_identity(self, subject_id, entities=None, | ||||
|                      check_not_on_or_after=True): | ||||
|         """ Get all the identity information that has been received and | ||||
|         are still valid about the subject. | ||||
|  | ||||
|         :param subject_id: The identifier of the subject | ||||
|         :param entities: The identifiers of the entities whoes assertions are | ||||
|             interesting. If the list is empty all entities are interesting. | ||||
|         :return: A 2-tuple consisting of the identity information (a | ||||
|             dictionary of attributes and values) and the list of entities | ||||
|             whoes information has timed out. | ||||
|         """ | ||||
|         res = {} | ||||
|         oldees = [] | ||||
|         if not entities: | ||||
|             for item in self._cache.find({"subject_id": subject_id}): | ||||
|                 try: | ||||
|                     info = self._get_info(item, check_not_on_or_after) | ||||
|                 except ToOld: | ||||
|                     oldees.append(item["entity_id"]) | ||||
|                     continue | ||||
|  | ||||
|                 for key, vals in info["ava"].items(): | ||||
|                     try: | ||||
|                         tmp = set(res[key]).union(set(vals)) | ||||
|                         res[key] = list(tmp) | ||||
|                     except KeyError: | ||||
|                         res[key] = vals | ||||
|         else: | ||||
|             for entity_id in entities: | ||||
|                 try: | ||||
|                     info = self.get(subject_id, entity_id, check_not_on_or_after) | ||||
|                 except ToOld: | ||||
|                     oldees.append(entity_id) | ||||
|                     continue | ||||
|  | ||||
|                 for key, vals in info["ava"].items(): | ||||
|                     try: | ||||
|                         tmp = set(res[key]).union(set(vals)) | ||||
|                         res[key] = list(tmp) | ||||
|                     except KeyError: | ||||
|                         res[key] = vals | ||||
|  | ||||
|         return res, oldees | ||||
|                  | ||||
|     def _get_info(self, item, check_not_on_or_after=True): | ||||
|         """ Get session information about a subject gotten from a | ||||
|         specified IdP/AA. | ||||
|  | ||||
|         :param item: Information stored | ||||
|         :return: The session information as a dictionary | ||||
|         """ | ||||
|         timestamp = item["timestamp"] | ||||
|  | ||||
|         if check_not_on_or_after and not time_util.not_on_or_after(timestamp): | ||||
|             raise ToOld() | ||||
|  | ||||
|         try: | ||||
|             return item["info"] | ||||
|         except KeyError: | ||||
|             return None | ||||
|  | ||||
|     def get(self, subject_id, entity_id, check_not_on_or_after=True): | ||||
|         res = self._cache.find_one({"subject_id": subject_id, | ||||
|                                     "entity_id": entity_id}) | ||||
|         if not res: | ||||
|             return {} | ||||
|         else: | ||||
|             return self._get_info(res, check_not_on_or_after) | ||||
|  | ||||
|     def set(self, subject_id, entity_id, info, timestamp=0): | ||||
|         """ Stores session information in the cache. Assumes that the subject_id | ||||
|         is unique within the context of the Service Provider. | ||||
|  | ||||
|         :param subject_id: The subject identifier | ||||
|         :param entity_id: The identifier of the entity_id/receiver of an | ||||
|             assertion | ||||
|         :param info: The session info, the assertion is part of this | ||||
|         :param timestamp: A time after which the assertion is not valid. | ||||
|         """ | ||||
|  | ||||
|         if isinstance(timestamp, datetime) or isinstance(timestamp, | ||||
|                                                          time.struct_time): | ||||
|             timestamp = time.strftime(TIME_FORMAT, timestamp) | ||||
|  | ||||
|         doc = {"subject_id": subject_id, | ||||
|                "entity_id": entity_id, | ||||
|                "info": info, | ||||
|                "timestamp": timestamp} | ||||
|  | ||||
|         _ = self._cache.insert(doc) | ||||
|  | ||||
|  | ||||
|     def reset(self, subject_id, entity_id): | ||||
|         """ Scrap the assertions received from a IdP or an AA about a special | ||||
|         subject. | ||||
|  | ||||
|         :param subject_id: The subjects identifier | ||||
|         :param entity_id: The identifier of the entity_id of the assertion | ||||
|         :return: | ||||
|         """ | ||||
|         self._cache.update({"subject_id":subject_id, | ||||
|                                    "entity_id":entity_id}, | ||||
|                                   {"$set": {"info":{}, "timestamp": 0}}) | ||||
|  | ||||
|     def entities(self, subject_id): | ||||
|         """ Returns all the entities of assertions for a subject, disregarding | ||||
|         whether the assertion still is valid or not. | ||||
|  | ||||
|         :param subject_id: The identifier of the subject | ||||
|         :return: A possibly empty list of entity identifiers | ||||
|         """ | ||||
|         try: | ||||
|             return [i["entity_id"] for i in self._cache.find({"subject_id": | ||||
|                     subject_id})] | ||||
|         except ValueError: | ||||
|             return [] | ||||
|  | ||||
|  | ||||
|     def receivers(self, subject_id): | ||||
|         """ Another name for entities() just to make it more logic in the IdP | ||||
|             scenario """ | ||||
|         return self.entities(subject_id) | ||||
|  | ||||
|     def active(self, subject_id, entity_id): | ||||
|         """ Returns the status of assertions from a specific entity_id. | ||||
|  | ||||
|         :param subject_id: The ID of the subject | ||||
|         :param entity_id: The entity ID of the entity_id of the assertion | ||||
|         :return: True or False depending on if the assertion is still | ||||
|             valid or not. | ||||
|         """ | ||||
|  | ||||
|         item = self._cache.find_one({"subject_id":subject_id, | ||||
|                                    "entity_id":entity_id}) | ||||
|         try: | ||||
|             return time_util.not_on_or_after(item["timestamp"]) | ||||
|         except ToOld: | ||||
|             return False | ||||
|  | ||||
|     def subjects(self): | ||||
|         """ Return identifiers for all the subjects that are in the cache. | ||||
|  | ||||
|         :return: list of subject identifiers | ||||
|         """ | ||||
|  | ||||
|         subj = [i["subject_id"] for i in self._cache.find()] | ||||
|  | ||||
|         return list(set(subj)) | ||||
|  | ||||
|     def update(self, subject_id, entity_id, ava): | ||||
|         """ """ | ||||
|         item = self._cache.find_one({"subject_id":subject_id, | ||||
|                                        "entity_id":entity_id}) | ||||
|         info = item["info"] | ||||
|         info["ava"].update(ava) | ||||
|         self._cache.update({"subject_id":subject_id, | ||||
|                             "entity_id":entity_id}, | ||||
|                             {"$set": {"info":info}}) | ||||
|  | ||||
|  | ||||
|     def valid_to(self, subject_id, entity_id, newtime): | ||||
|         """ """ | ||||
|         self._cache.update({"subject_id":subject_id, | ||||
|                             "entity_id":entity_id}, | ||||
|                             {"$set": {"timestamp": newtime}}) | ||||
|  | ||||
|     def clear(self): | ||||
|         self._cache.remove() | ||||
							
								
								
									
										1362
									
								
								src/saml2/metadata.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1362
									
								
								src/saml2/metadata.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										57
									
								
								src/saml2/population.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/saml2/population.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
|  | ||||
| from saml2.cache import Cache | ||||
|  | ||||
| class Population(object): | ||||
|     def __init__(self, cache=None): | ||||
|         if cache: | ||||
|             if isinstance(cache, basestring): | ||||
|                 self.cache = Cache(cache) | ||||
|             else: | ||||
|                 self.cache = cache | ||||
|         else: | ||||
|             self.cache = Cache() | ||||
|  | ||||
|     def add_information_about_person(self, session_info): | ||||
|         """If there already are information from this source in the cache  | ||||
|         this function will overwrite that information""" | ||||
|          | ||||
|         subject_id = session_info["name_id"] | ||||
|         issuer = session_info["issuer"] | ||||
|         del session_info["issuer"] | ||||
|         self.cache.set(subject_id, issuer, session_info,  | ||||
|                         session_info["not_on_or_after"]) | ||||
|         return subject_id | ||||
|      | ||||
|     def stale_sources_for_person(self, subject_id, sources=None): | ||||
|         if not sources: # assume that all the members has be asked | ||||
|                         # once before, hence they are represented in the cache | ||||
|             sources = self.cache.entities(subject_id) | ||||
|         sources = [m for m in sources \ | ||||
|                         if not self.cache.active(subject_id, m)] | ||||
|         return sources                        | ||||
|          | ||||
|     def issuers_of_info(self, subject_id): | ||||
|         return self.cache.entities(subject_id) | ||||
|  | ||||
|     def get_identity(self, subject_id, entities=None, check_not_on_or_after=True): | ||||
|         return self.cache.get_identity(subject_id, entities, check_not_on_or_after) | ||||
|  | ||||
|     def get_info_from(self, subject_id, entity_id): | ||||
|         return self.cache.get(subject_id, entity_id) | ||||
|          | ||||
|     def subjects(self): | ||||
|         """Returns the name id's for all the persons in the cache""" | ||||
|         return self.cache.subjects() | ||||
|  | ||||
|     def remove_person(self, subject_id): | ||||
|         self.cache.delete(subject_id) | ||||
|          | ||||
|     def get_entityid(self, subject_id, source_id, check_not_on_or_after=True): | ||||
|         try: | ||||
|             return self.cache.get(subject_id, source_id, | ||||
|                                   check_not_on_or_after)["name_id"] | ||||
|         except (KeyError, ValueError): | ||||
|             return "" | ||||
|              | ||||
|     def sources(self, subject_id): | ||||
|         return self.cache.entities(subject_id) | ||||
							
								
								
									
										3
									
								
								src/saml2/profile/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/saml2/profile/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #profile schema descriptions | ||||
| __author__ = 'rolandh' | ||||
|    | ||||
							
								
								
									
										190
									
								
								src/saml2/profile/ecp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/saml2/profile/ecp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Fri May 27 23:08:21 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| from saml2 import saml | ||||
| from saml2 import samlp | ||||
| #import soapenv as S | ||||
|  | ||||
| NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp' | ||||
|  | ||||
| class RequestType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp:RequestType element """ | ||||
|  | ||||
|     c_tag = 'RequestType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:assertion}Issuer'] = ('issuer', saml.Issuer) | ||||
|     c_children['{urn:oasis:names:tc:SAML:2.0:protocol}IDPList'] = ('idp_list', samlp.IDPList) | ||||
|     c_cardinality['idp_list'] = {"min":0, "max":1} | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}mustUnderstand'] = ('must_understand', 'None', True) | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}actor'] = ('actor', 'None', True) | ||||
|     c_attributes['ProviderName'] = ('provider_name', 'string', False) | ||||
|     c_attributes['IsPassive'] = ('is_passive', 'boolean', False) | ||||
|     c_child_order.extend(['issuer', 'idp_list']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             issuer=None, | ||||
|             idp_list=None, | ||||
|             must_understand=None, | ||||
|             actor=None, | ||||
|             provider_name=None, | ||||
|             is_passive=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.issuer=issuer | ||||
|         self.idp_list=idp_list | ||||
|         self.must_understand=must_understand | ||||
|         self.actor=actor | ||||
|         self.provider_name=provider_name | ||||
|         self.is_passive=is_passive | ||||
|  | ||||
| def request_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RequestType_, xml_string) | ||||
|  | ||||
|  | ||||
| class ResponseType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp:ResponseType element """ | ||||
|  | ||||
|     c_tag = 'ResponseType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}mustUnderstand'] = ('must_understand', 'None', True) | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}actor'] = ('actor', 'None', True) | ||||
|     c_attributes['AssertionConsumerServiceURL'] = ('assertion_consumer_service_url', 'anyURI', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             must_understand=None, | ||||
|             actor=None, | ||||
|             assertion_consumer_service_url=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.must_understand=must_understand | ||||
|         self.actor=actor | ||||
|         self.assertion_consumer_service_url=assertion_consumer_service_url | ||||
|  | ||||
| def response_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(ResponseType_, xml_string) | ||||
|  | ||||
|  | ||||
| class RelayStateType_(SamlBase): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp:RelayStateType element """ | ||||
|  | ||||
|     c_tag = 'RelayStateType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}mustUnderstand'] = ('must_understand', 'string', True) | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}actor'] = ('actor', 'string', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             must_understand=None, | ||||
|             actor=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.must_understand=must_understand | ||||
|         self.actor=actor | ||||
|  | ||||
| def relay_state_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RelayStateType_, xml_string) | ||||
|  | ||||
|  | ||||
| class Request(RequestType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp:Request element """ | ||||
|  | ||||
|     c_tag = 'Request' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = RequestType_.c_children.copy() | ||||
|     c_attributes = RequestType_.c_attributes.copy() | ||||
|     c_child_order = RequestType_.c_child_order[:] | ||||
|     c_cardinality = RequestType_.c_cardinality.copy() | ||||
|  | ||||
| def request_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Request, xml_string) | ||||
|  | ||||
|  | ||||
| class Response(ResponseType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp:Response element """ | ||||
|  | ||||
|     c_tag = 'Response' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = ResponseType_.c_children.copy() | ||||
|     c_attributes = ResponseType_.c_attributes.copy() | ||||
|     c_child_order = ResponseType_.c_child_order[:] | ||||
|     c_cardinality = ResponseType_.c_cardinality.copy() | ||||
|  | ||||
| def response_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Response, xml_string) | ||||
|  | ||||
|  | ||||
| class RelayState(RelayStateType_): | ||||
|     """The urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp:RelayState element """ | ||||
|  | ||||
|     c_tag = 'RelayState' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = RelayStateType_.c_children.copy() | ||||
|     c_attributes = RelayStateType_.c_attributes.copy() | ||||
|     c_child_order = RelayStateType_.c_child_order[:] | ||||
|     c_cardinality = RelayStateType_.c_cardinality.copy() | ||||
|  | ||||
| def relay_state_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RelayState, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     Request.c_tag: request_from_string, | ||||
|     RequestType_.c_tag: request_type__from_string, | ||||
|     Response.c_tag: response_from_string, | ||||
|     ResponseType_.c_tag: response_type__from_string, | ||||
|     RelayState.c_tag: relay_state_from_string, | ||||
|     RelayStateType_.c_tag: relay_state_type__from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'Request': Request, | ||||
|     'RequestType': RequestType_, | ||||
|     'Response': Response, | ||||
|     'ResponseType': ResponseType_, | ||||
|     'RelayState': RelayState, | ||||
|     'RelayStateType': RelayStateType_, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										133
									
								
								src/saml2/profile/paos.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/saml2/profile/paos.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Fri May 27 17:30:44 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| #import soapenv as S | ||||
|  | ||||
| NAMESPACE = 'urn:liberty:paos:2003-08' | ||||
|  | ||||
| class RequestType_(SamlBase): | ||||
|     """The urn:liberty:paos:2003-08:RequestType element """ | ||||
|  | ||||
|     c_tag = 'RequestType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['responseConsumerURL'] = ('response_consumer_url', 'anyURI', True) | ||||
|     c_attributes['service'] = ('service', 'anyURI', True) | ||||
|     c_attributes['messageID'] = ('message_id', 'None', False) | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}mustUnderstand'] = ('must_understand', 'None', True) | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}actor'] = ('actor', 'None', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             response_consumer_url=None, | ||||
|             service=None, | ||||
|             message_id=None, | ||||
|             must_understand=None, | ||||
|             actor=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.response_consumer_url=response_consumer_url | ||||
|         self.service=service | ||||
|         self.message_id=message_id | ||||
|         self.must_understand=must_understand | ||||
|         self.actor=actor | ||||
|  | ||||
| def request_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(RequestType_, xml_string) | ||||
|  | ||||
|  | ||||
| class ResponseType_(SamlBase): | ||||
|     """The urn:liberty:paos:2003-08:ResponseType element """ | ||||
|  | ||||
|     c_tag = 'ResponseType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['refToMessageID'] = ('ref_to_message_id', 'None', False) | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}mustUnderstand'] = ('must_understand', 'None', True) | ||||
|     c_attributes['{http://schemas.xmlsoap.org/soap/envelope/}actor'] = ('actor', 'None', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             ref_to_message_id=None, | ||||
|             must_understand=None, | ||||
|             actor=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.ref_to_message_id=ref_to_message_id | ||||
|         self.must_understand=must_understand | ||||
|         self.actor=actor | ||||
|  | ||||
| def response_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(ResponseType_, xml_string) | ||||
|  | ||||
|  | ||||
| class Request(RequestType_): | ||||
|     """The urn:liberty:paos:2003-08:Request element """ | ||||
|  | ||||
|     c_tag = 'Request' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = RequestType_.c_children.copy() | ||||
|     c_attributes = RequestType_.c_attributes.copy() | ||||
|     c_child_order = RequestType_.c_child_order[:] | ||||
|     c_cardinality = RequestType_.c_cardinality.copy() | ||||
|  | ||||
| def request_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Request, xml_string) | ||||
|  | ||||
|  | ||||
| class Response(ResponseType_): | ||||
|     """The urn:liberty:paos:2003-08:Response element """ | ||||
|  | ||||
|     c_tag = 'Response' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = ResponseType_.c_children.copy() | ||||
|     c_attributes = ResponseType_.c_attributes.copy() | ||||
|     c_child_order = ResponseType_.c_child_order[:] | ||||
|     c_cardinality = ResponseType_.c_cardinality.copy() | ||||
|  | ||||
| def response_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Response, xml_string) | ||||
|  | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     Request.c_tag: request_from_string, | ||||
|     RequestType_.c_tag: request_type__from_string, | ||||
|     Response.c_tag: response_from_string, | ||||
|     ResponseType_.c_tag: response_type__from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'Request': Request, | ||||
|     'RequestType': RequestType_, | ||||
|     'Response': Response, | ||||
|     'ResponseType': ResponseType_, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										189
									
								
								src/saml2/request.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								src/saml2/request.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | ||||
| import sys | ||||
|  | ||||
| from attribute_converter import to_local | ||||
| from saml2 import time_util | ||||
| from saml2 import s_utils | ||||
| from saml2.s_utils import OtherError | ||||
|  | ||||
| from saml2.validate import valid_instance | ||||
| from saml2.validate import NotValid | ||||
| from saml2.response import IncorrectlySigned | ||||
|  | ||||
| def _dummy(_arg): | ||||
|     return None | ||||
|      | ||||
| class Request(object): | ||||
|     def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,  | ||||
|                     debug=0): | ||||
|         self.sec = sec_context | ||||
|         self.receiver_addrs = receiver_addrs | ||||
|         self.timeslack = timeslack | ||||
|         self.log = log | ||||
|         self.debug = debug | ||||
|         if self.debug and not self.log: | ||||
|             self.debug = 0 | ||||
|          | ||||
|         self.xmlstr = "" | ||||
|         self.name_id = "" | ||||
|         self.message = None | ||||
|         self.not_on_or_after = 0 | ||||
|  | ||||
|         self.signature_check = _dummy # has to be set !!! | ||||
|      | ||||
|     def _clear(self): | ||||
|         self.xmlstr = "" | ||||
|         self.name_id = "" | ||||
|         self.message = None | ||||
|         self.not_on_or_after = 0 | ||||
|  | ||||
|     def _loads(self, xmldata, decode=True): | ||||
|         if decode: | ||||
|             if self.debug: | ||||
|                 self.log.debug("Expected to decode and inflate xml data") | ||||
|             decoded_xml = s_utils.decode_base64_and_inflate(xmldata) | ||||
|         else: | ||||
|             decoded_xml = xmldata | ||||
|      | ||||
|         # own copy | ||||
|         self.xmlstr = decoded_xml[:] | ||||
|         if self.debug: | ||||
|             self.log.info("xmlstr: %s" % (self.xmlstr,)) | ||||
|         try: | ||||
|             self.message = self.signature_check(decoded_xml) | ||||
|         except TypeError: | ||||
|             raise | ||||
|         except Exception, excp: | ||||
|             if self.log: | ||||
|                 self.log.info("EXCEPTION: %s", excp) | ||||
|      | ||||
|         if not self.message: | ||||
|             if self.log: | ||||
|                 self.log.error("Response was not correctly signed") | ||||
|                 self.log.info(decoded_xml) | ||||
|             raise IncorrectlySigned() | ||||
|      | ||||
|         if self.debug: | ||||
|             self.log.info("request: %s" % (self.message,)) | ||||
|  | ||||
|         try: | ||||
|             valid_instance(self.message) | ||||
|         except NotValid, exc: | ||||
|             if self.log: | ||||
|                 self.log.error("Not valid request: %s" % exc.args[0]) | ||||
|             else: | ||||
|                 print >> sys.stderr, "Not valid request: %s" % exc.args[0] | ||||
|             raise | ||||
|          | ||||
|         return self | ||||
|      | ||||
|     def issue_instant_ok(self): | ||||
|         """ Check that the request was issued at a reasonable time """ | ||||
|         upper = time_util.shift_time(time_util.time_in_a_while(days=1), | ||||
|                                     self.timeslack).timetuple() | ||||
|         lower = time_util.shift_time(time_util.time_a_while_ago(days=1), | ||||
|                                     -self.timeslack).timetuple() | ||||
|         # print "issue_instant: %s" % self.message.issue_instant | ||||
|         # print "%s < x < %s" % (lower, upper) | ||||
|         issued_at = time_util.str_to_time(self.message.issue_instant) | ||||
|         return issued_at > lower and issued_at < upper | ||||
|  | ||||
|     def _verify(self):             | ||||
|         assert self.message.version == "2.0" | ||||
|         if self.message.destination and \ | ||||
|             self.message.destination not in self.receiver_addrs: | ||||
|             if self.log: | ||||
|                 self.log.error("%s != %s" % (self.message.destination,  | ||||
|                                                 self.receiver_addrs)) | ||||
|             else: | ||||
|                 print >> sys.stderr, "%s != %s" % (self.message.destination,  | ||||
|                                                     self.receiver_addrs) | ||||
|             raise OtherError("Not destined for me!") | ||||
|              | ||||
|         assert self.issue_instant_ok() | ||||
|         return self | ||||
|  | ||||
|     def loads(self, xmldata, decode=True): | ||||
|         return self._loads(xmldata, decode) | ||||
|  | ||||
|     def verify(self): | ||||
|         try: | ||||
|             return self._verify() | ||||
|         except AssertionError: | ||||
|             return None | ||||
|              | ||||
|     def subject_id(self): | ||||
|         """ The name of the subject can be in either of  | ||||
|         BaseID, NameID or EncryptedID | ||||
|  | ||||
|         :return: The identifier if there is one | ||||
|         """ | ||||
|  | ||||
|         if "subject" in self.message.keys(): | ||||
|             _subj = self.message.subject | ||||
|             if "base_id" in _subj.keys() and _subj.base_id: | ||||
|                 return _subj.base_id | ||||
|             elif _subj.name_id: | ||||
|                 return _subj.name_id | ||||
|         else: | ||||
|             if "base_id" in self.message.keys() and self.message.base_id: | ||||
|                 return self.message.base_id | ||||
|             elif self.message.name_id: | ||||
|                 return self.message.name_id | ||||
|             else: # EncryptedID | ||||
|                 pass | ||||
|              | ||||
|     def sender(self): | ||||
|         return self.message.issuer.text() | ||||
|          | ||||
| class LogoutRequest(Request): | ||||
|     def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,  | ||||
|                     debug=0): | ||||
|         Request.__init__(self, sec_context, receiver_addrs, log, timeslack,  | ||||
|                             debug) | ||||
|         self.signature_check = self.sec.correctly_signed_logout_request | ||||
|          | ||||
|              | ||||
| class AttributeQuery(Request): | ||||
|     def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,  | ||||
|                     debug=0): | ||||
|         Request.__init__(self, sec_context, receiver_addrs, log, timeslack,  | ||||
|                             debug) | ||||
|         self.signature_check = self.sec.correctly_signed_attribute_query | ||||
|      | ||||
|     def attribute(self): | ||||
|         """ Which attributes that are sought for """ | ||||
|          | ||||
|         return [] | ||||
|  | ||||
|  | ||||
| class AuthnRequest(Request): | ||||
|     def __init__(self, sec_context, attribute_converters, receiver_addrs,  | ||||
|                     log=None, timeslack=0, debug=0): | ||||
|         Request.__init__(self, sec_context, receiver_addrs, log, timeslack,  | ||||
|                             debug) | ||||
|         self.attribute_converters = attribute_converters | ||||
|         self.signature_check = self.sec.correctly_signed_authn_request | ||||
|  | ||||
|  | ||||
|     def attributes(self): | ||||
|         return to_local(self.attribute_converters, self.message) | ||||
|              | ||||
|  | ||||
| class AuthzRequest(Request): | ||||
|     def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0, | ||||
|                     debug=0): | ||||
|         Request.__init__(self, sec_context, receiver_addrs, log, timeslack, | ||||
|                             debug) | ||||
|         self.signature_check = self.sec.correctly_signed_logout_request | ||||
|  | ||||
|     def action(self): | ||||
|         """ Which action authorization is requested for """ | ||||
|         pass | ||||
|  | ||||
|     def evidence(self): | ||||
|         """ The evidence on which the decision is based """ | ||||
|         pass | ||||
|  | ||||
|     def resource(self): | ||||
|         """ On which resource the action is expected to occur """ | ||||
|         pass | ||||
							
								
								
									
										709
									
								
								src/saml2/response.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										709
									
								
								src/saml2/response.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,709 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2010-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| import calendar | ||||
| import base64 | ||||
| import sys | ||||
|  | ||||
| from saml2 import samlp | ||||
| from saml2 import saml | ||||
| from saml2 import extension_element_to_element | ||||
| from saml2 import time_util | ||||
|  | ||||
| from saml2.saml import attribute_from_string | ||||
| from saml2.saml import encrypted_attribute_from_string | ||||
| from saml2.sigver import security_context | ||||
| from saml2.sigver import SignatureError | ||||
| from saml2.sigver import signed | ||||
| from saml2.attribute_converter import to_local | ||||
| from saml2.time_util import str_to_time | ||||
|  | ||||
| from saml2.validate import validate_on_or_after | ||||
| from saml2.validate import validate_before | ||||
| from saml2.validate import valid_instance | ||||
| from saml2.validate import valid_address | ||||
| from saml2.validate import NotValid | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| class IncorrectlySigned(Exception): | ||||
|     pass | ||||
|      | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| def _dummy(_): | ||||
|     return None | ||||
|  | ||||
| def for_me(condition, myself ): | ||||
|     # Am I among the intended audiences | ||||
|     for restriction in condition.audience_restriction: | ||||
|         for audience in restriction.audience: | ||||
|             if audience.text.strip() == myself: | ||||
|                 return True | ||||
|             else: | ||||
|                 #print "Not for me: %s != %s" % (audience.text.strip(), myself) | ||||
|                 pass | ||||
|      | ||||
|     return False | ||||
|  | ||||
| def authn_response(conf, return_addr, outstanding_queries=None, | ||||
|                     log=None, timeslack=0, debug=0, asynchop=True, | ||||
|                     allow_unsolicited=False): | ||||
|     sec = security_context(conf) | ||||
|     if not timeslack: | ||||
|         try: | ||||
|             timeslack = int(conf.accepted_time_diff) | ||||
|         except TypeError: | ||||
|             timeslack = 0 | ||||
|      | ||||
|     return AuthnResponse(sec, conf.attribute_converters, conf.entityid, | ||||
|                         return_addr, outstanding_queries, log, timeslack,  | ||||
|                         debug, asynchop=asynchop, | ||||
|                         allow_unsolicited=allow_unsolicited) | ||||
|  | ||||
| # comes in over SOAP so synchronous | ||||
| def attribute_response(conf, return_addr, log=None, timeslack=0, debug=0, | ||||
|                        asynchop=False, test=False): | ||||
|     sec = security_context(conf) | ||||
|     if not timeslack: | ||||
|         try: | ||||
|             timeslack = int(conf.accepted_time_diff) | ||||
|         except TypeError: | ||||
|             timeslack = 0 | ||||
|  | ||||
|     return AttributeResponse(sec, conf.attribute_converters, conf.entityid, | ||||
|                                 return_addr, log, timeslack, debug, | ||||
|                                 asynchop=asynchop, test=test) | ||||
|  | ||||
| class StatusResponse(object): | ||||
|     def __init__(self, sec_context, return_addr=None, log=None, timeslack=0,  | ||||
|                     debug=0, request_id=0): | ||||
|         self.sec = sec_context | ||||
|         self.return_addr = return_addr | ||||
|  | ||||
|         self.timeslack = timeslack | ||||
|         self.request_id = request_id | ||||
|         self.log = log | ||||
|         self.debug = debug | ||||
|         if self.debug and not self.log: | ||||
|             self.debug = 0 | ||||
|          | ||||
|         self.xmlstr = "" | ||||
|         self.name_id = "" | ||||
|         self.response = None | ||||
|         self.not_on_or_after = 0 | ||||
|         self.in_response_to = None | ||||
|         self.signature_check = self.sec.correctly_signed_response | ||||
|         self.not_signed = False | ||||
|      | ||||
|     def _clear(self): | ||||
|         self.xmlstr = "" | ||||
|         self.name_id = "" | ||||
|         self.response = None | ||||
|         self.not_on_or_after = 0 | ||||
|          | ||||
|     def _postamble(self): | ||||
|         if not self.response: | ||||
|             if self.log: | ||||
|                 self.log.error("Response was not correctly signed") | ||||
|                 if self.xmlstr: | ||||
|                     self.log.info(self.xmlstr) | ||||
|             raise IncorrectlySigned() | ||||
|      | ||||
|         if self.debug: | ||||
|             self.log.info("response: %s" % (self.response,)) | ||||
|  | ||||
|         try: | ||||
|             valid_instance(self.response) | ||||
|         except NotValid, exc: | ||||
|             if self.log: | ||||
|                 self.log.error("Not valid response: %s" % exc.args[0]) | ||||
|             else: | ||||
|                 print >> sys.stderr, "Not valid response: %s" % exc.args[0] | ||||
|          | ||||
|             self._clear() | ||||
|             return self | ||||
|          | ||||
|         self.in_response_to = self.response.in_response_to | ||||
|         return self | ||||
|          | ||||
|     def load_instance(self, instance): | ||||
|         if signed(instance): | ||||
|             # This will check signature on Assertion which is the default | ||||
|             try: | ||||
|                 self.response = self.sec.check_signature(instance) | ||||
|             except SignatureError: # The response as a whole might be signed or not | ||||
|                 self.response = self.sec.check_signature(instance, | ||||
|                                                     samlp.NAMESPACE+":Response") | ||||
|         else: | ||||
|             self.not_signed = True | ||||
|             self.response = instance | ||||
|              | ||||
|         return self._postamble() | ||||
|          | ||||
|     def _loads(self, xmldata, decode=True, origxml=None): | ||||
|         if decode: | ||||
|             decoded_xml = base64.b64decode(xmldata) | ||||
|         else: | ||||
|             decoded_xml = xmldata | ||||
|      | ||||
|         # own copy | ||||
|         self.xmlstr = decoded_xml[:] | ||||
|         if self.debug: | ||||
|             self.log.info("xmlstr: %s" % (self.xmlstr,)) | ||||
| #            fil = open("response.xml", "w") | ||||
| #            fil.write(self.xmlstr) | ||||
| #            fil.close() | ||||
|  | ||||
|         try: | ||||
|             self.response = self.signature_check(decoded_xml, origdoc=origxml) | ||||
|         except TypeError: | ||||
|             raise | ||||
|         except SignatureError: | ||||
|             raise | ||||
|         except Exception, excp: | ||||
|             if self.log: | ||||
|                 self.log.info("EXCEPTION: %s", excp) | ||||
|      | ||||
|         #print "<", self.response | ||||
|          | ||||
|         return self._postamble() | ||||
|      | ||||
|     def status_ok(self): | ||||
|         if self.response.status: | ||||
|             status = self.response.status | ||||
|             if self.log: | ||||
|                 self.log.info("status: %s" % (status,)) | ||||
|             if status.status_code.value != samlp.STATUS_SUCCESS: | ||||
|                 if self.log: | ||||
|                     self.log.info("Not successful operation: %s" % status) | ||||
|                 raise Exception( | ||||
|                     "Not successful according to: %s" % \ | ||||
|                     status.status_code.value) | ||||
|         return True | ||||
|  | ||||
|     def issue_instant_ok(self): | ||||
|         """ Check that the response was issued at a reasonable time """ | ||||
|         upper = time_util.shift_time(time_util.time_in_a_while(days=1), | ||||
|                                     self.timeslack).timetuple() | ||||
|         lower = time_util.shift_time(time_util.time_a_while_ago(days=1), | ||||
|                                     -self.timeslack).timetuple() | ||||
|         # print "issue_instant: %s" % self.response.issue_instant | ||||
|         # print "%s < x < %s" % (lower, upper) | ||||
|         issued_at = str_to_time(self.response.issue_instant) | ||||
|         return issued_at > lower and issued_at < upper | ||||
|  | ||||
|     def _verify(self): | ||||
|         if self.request_id and self.in_response_to and \ | ||||
|             self.in_response_to != self.request_id: | ||||
|             if self.log: | ||||
|                 self.log.error("Not the id I expected: %s != %s" % ( | ||||
|                                                         self.in_response_to, | ||||
|                                                         self.request_id)) | ||||
|             return None | ||||
|              | ||||
|         assert self.response.version == "2.0" | ||||
|         if self.response.destination and \ | ||||
|             self.response.destination != self.return_addr: | ||||
|             if self.log: | ||||
|                 self.log.error("%s != %s" % (self.response.destination,  | ||||
|                                                 self.return_addr)) | ||||
|             return None | ||||
|              | ||||
|         assert self.issue_instant_ok() | ||||
|         assert self.status_ok() | ||||
|         return self | ||||
|  | ||||
|     def loads(self, xmldata, decode=True, origxml=None): | ||||
|         return self._loads(xmldata, decode, origxml) | ||||
|  | ||||
|     def verify(self): | ||||
|         try: | ||||
|             return self._verify() | ||||
|         except AssertionError, exc: | ||||
|             self.log.error("Assertion error: %s" % exc) | ||||
|             return None | ||||
|  | ||||
|     def update(self, mold): | ||||
|         self.xmlstr = mold.xmlstr | ||||
|         self.in_response_to = mold.in_response_to | ||||
|         self.response = mold.response | ||||
|          | ||||
|     def issuer(self): | ||||
|         return self.response.issuer.text.strip() | ||||
|          | ||||
| class LogoutResponse(StatusResponse): | ||||
|     def __init__(self, sec_context, return_addr=None, log=None, timeslack=0,  | ||||
|                     debug=0): | ||||
|         StatusResponse.__init__(self, sec_context, return_addr, log, timeslack,  | ||||
|                                 debug) | ||||
|         self.signature_check = self.sec.correctly_signed_logout_response | ||||
|  | ||||
| #class AttributeResponse(StatusResponse): | ||||
| #    def __init__(self, sec_context, attribute_converters, entity_id, | ||||
| #                    return_addr=None, log=None, timeslack=0, debug=0): | ||||
| #        StatusResponse.__init__(self, sec_context, return_addr, log, timeslack, | ||||
| #                                debug) | ||||
| #        self.entity_id = entity_id | ||||
| #        self.attribute_converters = attribute_converters | ||||
| #        self.assertion = None | ||||
| # | ||||
| #    def get_identity(self): | ||||
| #        # The assertion can contain zero or one attributeStatements | ||||
| #        if not self.assertion.attribute_statement: | ||||
| #            self.log.error("Missing Attribute Statement") | ||||
| #            ava = {} | ||||
| #        else: | ||||
| #            assert len(self.assertion.attribute_statement) == 1 | ||||
| # | ||||
| #            if self.debug: | ||||
| #                self.log.info("Attribute Statement: %s" % ( | ||||
| #                                    self.assertion.attribute_statement[0],)) | ||||
| #                for aconv in self.attribute_converters: | ||||
| #                    self.log.info( | ||||
| #                            "Converts name format: %s" % (aconv.name_format,)) | ||||
| # | ||||
| #            ava = to_local(self.attribute_converters, | ||||
| #                            self.assertion.attribute_statement[0]) | ||||
| #        return ava | ||||
| # | ||||
| #    def session_info(self): | ||||
| #        """ Returns a predefined set of information gleened from the | ||||
| #        response. | ||||
| #        :returns: Dictionary with information | ||||
| #        """ | ||||
| #        if self.session_not_on_or_after > 0: | ||||
| #            nooa = self.session_not_on_or_after | ||||
| #        else: | ||||
| #            nooa = self.not_on_or_after | ||||
| # | ||||
| #        return { "ava": self.ava, "name_id": self.name_id, | ||||
| #                "came_from": self.came_from, "issuer": self.issuer(), | ||||
| #                "not_on_or_after": nooa, | ||||
| #                "authn_info": self.authn_info() } | ||||
|     | ||||
| class AuthnResponse(StatusResponse): | ||||
|     """ This is where all the profile compliance is checked. | ||||
|     This one does saml2int compliance. """ | ||||
|      | ||||
|     def __init__(self, sec_context, attribute_converters, entity_id,  | ||||
|                     return_addr=None, outstanding_queries=None, log=None,  | ||||
|                     timeslack=0, debug=0, asynchop=True, | ||||
|                     allow_unsolicited=False, test=False): | ||||
|  | ||||
|         StatusResponse.__init__(self, sec_context, return_addr, log, | ||||
|                                     timeslack, debug) | ||||
|         self.entity_id = entity_id | ||||
|         self.attribute_converters = attribute_converters | ||||
|         if outstanding_queries: | ||||
|             self.outstanding_queries = outstanding_queries | ||||
|         else: | ||||
|             self.outstanding_queries = {} | ||||
|         self.context = "AuthnReq"         | ||||
|         self.came_from = "" | ||||
|         self.ava = None | ||||
|         self.assertion = None | ||||
|         self.session_not_on_or_after = 0 | ||||
|         self.asynchop = asynchop | ||||
|         self.allow_unsolicited = allow_unsolicited | ||||
|         self.test = test | ||||
|  | ||||
|     def loads(self, xmldata, decode=True, origxml=None): | ||||
|         self._loads(xmldata, decode, origxml) | ||||
|          | ||||
|         if self.asynchop: | ||||
|             if self.in_response_to in self.outstanding_queries: | ||||
|                 self.came_from = self.outstanding_queries[self.in_response_to] | ||||
|                 del self.outstanding_queries[self.in_response_to] | ||||
|             elif self.allow_unsolicited: | ||||
|                 pass | ||||
|             else: | ||||
|                 if self.log: | ||||
|                     self.log("Unsolicited response") | ||||
|                 raise Exception("Unsolicited response") | ||||
|              | ||||
|         return self | ||||
|      | ||||
|     def clear(self): | ||||
|         self._clear() | ||||
|         self.came_from = "" | ||||
|         self.ava = None | ||||
|         self.assertion = None | ||||
|          | ||||
|     def authn_statement_ok(self, optional=False): | ||||
|         try: | ||||
|             # the assertion MUST contain one AuthNStatement | ||||
|             assert len(self.assertion.authn_statement) == 1 | ||||
|         except AssertionError: | ||||
|             if optional: | ||||
|                 return True | ||||
|             else: | ||||
|                 raise | ||||
|              | ||||
|         authn_statement = self.assertion.authn_statement[0] | ||||
|         if authn_statement.session_not_on_or_after: | ||||
|             if validate_on_or_after(authn_statement.session_not_on_or_after, | ||||
|                                     self.timeslack): | ||||
|                 self.session_not_on_or_after = calendar.timegm( | ||||
|                     time_util.str_to_time(authn_statement.session_not_on_or_after)) | ||||
|             else: | ||||
|                 return False | ||||
|         return True | ||||
|         # check authn_statement.session_index | ||||
|      | ||||
|     def condition_ok(self, lax=False): | ||||
|         # The Identity Provider MUST include a <saml:Conditions> element | ||||
|         #print "Conditions",assertion.conditions | ||||
|         if self.test: | ||||
|             lax = True | ||||
|         assert self.assertion.conditions | ||||
|         condition = self.assertion.conditions | ||||
|         if self.debug and self.log: | ||||
|             self.log.info("condition: %s" % condition) | ||||
|          | ||||
|         try: | ||||
|             self.not_on_or_after = validate_on_or_after( | ||||
|                                                     condition.not_on_or_after, | ||||
|                                                     self.timeslack) | ||||
|             validate_before(condition.not_before, self.timeslack) | ||||
|         except Exception, excp: | ||||
|             if self.log: | ||||
|                 self.log.error("Exception on condition: %s" % (excp,)) | ||||
|             if not lax: | ||||
|                 raise | ||||
|             else: | ||||
|                 self.not_on_or_after = 0 | ||||
|          | ||||
|         if not for_me(condition, self.entity_id): | ||||
|             if not lax: | ||||
|                 #print condition | ||||
|                 #print self.entity_id | ||||
|                 raise Exception("Not for me!!!") | ||||
|          | ||||
|         return True | ||||
|  | ||||
|     def decrypt_attributes(self, attribute_statement): | ||||
|         """ | ||||
|         Decrypts possible encrypted attributes and adds the decrypts to the | ||||
|         list of attributes. | ||||
|  | ||||
|         :param attribute_statement: A SAML.AttributeStatement which might | ||||
|             contain both encrypted attributes and attributes. | ||||
|         """ | ||||
| #        _node_name = [ | ||||
| #            "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedData", | ||||
| #            "urn:oasis:names:tc:SAML:2.0:assertion:EncryptedAttribute"] | ||||
|  | ||||
|         for encattr in attribute_statement.encrypted_attribute: | ||||
|             if not encattr.encrypted_key: | ||||
|                 _decr = self.sec.decrypt(encattr.encrypted_data) | ||||
|                 _attr = attribute_from_string(_decr) | ||||
|                 attribute_statement.attribute.append(_attr) | ||||
|             else: | ||||
|                 _decr = self.sec.decrypt(encattr) | ||||
|                 enc_attr = encrypted_attribute_from_string(_decr) | ||||
|                 attrlist = enc_attr.extensions_as_elements("Attribute", saml) | ||||
|                 attribute_statement.attribute.extend(attrlist) | ||||
|  | ||||
|     def get_identity(self): | ||||
|         """ The assertion can contain zero or one attributeStatements | ||||
|  | ||||
|         """ | ||||
|         if not self.assertion.attribute_statement: | ||||
|             if self.log: | ||||
|                 self.log.error("Missing Attribute Statement") | ||||
|             ava = {} | ||||
|         else: | ||||
|             assert len(self.assertion.attribute_statement) == 1 | ||||
|             _attr_statem = self.assertion.attribute_statement[0] | ||||
|  | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("Attribute Statement: %s" % (_attr_statem,)) | ||||
|                 for aconv in self.attribute_converters: | ||||
|                     self.log.info( | ||||
|                             "Converts name format: %s" % (aconv.name_format,)) | ||||
|  | ||||
|             self.decrypt_attributes(_attr_statem) | ||||
|             ava = to_local(self.attribute_converters, _attr_statem) | ||||
|         return ava | ||||
|      | ||||
|     def get_subject(self): | ||||
|         """ The assertion must contain a Subject | ||||
|         """ | ||||
|         assert self.assertion.subject | ||||
|         subject = self.assertion.subject | ||||
|         subjconf = [] | ||||
|         for subject_confirmation in subject.subject_confirmation: | ||||
|             data = subject_confirmation.subject_confirmation_data | ||||
|             if not data: | ||||
|                 # I don't know where this belongs so I ignore it | ||||
|                 continue | ||||
|                  | ||||
|             if data.address: | ||||
|                 if not valid_address(data.address): | ||||
|                     # ignore this subject_confirmation | ||||
|                     continue | ||||
|                      | ||||
|             # These two will raise exception if untrue | ||||
|             validate_on_or_after(data.not_on_or_after, self.timeslack) | ||||
|             validate_before(data.not_before, self.timeslack) | ||||
|              | ||||
|             # not_before must be < not_on_or_after | ||||
|             if not time_util.later_than(data.not_on_or_after, data.not_before): | ||||
|                 continue | ||||
|              | ||||
|             if self.asynchop and not self.came_from: | ||||
|                 if data.in_response_to in self.outstanding_queries: | ||||
|                     self.came_from = self.outstanding_queries[ | ||||
|                                                         data.in_response_to] | ||||
|                     del self.outstanding_queries[data.in_response_to] | ||||
|                 elif self.allow_unsolicited: | ||||
|                     pass | ||||
|                 else: | ||||
|                     # This is where I don't allow unsolicited reponses | ||||
|                     # Either in_response_to == None or has a value I don't | ||||
|                     # recognize | ||||
|                     if self.debug and self.log: | ||||
|                         self.log.info( | ||||
|                                 "in response to: '%s'" % data.in_response_to) | ||||
|                         self.log.info("outstanding queries: %s" % \ | ||||
|                                             self.outstanding_queries.keys()) | ||||
|                     raise Exception( | ||||
|                     "Combination of session id and requestURI I don't recall") | ||||
|                          | ||||
|             subjconf.append(subject_confirmation) | ||||
|              | ||||
|         if not subjconf: | ||||
|             raise Exception("No valid subject confirmation") | ||||
|              | ||||
|         subject.subject_confirmation = subjconf | ||||
|          | ||||
|         # The subject must contain a name_id | ||||
|         assert subject.name_id | ||||
|         self.name_id = subject.name_id.text.strip() | ||||
|         return self.name_id | ||||
|      | ||||
|     def _assertion(self, assertion): | ||||
|         self.assertion = assertion | ||||
|          | ||||
|         if self.debug and self.log: | ||||
|             self.log.info("assertion context: %s" % (self.context,)) | ||||
|             self.log.info("assertion keys: %s" % (assertion.keyswv())) | ||||
|             self.log.info("outstanding_queries: %s" % ( | ||||
|                                                     self.outstanding_queries,)) | ||||
|          | ||||
|         #if self.context == "AuthnReq" or self.context == "AttrQuery": | ||||
|         if self.context == "AuthnReq": | ||||
|             self.authn_statement_ok() | ||||
| #        elif self.context == "AttrQuery": | ||||
| #            self.authn_statement_ok(True) | ||||
|  | ||||
|         if not self.condition_ok(): | ||||
|             return None | ||||
|          | ||||
|         if self.debug and self.log: | ||||
|             self.log.info("--- Getting Identity ---") | ||||
|  | ||||
|         if self.context == "AuthnReq" or self.context == "AttrQuery": | ||||
|             self.ava = self.get_identity() | ||||
|          | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("--- AVA: %s" % (self.ava,)) | ||||
|          | ||||
|         try: | ||||
|             self.get_subject() | ||||
|             if self.asynchop: | ||||
|                 if self.allow_unsolicited: | ||||
|                     pass | ||||
|                 elif not self.came_from: | ||||
|                     return False | ||||
|             return True | ||||
|         except Exception, exc: | ||||
|             self.log.error("Exception: %s" % exc) | ||||
|             return False | ||||
|      | ||||
|     def _encrypted_assertion(self, xmlstr): | ||||
|         if xmlstr.encrypted_data: | ||||
|             assertion_str = self.sec.decrypt(xmlstr.encrypted_data) | ||||
|             assertion = saml.assertion_from_string(assertion_str) | ||||
|         else: | ||||
|             decrypt_xml = self.sec.decrypt(xmlstr) | ||||
|  | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("Decryption successfull") | ||||
|  | ||||
|             self.response = samlp.response_from_string(decrypt_xml) | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("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) | ||||
|              | ||||
|         if self.debug and self.log: | ||||
|             self.log.info("Decrypted Assertion: %s" % assertion) | ||||
|         return self._assertion(assertion) | ||||
|      | ||||
|     def parse_assertion(self): | ||||
|         try: | ||||
|             assert len(self.response.assertion) == 1 or \ | ||||
|                     len(self.response.encrypted_assertion) == 1 | ||||
|         except AssertionError: | ||||
|             raise Exception("No assertion part") | ||||
|          | ||||
|         if self.response.assertion: | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("***Unencrypted response***") | ||||
|             return self._assertion(self.response.assertion[0]) | ||||
|         else: | ||||
|             if self.debug and self.log: | ||||
|                 self.log.info("***Encrypted response***") | ||||
|             return self._encrypted_assertion( | ||||
|                                         self.response.encrypted_assertion[0]) | ||||
|          | ||||
|  | ||||
|     def verify(self): | ||||
|         """ Verify that the assertion is syntactically correct and | ||||
|         the signature is correct if present.""" | ||||
|          | ||||
|         try: | ||||
|             self._verify() | ||||
|         except AssertionError: | ||||
|             return None | ||||
|          | ||||
|         if self.parse_assertion(): | ||||
|             return self | ||||
|         else: | ||||
|             self.log.error("Could not parse the assertion") | ||||
|             return None | ||||
|          | ||||
|     def session_id(self): | ||||
|         """ Returns the SessionID of the response """  | ||||
|         return self.response.in_response_to | ||||
|      | ||||
|     def id(self): | ||||
|         """ Return the ID of the response """ | ||||
|         return self.response.id | ||||
|      | ||||
|     def authn_info(self): | ||||
|         res = [] | ||||
|         for astat in self.assertion.authn_statement: | ||||
|             context = astat.authn_context | ||||
|             if context: | ||||
|                 aclass = context.authn_context_class_ref.text | ||||
|                 try: | ||||
|                     authn_auth = [ | ||||
|                             a.text for a in context.authenticating_authority] | ||||
|                 except AttributeError: | ||||
|                     authn_auth = [] | ||||
|                 res.append((aclass, authn_auth)) | ||||
|         return res | ||||
|  | ||||
|     def authz_decision_info(self): | ||||
|         res = {"permit":[], "deny": [], "indeterminate":[] } | ||||
|         for adstat in self.assertion.authz_decision_statement: | ||||
|             # one of 'Permit', 'Deny', 'Indeterminate' | ||||
|             res[adstat.decision.text.lower()] = adstat | ||||
|         return res | ||||
|  | ||||
|     def session_info(self): | ||||
|         """ Returns a predefined set of information gleened from the  | ||||
|         response. | ||||
|         :returns: Dictionary with information | ||||
|         """ | ||||
|         if self.session_not_on_or_after > 0: | ||||
|             nooa = self.session_not_on_or_after | ||||
|         else: | ||||
|             nooa = self.not_on_or_after | ||||
|  | ||||
|         if self.context == "AuthzQuery": | ||||
|             return {"name_id": self.name_id, | ||||
|                     "came_from": self.came_from, "issuer": self.issuer(), | ||||
|                     "not_on_or_after": nooa, | ||||
|                     "authz_decision_info": self.authz_decision_info() } | ||||
|         else: | ||||
|             return { "ava": self.ava, "name_id": self.name_id, | ||||
|                     "came_from": self.came_from, "issuer": self.issuer(), | ||||
|                     "not_on_or_after": nooa, | ||||
|                     "authn_info": self.authn_info() } | ||||
|      | ||||
|     def __str__(self): | ||||
|         return "%s" % self.xmlstr | ||||
|  | ||||
| class AttributeResponse(AuthnResponse): | ||||
|     def __init__(self, sec_context, attribute_converters, entity_id, | ||||
|                     return_addr=None, log=None, timeslack=0, debug=0, | ||||
|                     asynchop=False, test=False): | ||||
|  | ||||
|         AuthnResponse.__init__(self, sec_context, attribute_converters, | ||||
|                                 entity_id, return_addr, log=log, | ||||
|                                 timeslack=timeslack, debug=debug, | ||||
|                                 asynchop=asynchop, test=test) | ||||
|         self.entity_id = entity_id | ||||
|         self.attribute_converters = attribute_converters | ||||
|         self.assertion = None | ||||
|         self.context = "AttrQuery" | ||||
|  | ||||
| class AuthzResponse(AuthnResponse): | ||||
|     """ A successful response will be in the form of assertions containing | ||||
|     authorization decision statements.""" | ||||
|     def __init__(self, sec_context, attribute_converters, entity_id, | ||||
|                     return_addr=None, log=None, timeslack=0, debug=0, | ||||
|                     asynchop=False): | ||||
|         AuthnResponse.__init__(self, sec_context, attribute_converters, | ||||
|                                 entity_id, return_addr, log=log, | ||||
|                                 timeslack=timeslack, debug=debug, | ||||
|                                 asynchop=asynchop) | ||||
|         self.entity_id = entity_id | ||||
|         self.attribute_converters = attribute_converters | ||||
|         self.assertion = None | ||||
|         self.context = "AuthzQuery" | ||||
|  | ||||
| def response_factory(xmlstr, conf, return_addr=None, | ||||
|                         outstanding_queries=None, log=None,  | ||||
|                         timeslack=0, debug=0, decode=True, request_id=0, | ||||
|                         origxml=None, asynchop=True, allow_unsolicited=False): | ||||
|     sec_context = security_context(conf) | ||||
|     if not timeslack: | ||||
|         try: | ||||
|             timeslack = int(conf.accepted_time_diff) | ||||
|         except TypeError: | ||||
|             timeslack = 0 | ||||
|              | ||||
|     attribute_converters = conf.attribute_converters | ||||
|     entity_id = conf.entityid | ||||
|  | ||||
|     response = StatusResponse(sec_context, return_addr, log, timeslack,  | ||||
|                                         debug, request_id) | ||||
|     try: | ||||
|         response.loads(xmlstr, decode, origxml) | ||||
|         if response.response.assertion or response.response.encrypted_assertion: | ||||
|             authnresp = AuthnResponse(sec_context, attribute_converters,  | ||||
|                             entity_id, return_addr, outstanding_queries, log, | ||||
|                             timeslack, debug, asynchop, allow_unsolicited) | ||||
|             authnresp.update(response) | ||||
|             return authnresp | ||||
|     except TypeError: | ||||
|         response.signature_check = sec_context.correctly_signed_logout_response | ||||
|         response.loads(xmlstr, decode, origxml) | ||||
|         logoutresp = LogoutResponse(sec_context, return_addr, log,  | ||||
|                                         timeslack, debug) | ||||
|         logoutresp.update(response) | ||||
|         return logoutresp | ||||
|          | ||||
|     return response | ||||
							
								
								
									
										306
									
								
								src/saml2/s_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								src/saml2/s_utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,306 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import time | ||||
| import base64 | ||||
| import sys | ||||
| import hmac | ||||
|  | ||||
| # from python 2.5 | ||||
| if sys.version_info >= (2,5): | ||||
|     import hashlib | ||||
| else: # before python 2.5 | ||||
|     import sha | ||||
|  | ||||
| from saml2 import saml | ||||
| from saml2 import samlp | ||||
| from saml2 import VERSION | ||||
| from saml2.time_util import instant | ||||
|  | ||||
| try: | ||||
|     from hashlib import md5 | ||||
| except ImportError: | ||||
|     from md5 import md5 | ||||
| import zlib | ||||
|  | ||||
| class VersionMismatch(Exception): | ||||
|     pass | ||||
|      | ||||
| class UnknownPrincipal(Exception): | ||||
|     pass | ||||
|      | ||||
| class UnsupportedBinding(Exception): | ||||
|     pass | ||||
|  | ||||
| class OtherError(Exception): | ||||
|     pass | ||||
|  | ||||
| class MissingValue(Exception): | ||||
|     pass | ||||
|  | ||||
|      | ||||
| EXCEPTION2STATUS = { | ||||
|     VersionMismatch: samlp.STATUS_VERSION_MISMATCH, | ||||
|     UnknownPrincipal: samlp.STATUS_UNKNOWN_PRINCIPAL, | ||||
|     UnsupportedBinding: samlp.STATUS_UNSUPPORTED_BINDING, | ||||
|     OtherError: samlp.STATUS_UNKNOWN_PRINCIPAL, | ||||
|     MissingValue: samlp.STATUS_REQUEST_UNSUPPORTED, | ||||
|     # Undefined | ||||
|     Exception: samlp.STATUS_AUTHN_FAILED, | ||||
| } | ||||
|  | ||||
| GENERIC_DOMAINS = "aero", "asia", "biz", "cat", "com", "coop", \ | ||||
|         "edu", "gov", "info", "int", "jobs", "mil", "mobi", "museum", \ | ||||
|         "name", "net", "org", "pro", "tel", "travel" | ||||
|  | ||||
| def valid_email(emailaddress, domains = GENERIC_DOMAINS): | ||||
|     """Checks for a syntactically valid email address.""" | ||||
|  | ||||
|     # Email address must be at least 6 characters in total. | ||||
|     # Assuming noone may have addresses of the type a@com | ||||
|     if len(emailaddress) < 6: | ||||
|         return False # Address too short. | ||||
|  | ||||
|     # Split up email address into parts. | ||||
|     try: | ||||
|         localpart, domainname = emailaddress.rsplit('@', 1) | ||||
|         host, toplevel = domainname.rsplit('.', 1) | ||||
|     except ValueError: | ||||
|         return False # Address does not have enough parts. | ||||
|  | ||||
|     # Check for Country code or Generic Domain. | ||||
|     if len(toplevel) != 2 and toplevel not in domains: | ||||
|         return False # Not a domain name. | ||||
|  | ||||
|     for i in '-_.%+.': | ||||
|         localpart = localpart.replace(i, "") | ||||
|     for i in '-_.': | ||||
|         host = host.replace(i, "") | ||||
|  | ||||
|     if localpart.isalnum() and host.isalnum(): | ||||
|         return True # Email address is fine. | ||||
|     else: | ||||
|         return False # Email address has funny characters. | ||||
|              | ||||
| def decode_base64_and_inflate( string ): | ||||
|     """ base64 decodes and then inflates according to RFC1951  | ||||
|      | ||||
|     :param string: a deflated and encoded string | ||||
|     :return: the string after decoding and inflating | ||||
|     """ | ||||
|  | ||||
|     return zlib.decompress( base64.b64decode( string ) , -15) | ||||
|  | ||||
| def deflate_and_base64_encode( string_val ): | ||||
|     """ | ||||
|     Deflates and the base64 encodes a string | ||||
|      | ||||
|     :param string_val: The string to deflate and encode | ||||
|     :return: The deflated and encoded string | ||||
|     """ | ||||
|     return base64.b64encode( zlib.compress( string_val )[2:-4] ) | ||||
|      | ||||
| def sid(seed=""): | ||||
|     """The hash of the server time + seed makes an unique SID for each session. | ||||
|      | ||||
|     :param seed: A seed string | ||||
|     :return: The hex version of the digest, prefixed by 'id-' to make it  | ||||
|         compliant with the NCName specification | ||||
|     """ | ||||
|     ident = md5() | ||||
|     ident.update(repr(time.time())) | ||||
|     if seed: | ||||
|         ident.update(seed) | ||||
|     return "id-"+ident.hexdigest() | ||||
|  | ||||
| def parse_attribute_map(filenames): | ||||
|     """ | ||||
|     Expects a file with each line being composed of the oid for the attribute | ||||
|     exactly one space, a user friendly name of the attribute and then | ||||
|     the type specification of the name. | ||||
|      | ||||
|     :param filenames: List of filenames on mapfiles. | ||||
|     :return: A 2-tuple, one dictionary with the oid as keys and the friendly  | ||||
|         names as values, the other one the other way around. | ||||
|     """ | ||||
|     forward = {} | ||||
|     backward = {} | ||||
|     for filename in filenames: | ||||
|         for line in open(filename).readlines(): | ||||
|             (name, friendly_name, name_format) = line.strip().split() | ||||
|             forward[(name, name_format)] = friendly_name | ||||
|             backward[friendly_name] = (name, name_format) | ||||
|          | ||||
|     return forward, backward | ||||
|      | ||||
| def identity_attribute(form, attribute, forward_map=None): | ||||
|     if form == "friendly": | ||||
|         if attribute.friendly_name: | ||||
|             return attribute.friendly_name | ||||
|         elif forward_map: | ||||
|             try: | ||||
|                 return forward_map[(attribute.name, attribute.name_format)] | ||||
|             except KeyError: | ||||
|                 return attribute.name | ||||
|     # default is name | ||||
|     return attribute.name         | ||||
|  | ||||
| #---------------------------------------------------------------------------- | ||||
|      | ||||
| def error_status_factory(info): | ||||
|     if isinstance(info, Exception): | ||||
|         try: | ||||
|             exc_val = EXCEPTION2STATUS[info.__class__] | ||||
|         except KeyError: | ||||
|             exc_val = samlp.STATUS_AUTHN_FAILED | ||||
|         msg = info.args[0] | ||||
|         status = samlp.Status( | ||||
|             status_message=samlp.StatusMessage(text=msg), | ||||
|             status_code=samlp.StatusCode( | ||||
|                 value=samlp.STATUS_RESPONDER, | ||||
|                 status_code=samlp.StatusCode( | ||||
|                     value=exc_val) | ||||
|                 ), | ||||
|         ) | ||||
|     else: | ||||
|         (errcode, text) = info | ||||
|         status = samlp.Status( | ||||
|             status_message=samlp.StatusMessage(text=text), | ||||
|             status_code=samlp.StatusCode( | ||||
|                 value=samlp.STATUS_RESPONDER, | ||||
|                 status_code=samlp.StatusCode(value=errcode) | ||||
|                 ), | ||||
|         ) | ||||
|          | ||||
|     return status | ||||
|          | ||||
| def success_status_factory(): | ||||
|     return samlp.Status(status_code=samlp.StatusCode( | ||||
|                                             value=samlp.STATUS_SUCCESS)) | ||||
|                                  | ||||
| def status_message_factory(message, code, fro=samlp.STATUS_RESPONDER): | ||||
|     return samlp.Status( | ||||
|         status_message=samlp.StatusMessage(text=message), | ||||
|         status_code=samlp.StatusCode( | ||||
|                         value=fro, | ||||
|                         status_code=samlp.StatusCode(value=code))) | ||||
|      | ||||
| def assertion_factory(**kwargs): | ||||
|     assertion = saml.Assertion(version=VERSION, id=sid(), | ||||
|                                 issue_instant=instant()) | ||||
|     for key, val in kwargs.items(): | ||||
|         setattr(assertion, key, val) | ||||
|     return assertion | ||||
|  | ||||
| def _attrval(val, typ=""): | ||||
|     if isinstance(val, list) or isinstance(val, set): | ||||
|         attrval = [saml.AttributeValue(text=v) for v in val] | ||||
|     elif val is None: | ||||
|         attrval = None | ||||
|     else: | ||||
|         attrval = [saml.AttributeValue(text=val)] | ||||
|  | ||||
|     if typ: | ||||
|         for ava in attrval: | ||||
|             ava.set_type(typ) | ||||
|              | ||||
|     return attrval | ||||
|  | ||||
| # --- attribute profiles ----- | ||||
|  | ||||
| # xmlns:xs="http://www.w3.org/2001/XMLSchema" | ||||
| # xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|  | ||||
| def do_ava(val, typ=""): | ||||
|     if isinstance(val, basestring): | ||||
|         ava = saml.AttributeValue() | ||||
|         ava.set_text(val) | ||||
|         attrval = [ava] | ||||
|     elif isinstance(val, list): | ||||
|         attrval = [do_ava(v)[0] for v in val] | ||||
|     elif val or val == False: | ||||
|         ava = saml.AttributeValue() | ||||
|         ava.set_text(val)         | ||||
|         attrval = [ava] | ||||
|     elif val is None: | ||||
|         attrval = None | ||||
|     else: | ||||
|         raise OtherError("strange value type on: %s" % val) | ||||
|  | ||||
|     if typ: | ||||
|         for ava in attrval: | ||||
|             ava.set_type(typ) | ||||
|  | ||||
|     return attrval | ||||
|      | ||||
| def do_attribute(val, typ, key): | ||||
|     attr = saml.Attribute() | ||||
|     attrval = do_ava(val, typ) | ||||
|     if attrval: | ||||
|         attr.attribute_value = attrval | ||||
|  | ||||
|     if isinstance(key, basestring): | ||||
|         attr.name = key | ||||
|     elif isinstance(key, tuple): # 3-tuple or 2-tuple | ||||
|         try: | ||||
|             (name, nformat, friendly) = key | ||||
|         except ValueError: | ||||
|             (name, nformat) = key | ||||
|             friendly = "" | ||||
|         if name: | ||||
|             attr.name = name | ||||
|         if format: | ||||
|             attr.name_format = nformat | ||||
|         if friendly: | ||||
|             attr.friendly_name = friendly | ||||
|     return attr | ||||
|      | ||||
| def do_attributes(identity): | ||||
|     attrs = [] | ||||
|     if not identity: | ||||
|         return attrs | ||||
|     for key, spec in identity.items(): | ||||
|         try: | ||||
|             val, typ = spec | ||||
|         except ValueError: | ||||
|             val = spec | ||||
|             typ = "" | ||||
|         except TypeError:  | ||||
|             val = "" | ||||
|             typ = "" | ||||
|              | ||||
|         attr = do_attribute(val, typ, key) | ||||
|         attrs.append(attr) | ||||
|     return attrs | ||||
|      | ||||
| def do_attribute_statement(identity): | ||||
|     """ | ||||
|     :param identity: A dictionary with fiendly names as keys | ||||
|     :return: | ||||
|     """ | ||||
|     return saml.AttributeStatement(attribute=do_attributes(identity)) | ||||
|  | ||||
| def factory(klass, **kwargs): | ||||
|     instance = klass() | ||||
|     for key, val in kwargs.items(): | ||||
|         setattr(instance, key, val) | ||||
|     return instance | ||||
|  | ||||
| def signature(secret, parts): | ||||
|     """Generates a signature. | ||||
|     """ | ||||
|     if sys.version_info >= (2, 5): | ||||
|         csum = hmac.new(secret, digestmod=hashlib.sha1) | ||||
|     else: | ||||
|         csum = hmac.new(secret, digestmod=sha) | ||||
|  | ||||
|     for part in parts: | ||||
|         csum.update(part) | ||||
|  | ||||
|     return csum.hexdigest() | ||||
|  | ||||
| def verify_signature(secret, parts): | ||||
|     """ Checks that the signature is correct """ | ||||
|     if signature(secret, parts[:-1]) == parts[-1]: | ||||
|         return True | ||||
|     else: | ||||
|         return False | ||||
							
								
								
									
										1585
									
								
								src/saml2/saml.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1585
									
								
								src/saml2/saml.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1726
									
								
								src/saml2/samlp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1726
									
								
								src/saml2/samlp.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								src/saml2/schema/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/saml2/schema/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| __author__ = 'rolandh' | ||||
|    | ||||
							
								
								
									
										511
									
								
								src/saml2/schema/soap.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										511
									
								
								src/saml2/schema/soap.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,511 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Fri May 27 17:23:42 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| from saml2.schema import wsdl | ||||
|  | ||||
| NAMESPACE = 'http://schemas.xmlsoap.org/wsdl/soap/' | ||||
|  | ||||
| class EncodingStyle_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:encodingStyle element """ | ||||
|  | ||||
|     c_tag = 'encodingStyle' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def encoding_style__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(EncodingStyle_, xml_string) | ||||
|  | ||||
|  | ||||
| class TStyleChoice_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tStyleChoice element """ | ||||
|  | ||||
|     c_tag = 'tStyleChoice' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'xs:string', 'enumeration': ['rpc', 'document']} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def t_style_choice__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TStyleChoice_, xml_string) | ||||
|  | ||||
|  | ||||
| class TOperation_(wsdl.TExtensibilityElement_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tOperation element """ | ||||
|  | ||||
|     c_tag = 'tOperation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = wsdl.TExtensibilityElement_.c_children.copy() | ||||
|     c_attributes = wsdl.TExtensibilityElement_.c_attributes.copy() | ||||
|     c_child_order = wsdl.TExtensibilityElement_.c_child_order[:] | ||||
|     c_cardinality = wsdl.TExtensibilityElement_.c_cardinality.copy() | ||||
|     c_attributes['soapAction'] = ('soap_action', 'anyURI', False) | ||||
|     c_attributes['style'] = ('style', TStyleChoice_, False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             soap_action=None, | ||||
|             style=None, | ||||
|             required=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         wsdl.TExtensibilityElement_.__init__(self,  | ||||
|                 required=required, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.soap_action=soap_action | ||||
|         self.style=style | ||||
|  | ||||
| def t_operation__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TOperation_, xml_string) | ||||
|  | ||||
|  | ||||
| class UseChoice_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:useChoice element """ | ||||
|  | ||||
|     c_tag = 'useChoice' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'xs:string', 'enumeration': ['literal', 'encoded']} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def use_choice__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(UseChoice_, xml_string) | ||||
|  | ||||
|  | ||||
| class TFaultRes_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tFaultRes element """ | ||||
|  | ||||
|     c_tag = 'tFaultRes' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['{http://schemas.xmlsoap.org/wsdl/}required'] = ('required', 'None', False) | ||||
|     c_attributes['parts'] = ('parts', 'NMTOKENS', False) | ||||
|     c_attributes['encodingStyle'] = ('encoding_style', EncodingStyle_, False) | ||||
|     c_attributes['use'] = ('use', UseChoice_, False) | ||||
|     c_attributes['namespace'] = ('namespace', 'anyURI', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             required=None, | ||||
|             parts=None, | ||||
|             encoding_style=None, | ||||
|             use=None, | ||||
|             namespace=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.required=required | ||||
|         self.parts=parts | ||||
|         self.encoding_style=encoding_style | ||||
|         self.use=use | ||||
|         self.namespace=namespace | ||||
|  | ||||
|  | ||||
| class TFault_(TFaultRes_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tFault element """ | ||||
|  | ||||
|     c_tag = 'tFault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TFaultRes_.c_children.copy() | ||||
|     c_attributes = TFaultRes_.c_attributes.copy() | ||||
|     c_child_order = TFaultRes_.c_child_order[:] | ||||
|     c_cardinality = TFaultRes_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             required=None, | ||||
|             parts=None, | ||||
|             encoding_style=None, | ||||
|             use=None, | ||||
|             namespace=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TFaultRes_.__init__(self,  | ||||
|                 required=required, | ||||
|                 parts=parts, | ||||
|                 encoding_style=encoding_style, | ||||
|                 use=use, | ||||
|                 namespace=namespace, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|  | ||||
| def t_fault__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TFault_, xml_string) | ||||
|  | ||||
|  | ||||
| class THeaderFault_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tHeaderFault element """ | ||||
|  | ||||
|     c_tag = 'tHeaderFault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['message'] = ('message', 'QName', True) | ||||
|     c_attributes['part'] = ('part', 'NMTOKEN', True) | ||||
|     c_attributes['use'] = ('use', UseChoice_, True) | ||||
|     c_attributes['encodingStyle'] = ('encoding_style', EncodingStyle_, False) | ||||
|     c_attributes['namespace'] = ('namespace', 'anyURI', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             message=None, | ||||
|             part=None, | ||||
|             use=None, | ||||
|             encoding_style=None, | ||||
|             namespace=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.message=message | ||||
|         self.part=part | ||||
|         self.use=use | ||||
|         self.encoding_style=encoding_style | ||||
|         self.namespace=namespace | ||||
|  | ||||
| def t_header_fault__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(THeaderFault_, xml_string) | ||||
|  | ||||
|  | ||||
| class TAddress_(wsdl.TExtensibilityElement_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tAddress element """ | ||||
|  | ||||
|     c_tag = 'tAddress' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = wsdl.TExtensibilityElement_.c_children.copy() | ||||
|     c_attributes = wsdl.TExtensibilityElement_.c_attributes.copy() | ||||
|     c_child_order = wsdl.TExtensibilityElement_.c_child_order[:] | ||||
|     c_cardinality = wsdl.TExtensibilityElement_.c_cardinality.copy() | ||||
|     c_attributes['location'] = ('location', 'anyURI', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             location=None, | ||||
|             required=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         wsdl.TExtensibilityElement_.__init__(self,  | ||||
|                 required=required, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.location=location | ||||
|  | ||||
| def t_address__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TAddress_, xml_string) | ||||
|  | ||||
|  | ||||
| class TBinding_(wsdl.TExtensibilityElement_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tBinding element """ | ||||
|  | ||||
|     c_tag = 'tBinding' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = wsdl.TExtensibilityElement_.c_children.copy() | ||||
|     c_attributes = wsdl.TExtensibilityElement_.c_attributes.copy() | ||||
|     c_child_order = wsdl.TExtensibilityElement_.c_child_order[:] | ||||
|     c_cardinality = wsdl.TExtensibilityElement_.c_cardinality.copy() | ||||
|     c_attributes['transport'] = ('transport', 'anyURI', True) | ||||
|     c_attributes['style'] = ('style', TStyleChoice_, False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             transport=None, | ||||
|             style=None, | ||||
|             required=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         wsdl.TExtensibilityElement_.__init__(self,  | ||||
|                 required=required, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.transport=transport | ||||
|         self.style=style | ||||
|  | ||||
| def t_binding__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBinding_, xml_string) | ||||
|  | ||||
|  | ||||
| class Operation(TOperation_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:operation element """ | ||||
|  | ||||
|     c_tag = 'operation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TOperation_.c_children.copy() | ||||
|     c_attributes = TOperation_.c_attributes.copy() | ||||
|     c_child_order = TOperation_.c_child_order[:] | ||||
|     c_cardinality = TOperation_.c_cardinality.copy() | ||||
|  | ||||
| def operation_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Operation, xml_string) | ||||
|  | ||||
|  | ||||
| class TBody_(wsdl.TExtensibilityElement_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tBody element """ | ||||
|  | ||||
|     c_tag = 'tBody' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = wsdl.TExtensibilityElement_.c_children.copy() | ||||
|     c_attributes = wsdl.TExtensibilityElement_.c_attributes.copy() | ||||
|     c_child_order = wsdl.TExtensibilityElement_.c_child_order[:] | ||||
|     c_cardinality = wsdl.TExtensibilityElement_.c_cardinality.copy() | ||||
|     c_attributes['parts'] = ('parts', 'NMTOKENS', False) | ||||
|     c_attributes['encodingStyle'] = ('encoding_style', EncodingStyle_, False) | ||||
|     c_attributes['use'] = ('use', UseChoice_, False) | ||||
|     c_attributes['namespace'] = ('namespace', 'anyURI', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             parts=None, | ||||
|             encoding_style=None, | ||||
|             use=None, | ||||
|             namespace=None, | ||||
|             required=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         wsdl.TExtensibilityElement_.__init__(self,  | ||||
|                 required=required, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.parts=parts | ||||
|         self.encoding_style=encoding_style | ||||
|         self.use=use | ||||
|         self.namespace=namespace | ||||
|  | ||||
| def t_body__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBody_, xml_string) | ||||
|  | ||||
|  | ||||
| class Fault(TFault_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:fault element """ | ||||
|  | ||||
|     c_tag = 'fault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TFault_.c_children.copy() | ||||
|     c_attributes = TFault_.c_attributes.copy() | ||||
|     c_child_order = TFault_.c_child_order[:] | ||||
|     c_cardinality = TFault_.c_cardinality.copy() | ||||
|  | ||||
| def fault_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Fault, xml_string) | ||||
|  | ||||
|  | ||||
| class Headerfault(THeaderFault_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:headerfault element """ | ||||
|  | ||||
|     c_tag = 'headerfault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = THeaderFault_.c_children.copy() | ||||
|     c_attributes = THeaderFault_.c_attributes.copy() | ||||
|     c_child_order = THeaderFault_.c_child_order[:] | ||||
|     c_cardinality = THeaderFault_.c_cardinality.copy() | ||||
|  | ||||
| def headerfault_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Headerfault, xml_string) | ||||
|  | ||||
|  | ||||
| class Address(TAddress_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:address element """ | ||||
|  | ||||
|     c_tag = 'address' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TAddress_.c_children.copy() | ||||
|     c_attributes = TAddress_.c_attributes.copy() | ||||
|     c_child_order = TAddress_.c_child_order[:] | ||||
|     c_cardinality = TAddress_.c_cardinality.copy() | ||||
|  | ||||
| def address_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Address, xml_string) | ||||
|  | ||||
|  | ||||
| class Binding(TBinding_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:binding element """ | ||||
|  | ||||
|     c_tag = 'binding' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TBinding_.c_children.copy() | ||||
|     c_attributes = TBinding_.c_attributes.copy() | ||||
|     c_child_order = TBinding_.c_child_order[:] | ||||
|     c_cardinality = TBinding_.c_cardinality.copy() | ||||
|  | ||||
| def binding_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Binding, xml_string) | ||||
|  | ||||
|  | ||||
| class Body(TBody_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:body element """ | ||||
|  | ||||
|     c_tag = 'body' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TBody_.c_children.copy() | ||||
|     c_attributes = TBody_.c_attributes.copy() | ||||
|     c_child_order = TBody_.c_child_order[:] | ||||
|     c_cardinality = TBody_.c_cardinality.copy() | ||||
|  | ||||
| def body_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Body, xml_string) | ||||
|  | ||||
|  | ||||
| class THeader_(wsdl.TExtensibilityElement_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:tHeader element """ | ||||
|  | ||||
|     c_tag = 'tHeader' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = wsdl.TExtensibilityElement_.c_children.copy() | ||||
|     c_attributes = wsdl.TExtensibilityElement_.c_attributes.copy() | ||||
|     c_child_order = wsdl.TExtensibilityElement_.c_child_order[:] | ||||
|     c_cardinality = wsdl.TExtensibilityElement_.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/soap/}headerfault'] = ('headerfault', [Headerfault]) | ||||
|     c_cardinality['headerfault'] = {"min":0} | ||||
|     c_attributes['message'] = ('message', 'QName', True) | ||||
|     c_attributes['part'] = ('part', 'NMTOKEN', True) | ||||
|     c_attributes['use'] = ('use', UseChoice_, True) | ||||
|     c_attributes['encodingStyle'] = ('encoding_style', EncodingStyle_, False) | ||||
|     c_attributes['namespace'] = ('namespace', 'anyURI', False) | ||||
|     c_child_order.extend(['headerfault']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             headerfault=None, | ||||
|             message=None, | ||||
|             part=None, | ||||
|             use=None, | ||||
|             encoding_style=None, | ||||
|             namespace=None, | ||||
|             required=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         wsdl.TExtensibilityElement_.__init__(self,  | ||||
|                 required=required, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.headerfault=headerfault or [] | ||||
|         self.message=message | ||||
|         self.part=part | ||||
|         self.use=use | ||||
|         self.encoding_style=encoding_style | ||||
|         self.namespace=namespace | ||||
|  | ||||
| def t_header__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(THeader_, xml_string) | ||||
|  | ||||
|  | ||||
| class Header(THeader_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/soap/:header element """ | ||||
|  | ||||
|     c_tag = 'header' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = THeader_.c_children.copy() | ||||
|     c_attributes = THeader_.c_attributes.copy() | ||||
|     c_child_order = THeader_.c_child_order[:] | ||||
|     c_cardinality = THeader_.c_cardinality.copy() | ||||
|  | ||||
| def header_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Header, xml_string) | ||||
|  | ||||
|  | ||||
| AG_tBodyAttributes = [ | ||||
|     ('encodingStyle', EncodingStyle_, False), | ||||
|     ('use', UseChoice_, False), | ||||
|     ('namespace', 'anyURI', False), | ||||
| ] | ||||
|  | ||||
| AG_tHeaderAttributes = [ | ||||
|     ('message', 'QName', True), | ||||
|     ('part', 'NMTOKEN', True), | ||||
|     ('use', UseChoice_, True), | ||||
|     ('encodingStyle', EncodingStyle_, False), | ||||
|     ('namespace', 'anyURI', False), | ||||
| ] | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     EncodingStyle_.c_tag: encoding_style__from_string, | ||||
|     Binding.c_tag: binding_from_string, | ||||
|     TBinding_.c_tag: t_binding__from_string, | ||||
|     TStyleChoice_.c_tag: t_style_choice__from_string, | ||||
|     Operation.c_tag: operation_from_string, | ||||
|     TOperation_.c_tag: t_operation__from_string, | ||||
|     Body.c_tag: body_from_string, | ||||
|     TBody_.c_tag: t_body__from_string, | ||||
|     UseChoice_.c_tag: use_choice__from_string, | ||||
|     Fault.c_tag: fault_from_string, | ||||
|     TFault_.c_tag: t_fault__from_string, | ||||
|     Header.c_tag: header_from_string, | ||||
|     THeader_.c_tag: t_header__from_string, | ||||
|     Headerfault.c_tag: headerfault_from_string, | ||||
|     THeaderFault_.c_tag: t_header_fault__from_string, | ||||
|     Address.c_tag: address_from_string, | ||||
|     TAddress_.c_tag: t_address__from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'encodingStyle': EncodingStyle_, | ||||
|     'binding': Binding, | ||||
|     'tBinding': TBinding_, | ||||
|     'tStyleChoice': TStyleChoice_, | ||||
|     'operation': Operation, | ||||
|     'tOperation': TOperation_, | ||||
|     'body': Body, | ||||
|     'tBody': TBody_, | ||||
|     'useChoice': UseChoice_, | ||||
|     'fault': Fault, | ||||
|     'tFault': TFault_, | ||||
|     'header': Header, | ||||
|     'tHeader': THeader_, | ||||
|     'headerfault': Headerfault, | ||||
|     'tHeaderFault': THeaderFault_, | ||||
|     'address': Address, | ||||
|     'tAddress': TAddress_, | ||||
|     'tFaultRes': TFaultRes_, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										293
									
								
								src/saml2/schema/soapenv.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								src/saml2/schema/soapenv.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,293 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Fri May 27 17:26:51 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
| NAMESPACE = 'http://schemas.xmlsoap.org/soap/envelope/' | ||||
|  | ||||
| class Header_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Header element """ | ||||
|  | ||||
|     c_tag = 'Header' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def header__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Header_, xml_string) | ||||
|  | ||||
|  | ||||
| class Body_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Body element """ | ||||
|  | ||||
|     c_tag = 'Body' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def body__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Body_, xml_string) | ||||
|  | ||||
|  | ||||
| class EncodingStyle_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:encodingStyle element """ | ||||
|  | ||||
|     c_tag = 'encodingStyle' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def encoding_style__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(EncodingStyle_, xml_string) | ||||
|  | ||||
|  | ||||
| class Fault_faultcode(SamlBase): | ||||
|  | ||||
|     c_tag = 'faultcode' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'QName'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def fault_faultcode_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Fault_faultcode, xml_string) | ||||
|  | ||||
|  | ||||
| class Fault_faultstring(SamlBase): | ||||
|  | ||||
|     c_tag = 'faultstring' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'string'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def fault_faultstring_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Fault_faultstring, xml_string) | ||||
|  | ||||
|  | ||||
| class Fault_faultactor(SamlBase): | ||||
|  | ||||
|     c_tag = 'faultactor' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_value_type = {'base': 'anyURI'} | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def fault_faultactor_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Fault_faultactor, xml_string) | ||||
|  | ||||
|  | ||||
| class Detail_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:detail element """ | ||||
|  | ||||
|     c_tag = 'detail' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def detail__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Detail_, xml_string) | ||||
|  | ||||
|  | ||||
| class Envelope_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Envelope element """ | ||||
|  | ||||
|     c_tag = 'Envelope' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/soap/envelope/}Header'] = ('header', Header_) | ||||
|     c_cardinality['header'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/soap/envelope/}Body'] = ('body', Body_) | ||||
|     c_child_order.extend(['header', 'body']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             header=None, | ||||
|             body=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.header=header | ||||
|         self.body=body | ||||
|  | ||||
| def envelope__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Envelope_, xml_string) | ||||
|  | ||||
|  | ||||
| class Header(Header_): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Header element """ | ||||
|  | ||||
|     c_tag = 'Header' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = Header_.c_children.copy() | ||||
|     c_attributes = Header_.c_attributes.copy() | ||||
|     c_child_order = Header_.c_child_order[:] | ||||
|     c_cardinality = Header_.c_cardinality.copy() | ||||
|  | ||||
| def header_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Header, xml_string) | ||||
|  | ||||
|  | ||||
| class Body(Body_): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Body element """ | ||||
|  | ||||
|     c_tag = 'Body' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = Body_.c_children.copy() | ||||
|     c_attributes = Body_.c_attributes.copy() | ||||
|     c_child_order = Body_.c_child_order[:] | ||||
|     c_cardinality = Body_.c_cardinality.copy() | ||||
|  | ||||
| def body_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Body, xml_string) | ||||
|  | ||||
|  | ||||
| class Fault_detail(Detail_): | ||||
|  | ||||
|     c_tag = 'detail' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = Detail_.c_children.copy() | ||||
|     c_attributes = Detail_.c_attributes.copy() | ||||
|     c_child_order = Detail_.c_child_order[:] | ||||
|     c_cardinality = Detail_.c_cardinality.copy() | ||||
|  | ||||
| def fault_detail_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Fault_detail, xml_string) | ||||
|  | ||||
|  | ||||
| class Fault_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Fault element """ | ||||
|  | ||||
|     c_tag = 'Fault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/soap/envelope/}faultcode'] = ('faultcode', Fault_faultcode) | ||||
|     c_children['{http://schemas.xmlsoap.org/soap/envelope/}faultstring'] = ('faultstring', Fault_faultstring) | ||||
|     c_children['{http://schemas.xmlsoap.org/soap/envelope/}faultactor'] = ('faultactor', Fault_faultactor) | ||||
|     c_cardinality['faultactor'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/soap/envelope/}detail'] = ('detail', Fault_detail) | ||||
|     c_cardinality['detail'] = {"min":0, "max":1} | ||||
|     c_child_order.extend(['faultcode', 'faultstring', 'faultactor', 'detail']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             faultcode=None, | ||||
|             faultstring=None, | ||||
|             faultactor=None, | ||||
|             detail=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.faultcode=faultcode | ||||
|         self.faultstring=faultstring | ||||
|         self.faultactor=faultactor | ||||
|         self.detail=detail | ||||
|  | ||||
| def fault__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Fault_, xml_string) | ||||
|  | ||||
|  | ||||
| class Envelope(Envelope_): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Envelope element """ | ||||
|  | ||||
|     c_tag = 'Envelope' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = Envelope_.c_children.copy() | ||||
|     c_attributes = Envelope_.c_attributes.copy() | ||||
|     c_child_order = Envelope_.c_child_order[:] | ||||
|     c_cardinality = Envelope_.c_cardinality.copy() | ||||
|  | ||||
| def envelope_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Envelope, xml_string) | ||||
|  | ||||
|  | ||||
| class Fault(Fault_): | ||||
|     """The http://schemas.xmlsoap.org/soap/envelope/:Fault element """ | ||||
|  | ||||
|     c_tag = 'Fault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = Fault_.c_children.copy() | ||||
|     c_attributes = Fault_.c_attributes.copy() | ||||
|     c_child_order = Fault_.c_child_order[:] | ||||
|     c_cardinality = Fault_.c_cardinality.copy() | ||||
|  | ||||
| def fault_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Fault, xml_string) | ||||
|  | ||||
|  | ||||
| #.................. | ||||
| # [] | ||||
| AG_encodingStyle = [ | ||||
|     ('encodingStyle', '', False), | ||||
| ] | ||||
|  | ||||
| ELEMENT_FROM_STRING = { | ||||
|     Envelope.c_tag: envelope_from_string, | ||||
|     Envelope_.c_tag: envelope__from_string, | ||||
|     Header.c_tag: header_from_string, | ||||
|     Header_.c_tag: header__from_string, | ||||
|     Body.c_tag: body_from_string, | ||||
|     Body_.c_tag: body__from_string, | ||||
|     EncodingStyle_.c_tag: encoding_style__from_string, | ||||
|     Fault.c_tag: fault_from_string, | ||||
|     Fault_.c_tag: fault__from_string, | ||||
|     Detail_.c_tag: detail__from_string, | ||||
|     Fault_faultcode.c_tag: fault_faultcode_from_string, | ||||
|     Fault_faultstring.c_tag: fault_faultstring_from_string, | ||||
|     Fault_faultactor.c_tag: fault_faultactor_from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'Envelope': Envelope, | ||||
|     'Envelope': Envelope_, | ||||
|     'Header': Header, | ||||
|     'Header': Header_, | ||||
|     'Body': Body, | ||||
|     'Body': Body_, | ||||
|     'encodingStyle': EncodingStyle_, | ||||
|     'Fault': Fault, | ||||
|     'Fault': Fault_, | ||||
|     'detail': Detail_, | ||||
|     'faultcode': Fault_faultcode, | ||||
|     'faultstring': Fault_faultstring, | ||||
|     'faultactor': Fault_faultactor, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										903
									
								
								src/saml2/schema/wsdl.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										903
									
								
								src/saml2/schema/wsdl.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,903 @@ | ||||
| #!!!! 'NoneType' object has no attribute 'py_class' | ||||
| #!!!! 'NoneType' object has no attribute 'py_class' | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # | ||||
| # Generated Fri May 27 17:23:24 2011 by parse_xsd.py version 0.4. | ||||
| # | ||||
|  | ||||
| import saml2 | ||||
| from saml2 import SamlBase | ||||
|  | ||||
|  | ||||
| NAMESPACE = 'http://schemas.xmlsoap.org/wsdl/' | ||||
|  | ||||
| class TDocumentation_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tDocumentation element """ | ||||
|  | ||||
|     c_tag = 'tDocumentation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|  | ||||
| def t_documentation__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TDocumentation_, xml_string) | ||||
|  | ||||
|  | ||||
| class TDocumented_documentation(TDocumentation_): | ||||
|  | ||||
|     c_tag = 'documentation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TDocumentation_.c_children.copy() | ||||
|     c_attributes = TDocumentation_.c_attributes.copy() | ||||
|     c_child_order = TDocumentation_.c_child_order[:] | ||||
|     c_cardinality = TDocumentation_.c_cardinality.copy() | ||||
|  | ||||
| def t_documented_documentation_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TDocumented_documentation, xml_string) | ||||
|  | ||||
|  | ||||
| class TDocumented_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tDocumented element """ | ||||
|  | ||||
|     c_tag = 'tDocumented' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}documentation'] = ('documentation', TDocumented_documentation) | ||||
|     c_cardinality['documentation'] = {"min":0, "max":1} | ||||
|     c_child_order.extend(['documentation']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.documentation=documentation | ||||
|  | ||||
| def t_documented__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TDocumented_, xml_string) | ||||
|  | ||||
|  | ||||
| class TExtensibleAttributesDocumented_(TDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tExtensibleAttributesDocumented element """ | ||||
|  | ||||
|     c_tag = 'tExtensibleAttributesDocumented' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TDocumented_.c_children.copy() | ||||
|     c_attributes = TDocumented_.c_attributes.copy() | ||||
|     c_child_order = TDocumented_.c_child_order[:] | ||||
|     c_cardinality = TDocumented_.c_cardinality.copy() | ||||
|  | ||||
|  | ||||
| class TExtensibleDocumented_(TDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tExtensibleDocumented element """ | ||||
|  | ||||
|     c_tag = 'tExtensibleDocumented' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TDocumented_.c_children.copy() | ||||
|     c_attributes = TDocumented_.c_attributes.copy() | ||||
|     c_child_order = TDocumented_.c_child_order[:] | ||||
|     c_cardinality = TDocumented_.c_cardinality.copy() | ||||
|  | ||||
|  | ||||
| class TImport_(TExtensibleAttributesDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tImport element """ | ||||
|  | ||||
|     c_tag = 'tImport' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleAttributesDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleAttributesDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleAttributesDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleAttributesDocumented_.c_cardinality.copy() | ||||
|     c_attributes['namespace'] = ('namespace', 'anyURI', True) | ||||
|     c_attributes['location'] = ('location', 'anyURI', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             namespace=None, | ||||
|             location=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleAttributesDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.namespace=namespace | ||||
|         self.location=location | ||||
|  | ||||
| def t_import__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TImport_, xml_string) | ||||
|  | ||||
|  | ||||
| class TTypes_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tTypes element """ | ||||
|  | ||||
|     c_tag = 'tTypes' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|  | ||||
| def t_types__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TTypes_, xml_string) | ||||
|  | ||||
|  | ||||
| class TPart_(TExtensibleAttributesDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tPart element """ | ||||
|  | ||||
|     c_tag = 'tPart' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleAttributesDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleAttributesDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleAttributesDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleAttributesDocumented_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_attributes['element'] = ('element', 'QName', False) | ||||
|     c_attributes['type'] = ('type', 'QName', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             element=None, | ||||
|             type=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleAttributesDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|         self.element=element | ||||
|         self.type=type | ||||
|  | ||||
| def t_part__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TPart_, xml_string) | ||||
|  | ||||
|  | ||||
| class TOperation_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tOperation element """ | ||||
|  | ||||
|     c_tag = 'tOperation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_attributes['parameterOrder'] = ('parameter_order', 'NMTOKENS', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             parameter_order=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|         self.parameter_order=parameter_order | ||||
|  | ||||
| def t_operation__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TOperation_, xml_string) | ||||
|  | ||||
|  | ||||
| class TParam_(TExtensibleAttributesDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tParam element """ | ||||
|  | ||||
|     c_tag = 'tParam' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleAttributesDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleAttributesDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleAttributesDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleAttributesDocumented_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', False) | ||||
|     c_attributes['message'] = ('message', 'QName', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             message=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleAttributesDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|         self.message=message | ||||
|  | ||||
| def t_param__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TParam_, xml_string) | ||||
|  | ||||
|  | ||||
| class TFault_(TExtensibleAttributesDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tFault element """ | ||||
|  | ||||
|     c_tag = 'tFault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleAttributesDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleAttributesDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleAttributesDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleAttributesDocumented_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_attributes['message'] = ('message', 'QName', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             message=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleAttributesDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|         self.message=message | ||||
|  | ||||
| def t_fault__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TFault_, xml_string) | ||||
|  | ||||
|  | ||||
| class TBindingOperationMessage_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tBindingOperationMessage element """ | ||||
|  | ||||
|     c_tag = 'tBindingOperationMessage' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|  | ||||
| def t_binding_operation_message__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBindingOperationMessage_, xml_string) | ||||
|  | ||||
|  | ||||
| class TBindingOperationFault_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tBindingOperationFault element """ | ||||
|  | ||||
|     c_tag = 'tBindingOperationFault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|  | ||||
| def t_binding_operation_fault__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBindingOperationFault_, xml_string) | ||||
|  | ||||
|  | ||||
| class TBindingOperation_input(TBindingOperationMessage_): | ||||
|  | ||||
|     c_tag = 'input' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TBindingOperationMessage_.c_children.copy() | ||||
|     c_attributes = TBindingOperationMessage_.c_attributes.copy() | ||||
|     c_child_order = TBindingOperationMessage_.c_child_order[:] | ||||
|     c_cardinality = TBindingOperationMessage_.c_cardinality.copy() | ||||
|  | ||||
| def t_binding_operation_input_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBindingOperation_input, xml_string) | ||||
|  | ||||
|  | ||||
| class TBindingOperation_output(TBindingOperationMessage_): | ||||
|  | ||||
|     c_tag = 'output' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TBindingOperationMessage_.c_children.copy() | ||||
|     c_attributes = TBindingOperationMessage_.c_attributes.copy() | ||||
|     c_child_order = TBindingOperationMessage_.c_child_order[:] | ||||
|     c_cardinality = TBindingOperationMessage_.c_cardinality.copy() | ||||
|  | ||||
| def t_binding_operation_output_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBindingOperation_output, xml_string) | ||||
|  | ||||
|  | ||||
| class TBindingOperation_fault(TBindingOperationFault_): | ||||
|  | ||||
|     c_tag = 'fault' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TBindingOperationFault_.c_children.copy() | ||||
|     c_attributes = TBindingOperationFault_.c_attributes.copy() | ||||
|     c_child_order = TBindingOperationFault_.c_child_order[:] | ||||
|     c_cardinality = TBindingOperationFault_.c_cardinality.copy() | ||||
|  | ||||
| def t_binding_operation_fault_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBindingOperation_fault, xml_string) | ||||
|  | ||||
|  | ||||
| class TBindingOperation_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tBindingOperation element """ | ||||
|  | ||||
|     c_tag = 'tBindingOperation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}input'] = ('input', TBindingOperation_input) | ||||
|     c_cardinality['input'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}output'] = ('output', TBindingOperation_output) | ||||
|     c_cardinality['output'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}fault'] = ('fault', [TBindingOperation_fault]) | ||||
|     c_cardinality['fault'] = {"min":0} | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_child_order.extend(['input', 'output', 'fault']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             input=None, | ||||
|             output=None, | ||||
|             fault=None, | ||||
|             name=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.input=input | ||||
|         self.output=output | ||||
|         self.fault=fault or [] | ||||
|         self.name=name | ||||
|  | ||||
| def t_binding_operation__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBindingOperation_, xml_string) | ||||
|  | ||||
|  | ||||
| class TPort_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tPort element """ | ||||
|  | ||||
|     c_tag = 'tPort' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_attributes['binding'] = ('binding', 'QName', True) | ||||
|  | ||||
|     def __init__(self, | ||||
|             name=None, | ||||
|             binding=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.name=name | ||||
|         self.binding=binding | ||||
|  | ||||
| def t_port__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TPort_, xml_string) | ||||
|  | ||||
|  | ||||
| class TExtensibilityElement_(SamlBase): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tExtensibilityElement element """ | ||||
|  | ||||
|     c_tag = 'tExtensibilityElement' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = SamlBase.c_children.copy() | ||||
|     c_attributes = SamlBase.c_attributes.copy() | ||||
|     c_child_order = SamlBase.c_child_order[:] | ||||
|     c_cardinality = SamlBase.c_cardinality.copy() | ||||
|     c_attributes['required'] = ('required', 'None', False) | ||||
|  | ||||
|     def __init__(self, | ||||
|             required=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         SamlBase.__init__(self,  | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.required=required | ||||
|  | ||||
|  | ||||
| class Import(TImport_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:import element """ | ||||
|  | ||||
|     c_tag = 'import' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TImport_.c_children.copy() | ||||
|     c_attributes = TImport_.c_attributes.copy() | ||||
|     c_child_order = TImport_.c_child_order[:] | ||||
|     c_cardinality = TImport_.c_cardinality.copy() | ||||
|  | ||||
| def import_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Import, xml_string) | ||||
|  | ||||
|  | ||||
| class Types(TTypes_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:types element """ | ||||
|  | ||||
|     c_tag = 'types' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TTypes_.c_children.copy() | ||||
|     c_attributes = TTypes_.c_attributes.copy() | ||||
|     c_child_order = TTypes_.c_child_order[:] | ||||
|     c_cardinality = TTypes_.c_cardinality.copy() | ||||
|  | ||||
| def types_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Types, xml_string) | ||||
|  | ||||
|  | ||||
| class TMessage_part(TPart_): | ||||
|  | ||||
|     c_tag = 'part' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TPart_.c_children.copy() | ||||
|     c_attributes = TPart_.c_attributes.copy() | ||||
|     c_child_order = TPart_.c_child_order[:] | ||||
|     c_cardinality = TPart_.c_cardinality.copy() | ||||
|  | ||||
| def t_message_part_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TMessage_part, xml_string) | ||||
|  | ||||
|  | ||||
| class TMessage_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tMessage element """ | ||||
|  | ||||
|     c_tag = 'tMessage' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}part'] = ('part', [TMessage_part]) | ||||
|     c_cardinality['part'] = {"min":0} | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_child_order.extend(['part']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             part=None, | ||||
|             name=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.part=part or [] | ||||
|         self.name=name | ||||
|  | ||||
| def t_message__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TMessage_, xml_string) | ||||
|  | ||||
|  | ||||
| class TPortType_operation(TOperation_): | ||||
|  | ||||
|     c_tag = 'operation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TOperation_.c_children.copy() | ||||
|     c_attributes = TOperation_.c_attributes.copy() | ||||
|     c_child_order = TOperation_.c_child_order[:] | ||||
|     c_cardinality = TOperation_.c_cardinality.copy() | ||||
|  | ||||
| def t_port_type_operation_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TPortType_operation, xml_string) | ||||
|  | ||||
|  | ||||
| class TPortType_(TExtensibleAttributesDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tPortType element """ | ||||
|  | ||||
|     c_tag = 'tPortType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleAttributesDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleAttributesDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleAttributesDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleAttributesDocumented_.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}operation'] = ('operation', [TPortType_operation]) | ||||
|     c_cardinality['operation'] = {"min":0} | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_child_order.extend(['operation']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             operation=None, | ||||
|             name=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleAttributesDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.operation=operation or [] | ||||
|         self.name=name | ||||
|  | ||||
| def t_port_type__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TPortType_, xml_string) | ||||
|  | ||||
|  | ||||
| class TBinding_operation(TBindingOperation_): | ||||
|  | ||||
|     c_tag = 'operation' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TBindingOperation_.c_children.copy() | ||||
|     c_attributes = TBindingOperation_.c_attributes.copy() | ||||
|     c_child_order = TBindingOperation_.c_child_order[:] | ||||
|     c_cardinality = TBindingOperation_.c_cardinality.copy() | ||||
|  | ||||
| def t_binding_operation_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBinding_operation, xml_string) | ||||
|  | ||||
|  | ||||
| class TBinding_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tBinding element """ | ||||
|  | ||||
|     c_tag = 'tBinding' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}operation'] = ('operation', [TBinding_operation]) | ||||
|     c_cardinality['operation'] = {"min":0} | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_attributes['type'] = ('type', 'QName', True) | ||||
|     c_child_order.extend(['operation']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             operation=None, | ||||
|             name=None, | ||||
|             type=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.operation=operation or [] | ||||
|         self.name=name | ||||
|         self.type=type | ||||
|  | ||||
| def t_binding__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TBinding_, xml_string) | ||||
|  | ||||
|  | ||||
| class TService_port(TPort_): | ||||
|  | ||||
|     c_tag = 'port' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TPort_.c_children.copy() | ||||
|     c_attributes = TPort_.c_attributes.copy() | ||||
|     c_child_order = TPort_.c_child_order[:] | ||||
|     c_cardinality = TPort_.c_cardinality.copy() | ||||
|  | ||||
| def t_service_port_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TService_port, xml_string) | ||||
|  | ||||
|  | ||||
| class TService_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tService element """ | ||||
|  | ||||
|     c_tag = 'tService' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}port'] = ('port', [TService_port]) | ||||
|     c_cardinality['port'] = {"min":0} | ||||
|     c_attributes['name'] = ('name', 'NCName', True) | ||||
|     c_child_order.extend(['port']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             port=None, | ||||
|             name=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.port=port or [] | ||||
|         self.name=name | ||||
|  | ||||
| def t_service__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TService_, xml_string) | ||||
|  | ||||
|  | ||||
| class Message(TMessage_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:message element """ | ||||
|  | ||||
|     c_tag = 'message' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TMessage_.c_children.copy() | ||||
|     c_attributes = TMessage_.c_attributes.copy() | ||||
|     c_child_order = TMessage_.c_child_order[:] | ||||
|     c_cardinality = TMessage_.c_cardinality.copy() | ||||
|  | ||||
| def message_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Message, xml_string) | ||||
|  | ||||
|  | ||||
| class PortType(TPortType_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:portType element """ | ||||
|  | ||||
|     c_tag = 'portType' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TPortType_.c_children.copy() | ||||
|     c_attributes = TPortType_.c_attributes.copy() | ||||
|     c_child_order = TPortType_.c_child_order[:] | ||||
|     c_cardinality = TPortType_.c_cardinality.copy() | ||||
|  | ||||
| def port_type_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(PortType, xml_string) | ||||
|  | ||||
|  | ||||
| class Binding(TBinding_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:binding element """ | ||||
|  | ||||
|     c_tag = 'binding' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TBinding_.c_children.copy() | ||||
|     c_attributes = TBinding_.c_attributes.copy() | ||||
|     c_child_order = TBinding_.c_child_order[:] | ||||
|     c_cardinality = TBinding_.c_cardinality.copy() | ||||
|  | ||||
| def binding_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Binding, xml_string) | ||||
|  | ||||
|  | ||||
| class Service(TService_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:service element """ | ||||
|  | ||||
|     c_tag = 'service' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TService_.c_children.copy() | ||||
|     c_attributes = TService_.c_attributes.copy() | ||||
|     c_child_order = TService_.c_child_order[:] | ||||
|     c_cardinality = TService_.c_cardinality.copy() | ||||
|  | ||||
| def service_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Service, xml_string) | ||||
|  | ||||
|  | ||||
| class TDefinitions_(TExtensibleDocumented_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:tDefinitions element """ | ||||
|  | ||||
|     c_tag = 'tDefinitions' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TExtensibleDocumented_.c_children.copy() | ||||
|     c_attributes = TExtensibleDocumented_.c_attributes.copy() | ||||
|     c_child_order = TExtensibleDocumented_.c_child_order[:] | ||||
|     c_cardinality = TExtensibleDocumented_.c_cardinality.copy() | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}import'] = ('import', Import) | ||||
|     c_cardinality['import'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}types'] = ('types', Types) | ||||
|     c_cardinality['types'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}message'] = ('message', Message) | ||||
|     c_cardinality['message'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}portType'] = ('port_type', PortType) | ||||
|     c_cardinality['port_type'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}binding'] = ('binding', Binding) | ||||
|     c_cardinality['binding'] = {"min":0, "max":1} | ||||
|     c_children['{http://schemas.xmlsoap.org/wsdl/}service'] = ('service', Service) | ||||
|     c_cardinality['service'] = {"min":0, "max":1} | ||||
|     c_attributes['targetNamespace'] = ('target_namespace', 'anyURI', False) | ||||
|     c_attributes['name'] = ('name', 'NCName', False) | ||||
|     c_child_order.extend(['import', 'types', 'message', 'port_type', 'binding', 'service']) | ||||
|  | ||||
|     def __init__(self, | ||||
|             import_=None, | ||||
|             types=None, | ||||
|             message=None, | ||||
|             port_type=None, | ||||
|             binding=None, | ||||
|             service=None, | ||||
|             target_namespace=None, | ||||
|             name=None, | ||||
|             documentation=None, | ||||
|             text=None, | ||||
|             extension_elements=None, | ||||
|             extension_attributes=None, | ||||
|         ): | ||||
|         TExtensibleDocumented_.__init__(self,  | ||||
|                 documentation=documentation, | ||||
|                 text=text, | ||||
|                 extension_elements=extension_elements, | ||||
|                 extension_attributes=extension_attributes, | ||||
|                 ) | ||||
|         self.import_=import_ | ||||
|         self.types=types | ||||
|         self.message=message | ||||
|         self.port_type=port_type | ||||
|         self.binding=binding | ||||
|         self.service=service | ||||
|         self.target_namespace=target_namespace | ||||
|         self.name=name | ||||
|  | ||||
| def t_definitions__from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(TDefinitions_, xml_string) | ||||
|  | ||||
|  | ||||
| class Definitions(TDefinitions_): | ||||
|     """The http://schemas.xmlsoap.org/wsdl/:definitions element """ | ||||
|  | ||||
|     c_tag = 'definitions' | ||||
|     c_namespace = NAMESPACE | ||||
|     c_children = TDefinitions_.c_children.copy() | ||||
|     c_attributes = TDefinitions_.c_attributes.copy() | ||||
|     c_child_order = TDefinitions_.c_child_order[:] | ||||
|     c_cardinality = TDefinitions_.c_cardinality.copy() | ||||
|  | ||||
| def definitions_from_string(xml_string): | ||||
|     return saml2.create_class_from_xml_string(Definitions, xml_string) | ||||
|  | ||||
|  | ||||
| #.................. | ||||
| # [] | ||||
| ELEMENT_FROM_STRING = { | ||||
|     TDocumentation_.c_tag: t_documentation__from_string, | ||||
|     TDocumented_.c_tag: t_documented__from_string, | ||||
|     Definitions.c_tag: definitions_from_string, | ||||
|     TDefinitions_.c_tag: t_definitions__from_string, | ||||
|     TImport_.c_tag: t_import__from_string, | ||||
|     TTypes_.c_tag: t_types__from_string, | ||||
|     TMessage_.c_tag: t_message__from_string, | ||||
|     TPart_.c_tag: t_part__from_string, | ||||
|     TPortType_.c_tag: t_port_type__from_string, | ||||
|     TOperation_.c_tag: t_operation__from_string, | ||||
|     TParam_.c_tag: t_param__from_string, | ||||
|     TFault_.c_tag: t_fault__from_string, | ||||
|     TBinding_.c_tag: t_binding__from_string, | ||||
|     TBindingOperationMessage_.c_tag: t_binding_operation_message__from_string, | ||||
|     TBindingOperationFault_.c_tag: t_binding_operation_fault__from_string, | ||||
|     TBindingOperation_.c_tag: t_binding_operation__from_string, | ||||
|     TService_.c_tag: t_service__from_string, | ||||
|     TPort_.c_tag: t_port__from_string, | ||||
|     TDocumented_documentation.c_tag: t_documented_documentation_from_string, | ||||
|     TBindingOperation_input.c_tag: t_binding_operation_input_from_string, | ||||
|     TBindingOperation_output.c_tag: t_binding_operation_output_from_string, | ||||
|     TBindingOperation_fault.c_tag: t_binding_operation_fault_from_string, | ||||
|     Import.c_tag: import_from_string, | ||||
|     Types.c_tag: types_from_string, | ||||
|     TMessage_part.c_tag: t_message_part_from_string, | ||||
|     TPortType_operation.c_tag: t_port_type_operation_from_string, | ||||
|     TService_port.c_tag: t_service_port_from_string, | ||||
|     Message.c_tag: message_from_string, | ||||
|     PortType.c_tag: port_type_from_string, | ||||
|     Binding.c_tag: binding_from_string, | ||||
|     Service.c_tag: service_from_string, | ||||
| } | ||||
|  | ||||
| ELEMENT_BY_TAG = { | ||||
|     'tDocumentation': TDocumentation_, | ||||
|     'tDocumented': TDocumented_, | ||||
|     'definitions': Definitions, | ||||
|     'tDefinitions': TDefinitions_, | ||||
|     'tImport': TImport_, | ||||
|     'tTypes': TTypes_, | ||||
|     'tMessage': TMessage_, | ||||
|     'tPart': TPart_, | ||||
|     'tPortType': TPortType_, | ||||
|     'tOperation': TOperation_, | ||||
|     'tParam': TParam_, | ||||
|     'tFault': TFault_, | ||||
|     'tBinding': TBinding_, | ||||
|     'tBindingOperationMessage': TBindingOperationMessage_, | ||||
|     'tBindingOperationFault': TBindingOperationFault_, | ||||
|     'tBindingOperation': TBindingOperation_, | ||||
|     'tService': TService_, | ||||
|     'tPort': TPort_, | ||||
|     'documentation': TDocumented_documentation, | ||||
|     'input': TBindingOperation_input, | ||||
|     'output': TBindingOperation_output, | ||||
|     'fault': TBindingOperation_fault, | ||||
|     'import': Import, | ||||
|     'types': Types, | ||||
|     'part': TMessage_part, | ||||
|     'operation': TPortType_operation, | ||||
|     'port': TService_port, | ||||
|     'message': Message, | ||||
|     'portType': PortType, | ||||
|     'binding': Binding, | ||||
|     'service': Service, | ||||
|     'tExtensibleAttributesDocumented': TExtensibleAttributesDocumented_, | ||||
|     'tExtensibleDocumented': TExtensibleDocumented_, | ||||
|     'tExtensibilityElement': TExtensibilityElement_, | ||||
| } | ||||
|  | ||||
|  | ||||
| def factory(tag, **kwargs): | ||||
|     return ELEMENT_BY_TAG[tag](**kwargs) | ||||
|  | ||||
							
								
								
									
										829
									
								
								src/saml2/server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										829
									
								
								src/saml2/server.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,829 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2009-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """Contains classes and functions that a SAML2.0 Identity provider (IdP)  | ||||
| or attribute authority (AA) may use to conclude its tasks. | ||||
| """ | ||||
|  | ||||
| import shelve | ||||
| import sys | ||||
| import memcache | ||||
|  | ||||
| from saml2 import saml | ||||
| from saml2 import class_name | ||||
| from saml2 import soap | ||||
| from saml2 import BINDING_HTTP_REDIRECT | ||||
| from saml2 import BINDING_SOAP | ||||
| from saml2 import BINDING_PAOS | ||||
|  | ||||
| from saml2.request import AuthnRequest | ||||
| from saml2.request import AttributeQuery | ||||
| from saml2.request import LogoutRequest | ||||
|  | ||||
| from saml2.s_utils import sid | ||||
| from saml2.s_utils import MissingValue | ||||
| from saml2.s_utils import success_status_factory | ||||
| from saml2.s_utils import OtherError | ||||
| from saml2.s_utils import UnknownPrincipal | ||||
| from saml2.s_utils import UnsupportedBinding | ||||
| from saml2.s_utils import error_status_factory | ||||
|  | ||||
| from saml2.time_util import instant | ||||
|  | ||||
| from saml2.binding import http_soap_message | ||||
| from saml2.binding import http_redirect_message | ||||
| from saml2.binding import http_post_message | ||||
|  | ||||
| from saml2.sigver import security_context | ||||
| from saml2.sigver import signed_instance_factory | ||||
| from saml2.sigver import pre_signature_part | ||||
| from saml2.sigver import response_factory, logoutresponse_factory | ||||
|  | ||||
| from saml2.config import config_factory | ||||
|  | ||||
| from saml2.assertion import Assertion, Policy | ||||
|  | ||||
| class UnknownVO(Exception): | ||||
|     pass | ||||
|      | ||||
| class Identifier(object): | ||||
|     """ A class that handles identifiers of objects """ | ||||
|     def __init__(self, db, voconf=None, debug=0, log=None): | ||||
|         if isinstance(db, basestring): | ||||
|             self.map = shelve.open(db, writeback=True) | ||||
|         else: | ||||
|             self.map = db | ||||
|         self.voconf = voconf | ||||
|         self.debug = debug | ||||
|         self.log = log | ||||
|          | ||||
|     def _store(self, typ, entity_id, local, remote): | ||||
|         self.map["|".join([typ, entity_id, "f", local])] = remote | ||||
|         self.map["|".join([typ, entity_id, "b", remote])] = local | ||||
|      | ||||
|     def _get_remote(self, typ, entity_id, local): | ||||
|         return self.map["|".join([typ, entity_id, "f", local])] | ||||
|  | ||||
|     def _get_local(self, typ, entity_id, remote): | ||||
|         return self.map["|".join([typ, entity_id, "b", remote])] | ||||
|          | ||||
|     def persistent(self, entity_id, subject_id): | ||||
|         """ Keeps the link between a permanent identifier and a  | ||||
|         temporary/pseudo-temporary identifier for a subject | ||||
|          | ||||
|         The store supports look-up both ways: from a permanent local | ||||
|         identifier to a identifier used talking to a SP and from an | ||||
|         identifier given back by an SP to the local permanent. | ||||
|          | ||||
|         :param entity_id: SP entity ID or VO entity ID | ||||
|         :param subject_id: The local permanent identifier of the subject | ||||
|         :return: An arbitrary identifier for the subject unique to the | ||||
|             service/group of services/VO with a given entity_id | ||||
|         """ | ||||
|         try: | ||||
|             return self._get_remote("persistent", entity_id, subject_id) | ||||
|         except KeyError: | ||||
|             temp_id = "xyz" | ||||
|             while True: | ||||
|                 temp_id = sid() | ||||
|                 try: | ||||
|                     self._get_local("persistent", entity_id, temp_id) | ||||
|                 except KeyError: | ||||
|                     break | ||||
|             self._store("persistent", entity_id, subject_id, temp_id) | ||||
|             self.map.sync() | ||||
|              | ||||
|             return temp_id | ||||
|  | ||||
|     def _get_vo_identifier(self, sp_name_qualifier, userid, identity): | ||||
|         try: | ||||
|             vo_conf = self.voconf[sp_name_qualifier] | ||||
|             if "common_identifier" in vo_conf: | ||||
|                 try: | ||||
|                     subj_id = identity[vo_conf["common_identifier"]] | ||||
|                 except KeyError: | ||||
|                     raise MissingValue("Common identifier") | ||||
|             else: | ||||
|                 return self.persistent_nameid(sp_name_qualifier, userid) | ||||
|         except (KeyError, TypeError): | ||||
|             raise UnknownVO("%s" % sp_name_qualifier) | ||||
|  | ||||
|         try: | ||||
|             nameid_format = vo_conf["nameid_format"] | ||||
|         except KeyError: | ||||
|             nameid_format = saml.NAMEID_FORMAT_PERSISTENT | ||||
|  | ||||
|         return saml.NameID(format=nameid_format, | ||||
|                             sp_name_qualifier=sp_name_qualifier, | ||||
|                             text=subj_id) | ||||
|      | ||||
|     def persistent_nameid(self, sp_name_qualifier, userid): | ||||
|         """ Get or create a persistent identifier for this object to be used | ||||
|         when communicating with servers using a specific SPNameQualifier | ||||
|          | ||||
|         :param sp_name_qualifier: An identifier for a 'context' | ||||
|         :param userid: The local permanent identifier of the object  | ||||
|         :return: A persistent random identifier. | ||||
|         """ | ||||
|         subj_id = self.persistent(sp_name_qualifier, userid) | ||||
|         return saml.NameID(format=saml.NAMEID_FORMAT_PERSISTENT, | ||||
|                             sp_name_qualifier=sp_name_qualifier, | ||||
|                             text=subj_id) | ||||
|  | ||||
|     def transient_nameid(self, sp_entity_id, userid): | ||||
|         """ Returns a random one-time identifier. One-time means it is | ||||
|         kept around as long as the session is active. | ||||
|          | ||||
|         :param sp_entity_id: A qualifier to bind the created identifier to | ||||
|         :param userid: The local persistent identifier for the subject. | ||||
|         :return: The created identifier, | ||||
|         """ | ||||
|         temp_id = sid() | ||||
|         while True: | ||||
|             try: | ||||
|                 _ = self._get_local("transient", sp_entity_id, temp_id) | ||||
|                 temp_id = sid() | ||||
|             except KeyError: | ||||
|                 break | ||||
|         self._store("transient", sp_entity_id, userid, temp_id) | ||||
|         self.map.sync() | ||||
|  | ||||
|         return saml.NameID(format=saml.NAMEID_FORMAT_TRANSIENT, | ||||
|                             sp_name_qualifier=sp_entity_id, | ||||
|                             text=temp_id) | ||||
|  | ||||
|     def email_nameid(self, sp_name_qualifier, userid): | ||||
|         return saml.NameID(format=saml.NAMEID_FORMAT_EMAILADDRESS, | ||||
|                        sp_name_qualifier=sp_name_qualifier, | ||||
|                        text=userid) | ||||
|  | ||||
|     def construct_nameid(self, local_policy, userid, sp_entity_id, | ||||
|                         identity=None, name_id_policy=None, sp_nid=None): | ||||
|         """ Returns a name_id for the object. How the name_id is  | ||||
|         constructed depends on the context. | ||||
|          | ||||
|         :param local_policy: The policy the server is configured to follow | ||||
|         :param userid: The local permanent identifier of the object | ||||
|         :param sp_entity_id: The 'user' of the name_id | ||||
|         :param identity: Attribute/value pairs describing the object | ||||
|         :param name_id_policy: The policy the server on the other side wants | ||||
|             us to follow. | ||||
|         :param sp_nid: Name ID Formats from the SPs metadata | ||||
|         :return: NameID instance precursor | ||||
|         """ | ||||
|         if name_id_policy and name_id_policy.sp_name_qualifier: | ||||
|             try: | ||||
|                 return self._get_vo_identifier(name_id_policy.sp_name_qualifier, | ||||
|                                                 userid, identity) | ||||
|             except Exception: | ||||
|                 pass | ||||
|  | ||||
|         if sp_nid: | ||||
|             nameid_format = sp_nid[0] | ||||
|         else: | ||||
|             nameid_format = local_policy.get_nameid_format(sp_entity_id) | ||||
|  | ||||
|         if nameid_format == saml.NAMEID_FORMAT_PERSISTENT: | ||||
|             return self.persistent_nameid(sp_entity_id, userid) | ||||
|         elif nameid_format == saml.NAMEID_FORMAT_TRANSIENT: | ||||
|             return self.transient_nameid(sp_entity_id, userid) | ||||
|         elif nameid_format == saml.NAMEID_FORMAT_EMAILADDRESS: | ||||
|             return self.email_nameid(sp_entity_id, userid) | ||||
|  | ||||
|     def local_name(self, entity_id, remote_id): | ||||
|         """ Get the local persistent name that has the specified remote ID. | ||||
|          | ||||
|         :param entity_id: The identifier of the entity that got the remote id | ||||
|         :param remote_id: The identifier that was exported | ||||
|         :return: Local identifier | ||||
|         """ | ||||
|         try: | ||||
|             return self._get_local("persistent", entity_id, remote_id) | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 return self._get_local("transient", entity_id, remote_id) | ||||
|             except KeyError: | ||||
|                 return None | ||||
|          | ||||
| class Server(object): | ||||
|     """ A class that does things that IdPs or AAs do """ | ||||
|     def __init__(self, config_file="", config=None, _cache="", | ||||
|                     log=None, debug=0, stype="idp"): | ||||
|  | ||||
|         self.log = log | ||||
|         self.debug = debug | ||||
|         self.ident = None | ||||
|         if config_file: | ||||
|             self.load_config(config_file, stype) | ||||
|         elif config: | ||||
|             self.conf = config | ||||
|         else: | ||||
|             raise Exception("Missing configuration") | ||||
|  | ||||
|         if self.log is None: | ||||
|             self.log = self.conf.setup_logger() | ||||
|              | ||||
|         self.metadata = self.conf.metadata | ||||
|         self.sec = security_context(self.conf, log) | ||||
|         self._cache = _cache | ||||
|  | ||||
|         # if cache: | ||||
|         #     if isinstance(cache, basestring): | ||||
|         #         self.cache = Cache(cache) | ||||
|         #     else: | ||||
|         #         self.cache = cache | ||||
|         # else: | ||||
|         #     self.cache = Cache() | ||||
|          | ||||
|     def load_config(self, config_file, stype="idp"): | ||||
|         """ Load the server configuration  | ||||
|          | ||||
|         :param config_file: The name of the configuration file | ||||
|         :param stype: The type of Server ("idp"/"aa") | ||||
|         """ | ||||
|         self.conf = config_factory(stype, config_file) | ||||
|         if stype == "aa": | ||||
|             return | ||||
|          | ||||
|         try: | ||||
|             # subject information is stored in a database | ||||
|             # default database is a shelve database which is OK in some setups | ||||
|             dbspec = self.conf.subject_data | ||||
|             idb = None | ||||
|             if isinstance(dbspec, basestring): | ||||
|                 idb = shelve.open(dbspec, writeback=True) | ||||
|             else: # database spec is a a 2-tuple (type, address) | ||||
|                 print >> sys.stderr, "DBSPEC: %s" % dbspec | ||||
|                 (typ, addr) = dbspec | ||||
|                 if typ == "shelve": | ||||
|                     idb = shelve.open(addr, writeback=True) | ||||
|                 elif typ == "memcached": | ||||
|                     idb = memcache.Client(addr) | ||||
|                 elif typ == "dict": # in-memory dictionary | ||||
|                     idb = addr | ||||
|                      | ||||
|             if idb is not None: | ||||
|                 self.ident = Identifier(idb, self.conf.virtual_organization, | ||||
|                                         self.debug, self.log) | ||||
|             else: | ||||
|                 raise Exception("Couldn't open identity database: %s" % | ||||
|                                 (dbspec,)) | ||||
|         except AttributeError: | ||||
|             self.ident = None | ||||
|      | ||||
|     def issuer(self, entityid=None): | ||||
|         """ Return an Issuer precursor """ | ||||
|         if entityid: | ||||
|             return saml.Issuer(text=entityid, | ||||
|                                 format=saml.NAMEID_FORMAT_ENTITY) | ||||
|         else: | ||||
|             return saml.Issuer(text=self.conf.entityid, | ||||
|                                 format=saml.NAMEID_FORMAT_ENTITY) | ||||
|          | ||||
|     def parse_authn_request(self, enc_request, binding=BINDING_HTTP_REDIRECT): | ||||
|         """Parse a Authentication Request | ||||
|          | ||||
|         :param enc_request: The request in its transport format | ||||
|         :param binding: Which binding that was used to transport the message | ||||
|             to this entity. | ||||
|         :return: A dictionary with keys: | ||||
|             consumer_url - as gotten from the SPs entity_id and the metadata | ||||
|             id - the id of the request | ||||
|             sp_entity_id - the entity id of the SP | ||||
|             request - The verified request | ||||
|         """ | ||||
|          | ||||
|         response = {} | ||||
|         if self.log: | ||||
|             _log_info = self.log.info | ||||
|         else: | ||||
|             _log_info = None | ||||
|  | ||||
|         # The addresses I should receive messages like this on | ||||
|         receiver_addresses = self.conf.endpoint("single_sign_on_service", | ||||
|                                                  binding) | ||||
|         if self.debug and self.log: | ||||
|             _log_info("receiver addresses: %s" % receiver_addresses) | ||||
|             _log_info("Binding: %s" % binding) | ||||
|  | ||||
|  | ||||
|         try: | ||||
|             timeslack = self.conf.accepted_time_diff | ||||
|             if not timeslack: | ||||
|                 timeslack = 0 | ||||
|         except AttributeError: | ||||
|             timeslack = 0 | ||||
|  | ||||
|         authn_request = AuthnRequest(self.sec, | ||||
|                                      self.conf.attribute_converters, | ||||
|                                      receiver_addresses, log=self.log, | ||||
|                                      timeslack=timeslack) | ||||
|  | ||||
|         if binding == BINDING_SOAP or binding == BINDING_PAOS: | ||||
|             # not base64 decoding and unzipping | ||||
|             authn_request.debug=True | ||||
|             _log_info("Don't decode") | ||||
|             authn_request = authn_request.loads(enc_request, decode=False) | ||||
|         else: | ||||
|             authn_request = authn_request.loads(enc_request) | ||||
|  | ||||
|         if self.debug and self.log: | ||||
|             _log_info("Loaded authn_request") | ||||
|  | ||||
|         if authn_request: | ||||
|             authn_request = authn_request.verify() | ||||
|  | ||||
|         if self.debug and self.log: | ||||
|             _log_info("Verified authn_request") | ||||
|  | ||||
|         if not authn_request: | ||||
|             return None | ||||
|              | ||||
|         response["id"] = authn_request.message.id # put in in_reply_to | ||||
|  | ||||
|         sp_entity_id = authn_request.message.issuer.text | ||||
|         # try to find return address in metadata | ||||
|         try: | ||||
|             # What's the binding ? ProtocolBinding | ||||
|             _binding = authn_request.message.protocol_binding | ||||
|             consumer_url = self.metadata.consumer_url(sp_entity_id, | ||||
|                                                       binding=_binding) | ||||
|         except KeyError: | ||||
|             if self.log: | ||||
|                 _log_info("Failed to find consumer URL for %s" % sp_entity_id) | ||||
|                 _log_info("entities: %s" % self.metadata.entity.keys()) | ||||
|             raise UnknownPrincipal(sp_entity_id) | ||||
|              | ||||
|         if not consumer_url: # what to do ? | ||||
|             if self.log: | ||||
|                 _log_info("Couldn't find a consumer URL binding=%s" % _binding) | ||||
|             raise UnsupportedBinding(sp_entity_id) | ||||
|  | ||||
|         response["sp_entity_id"] = sp_entity_id | ||||
|  | ||||
|         if authn_request.message.assertion_consumer_service_url: | ||||
|             return_destination = \ | ||||
|                         authn_request.message.assertion_consumer_service_url | ||||
|          | ||||
|             if consumer_url != return_destination: | ||||
|                 # serious error on someones behalf | ||||
|                 if self.log: | ||||
|                     _log_info("%s != %s" % (consumer_url, return_destination)) | ||||
|                 else: | ||||
|                     print >> sys.stderr, \ | ||||
|                                 "%s != %s" % (consumer_url, return_destination) | ||||
|                 raise OtherError("ConsumerURL and return destination mismatch") | ||||
|          | ||||
|         response["consumer_url"] = consumer_url | ||||
|         response["request"] = authn_request.message | ||||
|  | ||||
|         return response | ||||
|                          | ||||
|     def wants(self, sp_entity_id): | ||||
|         """ Returns what attributes the SP requiers and which are optional | ||||
|         if any such demands are registered in the Metadata. | ||||
|          | ||||
|         :param sp_entity_id: The entity id of the SP | ||||
|         :return: 2-tuple, list of required and list of optional attributes | ||||
|         """ | ||||
|         return self.metadata.requests(sp_entity_id) | ||||
|          | ||||
|     def parse_attribute_query(self, xml_string, decode=True): | ||||
|         """ Parse an attribute query | ||||
|          | ||||
|         :param xml_string: The Attribute Query as an XML string | ||||
|         :param decode: Whether the xmlstring is base64encoded and zipped | ||||
|         :return: 3-Tuple containing: | ||||
|             subject - identifier of the subject | ||||
|             attribute - which attributes that the requestor wants back | ||||
|             query - the whole query | ||||
|         """ | ||||
|         receiver_addresses = self.conf.endpoint("attribute_service") | ||||
|         attribute_query = AttributeQuery( self.sec, receiver_addresses) | ||||
|  | ||||
|         attribute_query = attribute_query.loads(xml_string, decode=decode) | ||||
|         attribute_query = attribute_query.verify() | ||||
|  | ||||
|         self.log.info("KEYS: %s" % attribute_query.message.keys()) | ||||
|         # Subject is described in the a saml.Subject instance | ||||
|         subject = attribute_query.subject_id() | ||||
|         attribute = attribute_query.attribute() | ||||
|  | ||||
|         return subject, attribute, attribute_query.message | ||||
|              | ||||
|     # ------------------------------------------------------------------------ | ||||
|  | ||||
|     def _response(self, in_response_to, consumer_url=None, sp_entity_id=None,  | ||||
|                     identity=None, name_id=None, status=None, sign=False, | ||||
|                     policy=Policy(), authn=None, authn_decl=None, issuer=None): | ||||
|         """ Create a Response that adhers to the ??? profile. | ||||
|          | ||||
|         :param in_response_to: The session identifier of the request | ||||
|         :param consumer_url: The URL which should receive the response | ||||
|         :param sp_entity_id: The entity identifier of the SP | ||||
|         :param identity: A dictionary with attributes and values that are | ||||
|             expected to be the bases for the assertion in the response. | ||||
|         :param name_id: The identifier of the subject | ||||
|         :param status: The status of the response | ||||
|         :param sign: Whether the assertion should be signed or not  | ||||
|         :param policy: The attribute release policy for this instance | ||||
|         :param authn: A 2-tuple denoting the authn class and the authn | ||||
|             authority | ||||
|         :param authn_decl: | ||||
|         :param issuer: The issuer of the response | ||||
|         :return: A Response instance | ||||
|         """ | ||||
|                  | ||||
|         to_sign = [] | ||||
|  | ||||
|         if not status:  | ||||
|             status = success_status_factory() | ||||
|  | ||||
|         _issuer = self.issuer(issuer) | ||||
|  | ||||
|         response = response_factory( | ||||
|             issuer=_issuer, | ||||
|             in_response_to = in_response_to, | ||||
|             status = status, | ||||
|             ) | ||||
|  | ||||
|         if consumer_url: | ||||
|             response.destination = consumer_url | ||||
|  | ||||
|         if identity:             | ||||
|             ast = Assertion(identity) | ||||
|             try: | ||||
|                 ast.apply_policy(sp_entity_id, policy, self.metadata) | ||||
|             except MissingValue, exc: | ||||
|                 return self.error_response(in_response_to, consumer_url,  | ||||
|                                                sp_entity_id, exc, name_id) | ||||
|  | ||||
|             if authn: # expected to be a 2-tuple class+authority | ||||
|                 (authn_class, authn_authn) = authn | ||||
|                 assertion = ast.construct(sp_entity_id, in_response_to,  | ||||
|                                             consumer_url, name_id, | ||||
|                                             self.conf.attribute_converters, | ||||
|                                             policy, issuer=_issuer,  | ||||
|                                             authn_class=authn_class,  | ||||
|                                             authn_auth=authn_authn) | ||||
|             elif authn_decl: | ||||
|                 assertion = ast.construct(sp_entity_id, in_response_to,  | ||||
|                                             consumer_url, name_id, | ||||
|                                             self.conf.attribute_converters, | ||||
|                                             policy, issuer=_issuer,  | ||||
|                                             authn_decl=authn_decl) | ||||
|             else: | ||||
|                 assertion = ast.construct(sp_entity_id, in_response_to,  | ||||
|                                             consumer_url, name_id, | ||||
|                                             self.conf.attribute_converters, | ||||
|                                             policy, issuer=_issuer) | ||||
|              | ||||
|             if sign: | ||||
|                 assertion.signature = pre_signature_part(assertion.id, | ||||
|                                                         self.sec.my_cert, 1) | ||||
|                 # Just the assertion or the response and the assertion ? | ||||
|                 to_sign = [(class_name(assertion), assertion.id)] | ||||
|  | ||||
|             # Store which assertion that has been sent to which SP about which | ||||
|             # subject. | ||||
|              | ||||
|             # self.cache.set(assertion.subject.name_id.text,  | ||||
|             #                 sp_entity_id, {"ava": identity, "authn": authn},  | ||||
|             #                 assertion.conditions.not_on_or_after) | ||||
|              | ||||
|             response.assertion = assertion | ||||
|                  | ||||
|         return signed_instance_factory(response, self.sec, to_sign) | ||||
|  | ||||
|     # ------------------------------------------------------------------------ | ||||
|      | ||||
|     def do_response(self, in_response_to, consumer_url, | ||||
|                         sp_entity_id, identity=None, name_id=None,  | ||||
|                         status=None, sign=False, authn=None, authn_decl=None, | ||||
|                         issuer=None): | ||||
|         """ Create a response. A layer of indirection. | ||||
|          | ||||
|         :param in_response_to: The session identifier of the request | ||||
|         :param consumer_url: The URL which should receive the response | ||||
|         :param sp_entity_id: The entity identifier of the SP | ||||
|         :param identity: A dictionary with attributes and values that are | ||||
|             expected to be the bases for the assertion in the response. | ||||
|         :param name_id: The identifier of the subject | ||||
|         :param status: The status of the response | ||||
|         :param sign: Whether the assertion should be signed or not  | ||||
|         :param authn: A 2-tuple denoting the authn class and the authn | ||||
|             authority. | ||||
|         :param authn_decl: | ||||
|         :param issuer: The issuer of the response | ||||
|         :return: A Response instance. | ||||
|         """ | ||||
|  | ||||
|         policy = self.conf.policy | ||||
|  | ||||
|         return self._response(in_response_to, consumer_url, | ||||
|                         sp_entity_id, identity, name_id,  | ||||
|                         status, sign, policy, authn, authn_decl, issuer) | ||||
|                          | ||||
|     # ------------------------------------------------------------------------ | ||||
|      | ||||
|     def error_response(self, in_response_to, destination, spid, info,  | ||||
|                         name_id=None, sign=False, issuer=None): | ||||
|         """ Create a error response. | ||||
|          | ||||
|         :param in_response_to: The identifier of the message this is a response | ||||
|             to. | ||||
|             :param destination: The intended recipient of this message | ||||
|         :param spid: The entitiy ID of the SP that will get this. | ||||
|         :param info: Either an Exception instance or a 2-tuple consisting of | ||||
|             error code and descriptive text | ||||
|         :param name_id: | ||||
|         :param sign: Whether the message should be signed or not | ||||
|         :param issuer: The issuer of the response | ||||
|         :return: A Response instance | ||||
|         """ | ||||
|         status = error_status_factory(info) | ||||
|              | ||||
|         return self._response( | ||||
|                         in_response_to, # in_response_to | ||||
|                         destination,    # consumer_url | ||||
|                         spid,           # sp_entity_id | ||||
|                         name_id=name_id, | ||||
|                         status=status, | ||||
|                         sign=sign, | ||||
|                         issuer=issuer | ||||
|                         ) | ||||
|  | ||||
|     # ------------------------------------------------------------------------ | ||||
|     #noinspection PyUnusedLocal | ||||
|     def do_aa_response(self, in_response_to, consumer_url, sp_entity_id,  | ||||
|                         identity=None, userid="", name_id=None, status=None,  | ||||
|                         sign=False, _name_id_policy=None, issuer=None): | ||||
|         """ Create an attribute assertion response. | ||||
|          | ||||
|         :param in_response_to: The session identifier of the request | ||||
|         :param consumer_url: The URL which should receive the response | ||||
|         :param sp_entity_id: The entity identifier of the SP | ||||
|         :param identity: A dictionary with attributes and values that are | ||||
|             expected to be the bases for the assertion in the response. | ||||
|         :param userid: A identifier of the user | ||||
|         :param name_id: The identifier of the subject | ||||
|         :param status: The status of the response | ||||
|         :param sign: Whether the assertion should be signed or not  | ||||
|         :param _name_id_policy: Policy for NameID creation. | ||||
|         :param issuer: The issuer of the response | ||||
|         :return: A Response instance. | ||||
|         """ | ||||
| #        name_id = self.ident.construct_nameid(self.conf.policy, userid, | ||||
| #                                            sp_entity_id, identity) | ||||
|          | ||||
|         return self._response(in_response_to, consumer_url, | ||||
|                         sp_entity_id, identity, name_id,  | ||||
|                         status, sign, policy=self.conf.policy, issuer=issuer) | ||||
|  | ||||
|     # ------------------------------------------------------------------------ | ||||
|  | ||||
|     def authn_response(self, identity, in_response_to, destination, | ||||
|                         sp_entity_id, name_id_policy, userid, sign=False,  | ||||
|                         authn=None, sign_response=False, authn_decl=None, | ||||
|                         issuer=None, instance=False): | ||||
|         """ Constructs an AuthenticationResponse | ||||
|  | ||||
|         :param identity: Information about an user | ||||
|         :param in_response_to: The identifier of the authentication request | ||||
|             this response is an answer to. | ||||
|         :param destination: Where the response should be sent | ||||
|         :param sp_entity_id: The entity identifier of the Service Provider | ||||
|         :param name_id_policy: ... | ||||
|         :param userid: The subject identifier | ||||
|         :param sign: Whether the assertion should be signed or not. This is | ||||
|             different from signing the response as such. | ||||
|         :param authn: Information about the authentication | ||||
|         :param sign_response: The response can be signed separately from the  | ||||
|             assertions. | ||||
|         :param authn_decl: | ||||
|         :param issuer: Issuer of the response | ||||
|         :param instance: Whether to return the instance or a string | ||||
|             representation | ||||
|         :return: A XML string representing an authentication response | ||||
|         """ | ||||
|  | ||||
|         name_id = None | ||||
|         try: | ||||
|             nid_formats = [] | ||||
|             for _sp in self.metadata.entity[sp_entity_id]["sp_sso"]: | ||||
|                 nid_formats.extend([n.text for n in _sp.name_id_format]) | ||||
|  | ||||
|             policy = self.conf.policy | ||||
|             name_id = self.ident.construct_nameid(policy, userid, sp_entity_id, | ||||
|                                                     identity, name_id_policy, | ||||
|                                                     nid_formats) | ||||
|         except IOError, exc: | ||||
|             response = self.error_response(in_response_to, destination,  | ||||
|                                             sp_entity_id, exc, name_id) | ||||
|             return ("%s" % response).split("\n") | ||||
|          | ||||
|         try: | ||||
|             response = self.do_response( | ||||
|                             in_response_to, # in_response_to | ||||
|                             destination,    # consumer_url | ||||
|                             sp_entity_id,   # sp_entity_id | ||||
|                             identity,       # identity as dictionary | ||||
|                             name_id, | ||||
|                             sign=sign,      # If the assertion should be signed | ||||
|                             authn=authn,    # Information about the  | ||||
|                                             #   authentication | ||||
|                             authn_decl=authn_decl, | ||||
|                             issuer=issuer | ||||
|                         ) | ||||
|         except MissingValue, exc: | ||||
|             response = self.error_response(in_response_to, destination,  | ||||
|                                         sp_entity_id, exc, name_id) | ||||
|          | ||||
|  | ||||
|         if sign_response: | ||||
|             try: | ||||
|                 response.signature = pre_signature_part(response.id, | ||||
|                                                         self.sec.my_cert, 2) | ||||
|          | ||||
|                 return self.sec.sign_statement_using_xmlsec(response, | ||||
|                                                         class_name(response), | ||||
|                                                         nodeid=response.id) | ||||
|             except Exception, exc: | ||||
|                 response = self.error_response(in_response_to, destination,  | ||||
|                                                 sp_entity_id, exc, name_id) | ||||
|                 if instance: | ||||
|                     return response | ||||
|                 else: | ||||
|                     return ("%s" % response).split("\n") | ||||
|         else: | ||||
|             if instance: | ||||
|                 return response | ||||
|             else: | ||||
|                 return ("%s" % response).split("\n") | ||||
|  | ||||
|     def parse_logout_request(self, text, binding=BINDING_SOAP): | ||||
|         """Parse a Logout Request | ||||
|          | ||||
|         :param text: The request in its transport format, if the binding is  | ||||
|             HTTP-Redirect or HTTP-Post the text *must* be the value of the  | ||||
|             SAMLRequest attribute. | ||||
|         :return: A validated LogoutRequest instance or None if validation  | ||||
|             failed. | ||||
|         """ | ||||
|          | ||||
|         try: | ||||
|             slo = self.conf.endpoint("single_logout_service", binding) | ||||
|         except IndexError: | ||||
|             if self.log: | ||||
|                 self.log.info("enpoints: %s" % (self.conf.endpoints,)) | ||||
|                 self.log.info("binding wanted: %s" % (binding,)) | ||||
|             raise | ||||
|  | ||||
|         if not slo: | ||||
|             raise Exception("No single_logout_server for that binding") | ||||
|          | ||||
|         if self.log: | ||||
|             self.log.info("Endpoint: %s" % slo) | ||||
|         req = LogoutRequest(self.sec, slo) | ||||
|         if binding == BINDING_SOAP: | ||||
|             lreq = soap.parse_soap_enveloped_saml_logout_request(text) | ||||
|             try: | ||||
|                 req = req.loads(lreq, False) # Got it over SOAP so no base64+zip | ||||
|             except Exception: | ||||
|                 return None | ||||
|         else: | ||||
|             try: | ||||
|                 req = req.loads(text) | ||||
|             except Exception, exc: | ||||
|                 self.log.error("%s" % (exc,)) | ||||
|                 return None | ||||
|  | ||||
|         req = req.verify() | ||||
|          | ||||
|         if not req: # Not a valid request | ||||
|             # return a error message with status code element set to | ||||
|             # urn:oasis:names:tc:SAML:2.0:status:Requester | ||||
|             return None | ||||
|         else: | ||||
|             return req | ||||
|  | ||||
|  | ||||
|     def logout_response(self, request, bindings, status=None, | ||||
|                             sign=False, issuer=None): | ||||
|         """ Create a LogoutResponse. What is returned depends on which binding | ||||
|         is used. | ||||
|          | ||||
|         :param request: The request this is a response to | ||||
|         :param bindings: Which bindings that can be used to send the response | ||||
|         :param status: The return status of the response operation | ||||
|         :param issuer: The issuer of the message | ||||
|         :return: A 3-tuple consisting of HTTP return code, HTTP headers and  | ||||
|             possibly a message. | ||||
|         """ | ||||
|         sp_entity_id = request.issuer.text.strip() | ||||
|          | ||||
|         binding = None | ||||
|         destinations = [] | ||||
|         for binding in bindings: | ||||
|             destinations = self.conf.single_logout_services(sp_entity_id, | ||||
|                                                            binding) | ||||
|             if destinations: | ||||
|                 break | ||||
|                  | ||||
|  | ||||
|         if not destinations: | ||||
|             if self.log: | ||||
|                 self.log.error("Not way to return a response !!!") | ||||
|             return ("412 Precondition Failed", | ||||
|                     [("Content-type", "text/html")], | ||||
|                     ["No return way defined"]) | ||||
|          | ||||
|         # Pick the first | ||||
|         destination = destinations[0] | ||||
|          | ||||
|         if self.log: | ||||
|             self.log.info("Logout Destination: %s, binding: %s" % (destination, | ||||
|                                                                     binding)) | ||||
|         if not status:  | ||||
|             status = success_status_factory() | ||||
|  | ||||
|         mid = sid() | ||||
|         rcode = "200 OK" | ||||
|          | ||||
|         # response and packaging differs depending on binding | ||||
|          | ||||
|         if binding == BINDING_SOAP: | ||||
|             response = logoutresponse_factory( | ||||
|                                 sign=sign, | ||||
|                                 id = mid, | ||||
|                                 in_response_to = request.id, | ||||
|                                 status = status, | ||||
|                                 ) | ||||
|             if sign: | ||||
|                 to_sign = [(class_name(response), mid)] | ||||
|                 response = signed_instance_factory(response, self.sec, to_sign) | ||||
|                  | ||||
|             (headers, message) = http_soap_message(response) | ||||
|         else: | ||||
|             _issuer = self.issuer(issuer) | ||||
|             response = logoutresponse_factory( | ||||
|                                 sign=sign, | ||||
|                                 id = mid, | ||||
|                                 in_response_to = request.id, | ||||
|                                 status = status, | ||||
|                                 issuer = _issuer, | ||||
|                                 destination = destination, | ||||
|                                 sp_entity_id = sp_entity_id, | ||||
|                                 instant=instant(), | ||||
|                                 ) | ||||
|             if sign: | ||||
|                 to_sign = [(class_name(response), mid)] | ||||
|                 response = signed_instance_factory(response, self.sec, to_sign) | ||||
|                  | ||||
|             if self.log: | ||||
|                 self.log.info("Response: %s" % (response,)) | ||||
|             if binding == BINDING_HTTP_REDIRECT: | ||||
|                 (headers, message) = http_redirect_message(response,  | ||||
|                                                             destination,  | ||||
|                                                             typ="SAMLResponse") | ||||
|                 rcode = "302 Found" | ||||
|             else: | ||||
|                 (headers, message) = http_post_message(response, destination, | ||||
|                                                         typ="SAMLResponse") | ||||
|                  | ||||
|         return rcode, headers, message | ||||
|  | ||||
|     def parse_authz_decision_query(self, xml_string): | ||||
|         """ Parse an attribute query | ||||
|  | ||||
|         :param xml_string: The Authz decision Query as an XML string | ||||
|         :return: 3-Tuple containing: | ||||
|             subject - identifier of the subject | ||||
|             attribute - which attributes that the requestor wants back | ||||
|             query - the whole query | ||||
|         """ | ||||
|         receiver_addresses = self.conf.endpoint("attribute_service") | ||||
|         attribute_query = AttributeQuery( self.sec, receiver_addresses) | ||||
|  | ||||
|         attribute_query = attribute_query.loads(xml_string) | ||||
|         attribute_query = attribute_query.verify() | ||||
|  | ||||
|         # Subject name is a BaseID,NameID or EncryptedID instance | ||||
|         subject = attribute_query.subject_id() | ||||
|         attribute = attribute_query.attribute() | ||||
|  | ||||
|         return subject, attribute, attribute_query.message | ||||
							
								
								
									
										1029
									
								
								src/saml2/sigver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1029
									
								
								src/saml2/sigver.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										336
									
								
								src/saml2/soap.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								src/saml2/soap.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,336 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2009-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
|  | ||||
| """ | ||||
| Suppport for the client part of the SAML2.0 SOAP binding. | ||||
| """ | ||||
|  | ||||
| from httplib2 import Http | ||||
|  | ||||
| from saml2 import httplib2cookie | ||||
| from saml2 import create_class_from_element_tree | ||||
| from saml2.samlp import NAMESPACE as SAMLP_NAMESPACE | ||||
| #from saml2 import element_to_extension_element | ||||
| from saml2.schema import soapenv | ||||
| from saml2 import class_name | ||||
|  | ||||
| try: | ||||
|     from xml.etree import cElementTree as ElementTree | ||||
| except ImportError: | ||||
|     try: | ||||
|         import cElementTree as ElementTree | ||||
|     except ImportError: | ||||
|         #noinspection PyUnresolvedReferences | ||||
|         from elementtree import ElementTree | ||||
|  | ||||
| class XmlParseError(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| #NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/" | ||||
|  | ||||
| def parse_soap_enveloped_saml_response(text): | ||||
|     tags = ['{%s}Response' % SAMLP_NAMESPACE,  | ||||
|             '{%s}LogoutResponse' % SAMLP_NAMESPACE] | ||||
|     return parse_soap_enveloped_saml_thingy(text, tags) | ||||
|  | ||||
| def parse_soap_enveloped_saml_attribute_query(text): | ||||
|     expected_tag = '{%s}AttributeQuery' % SAMLP_NAMESPACE | ||||
|     return parse_soap_enveloped_saml_thingy(text, [expected_tag]) | ||||
|  | ||||
| def parse_soap_enveloped_saml_logout_request(text): | ||||
|     expected_tag = '{%s}LogoutRequest' % SAMLP_NAMESPACE | ||||
|     return parse_soap_enveloped_saml_thingy(text, [expected_tag]) | ||||
|  | ||||
| def parse_soap_enveloped_saml_authentication_request(text): | ||||
|     expected_tag = '{%s}AuthenticationRequest' % SAMLP_NAMESPACE | ||||
|     return parse_soap_enveloped_saml_thingy(text, [expected_tag]) | ||||
|  | ||||
| #def parse_soap_enveloped_saml_logout_response(text): | ||||
| #    expected_tag = '{%s}LogoutResponse' % SAMLP_NAMESPACE | ||||
| #    return parse_soap_enveloped_saml_thingy(text, [expected_tag]) | ||||
|  | ||||
| def parse_soap_enveloped_saml_thingy(text, expected_tags): | ||||
|     """Parses a SOAP enveloped SAML thing and returns the thing as | ||||
|     a string. | ||||
|      | ||||
|     :param text: The SOAP object as XML  | ||||
|     :param expected_tags: What the tag of the SAML thingy is expected to be. | ||||
|     :return: SAML thingy as a string | ||||
|     """ | ||||
|     envelope = ElementTree.fromstring(text) | ||||
| #    if True: | ||||
| #        fil = open("soap.xml", "w") | ||||
| #        fil.write(text) | ||||
| #        fil.close() | ||||
|          | ||||
|     assert envelope.tag == '{%s}Envelope' % soapenv.NAMESPACE | ||||
|      | ||||
|     assert len(envelope) >= 1 | ||||
|     body = None | ||||
|     for part in envelope: | ||||
|         if part.tag == '{%s}Body' % soapenv.NAMESPACE: | ||||
|             assert len(part) == 1 | ||||
|             body = part | ||||
|             break | ||||
|  | ||||
|     if body is None: | ||||
|         return "" | ||||
|      | ||||
|     saml_part = body[0] | ||||
|     if saml_part.tag in expected_tags: | ||||
|         return ElementTree.tostring(saml_part, encoding="UTF-8") | ||||
|     else: | ||||
|         return "" | ||||
|  | ||||
| import re | ||||
|  | ||||
| NS_AND_TAG = re.compile("\{([^}]+)\}(.*)") | ||||
|  | ||||
| def class_instances_from_soap_enveloped_saml_thingies(text, modules): | ||||
|     """Parses a SOAP enveloped header and body SAML thing and returns the | ||||
|     thing as a dictionary class instance. | ||||
|  | ||||
|     :param text: The SOAP object as XML | ||||
|     :param modules: modules representing xsd schemas | ||||
|     :return: SAML thingy as a class instance | ||||
|     """ | ||||
|     try: | ||||
|         envelope = ElementTree.fromstring(text) | ||||
|     except Exception, exc: | ||||
|         raise XmlParseError("%s" % exc) | ||||
|  | ||||
|     assert envelope.tag == '{%s}Envelope' % soapenv.NAMESPACE | ||||
|     assert len(envelope) >= 1 | ||||
|     env = {"header":[], "body":None} | ||||
|      | ||||
|     for part in envelope: | ||||
|         if part.tag == '{%s}Body' % soapenv.NAMESPACE: | ||||
|             assert len(part) == 1 | ||||
|             m = NS_AND_TAG.match(part[0].tag) | ||||
|             ns,tag = m.groups() | ||||
|             for module in modules: | ||||
|                 if module.NAMESPACE == ns: | ||||
|                     try: | ||||
|                         target = module.ELEMENT_BY_TAG[tag] | ||||
|                         env["body"] = create_class_from_element_tree(target, | ||||
|                                                                      part[0]) | ||||
|                     except KeyError: | ||||
|                         continue | ||||
|         elif part.tag == "{%s}Header" % soapenv.NAMESPACE: | ||||
|             for item in part: | ||||
|                 m = NS_AND_TAG.match(item.tag) | ||||
|                 ns,tag = m.groups() | ||||
|                 for module in modules: | ||||
|                     if module.NAMESPACE == ns: | ||||
|                         try: | ||||
|                             target = module.ELEMENT_BY_TAG[tag] | ||||
|                             env["header"].append(create_class_from_element_tree( | ||||
|                                                                     target, | ||||
|                                                                     item)) | ||||
|                         except KeyError: | ||||
|                             continue | ||||
|  | ||||
|     return env | ||||
|  | ||||
| def make_soap_enveloped_saml_thingy(thingy, headers=None): | ||||
|     """ Returns a soap envelope containing a SAML request | ||||
|     as a text string. | ||||
|      | ||||
|     :param thingy: The SAML thingy | ||||
|     :return: The SOAP envelope as a string | ||||
|     """ | ||||
|     soap_envelope = soapenv.Envelope() | ||||
|  | ||||
|     if headers: | ||||
|         _header = soapenv.Header() | ||||
|         _header.add_extension_elements(headers) | ||||
|         soap_envelope.header = _header | ||||
|  | ||||
|     soap_envelope.body = soapenv.Body() | ||||
|     soap_envelope.body.add_extension_element(thingy) | ||||
|  | ||||
|     return "%s" % soap_envelope | ||||
|  | ||||
| def soap_fault(message=None, actor=None, code=None, detail=None): | ||||
|     """ Create a SOAP Fault message | ||||
|  | ||||
|     :param message: Human readable error message | ||||
|     :param actor: Who discovered the error | ||||
|     :param code: Error code | ||||
|     :param detail: More specific error message | ||||
|     :return: A SOAP Fault message as a string | ||||
|     """ | ||||
|     _string = _actor = _code = _detail = None | ||||
|  | ||||
|     if message: | ||||
|         _string = soapenv.Fault_faultstring(text=message) | ||||
|     if actor: | ||||
|         _actor = soapenv.Fault_faultactor(text=actor) | ||||
|     if code: | ||||
|         _code = soapenv.Fault_faultcode(text=code) | ||||
|     if detail: | ||||
|         _detail = soapenv.Fault_detail(text=detail) | ||||
|  | ||||
|     fault = soapenv.Fault( | ||||
|         faultcode=_code, | ||||
|         faultstring=_string, | ||||
|         faultactor=_actor, | ||||
|         detail=_detail, | ||||
|     ) | ||||
|  | ||||
|     return "%s" % fault | ||||
|  | ||||
| class HTTPClient(object): | ||||
|     """ For sending a message to a HTTP server using POST or GET """ | ||||
|     def __init__(self, path, keyfile=None, certfile=None, log=None, | ||||
|                  cookiejar=None, ca_certs="", | ||||
|                  disable_ssl_certificate_validation=True): | ||||
|         self.path = path | ||||
|         if cookiejar is not None: | ||||
|             self.cj = True | ||||
|             self.server = httplib2cookie.CookiefulHttp(cookiejar, | ||||
|                 ca_certs=ca_certs, | ||||
|                 disable_ssl_certificate_validation=disable_ssl_certificate_validation) | ||||
|         else: | ||||
|             self.cj = False | ||||
|             self.server = Http(ca_certs=ca_certs, | ||||
|                 disable_ssl_certificate_validation=disable_ssl_certificate_validation) | ||||
|         self.log = log | ||||
|         self.response = None | ||||
|  | ||||
|         if keyfile: | ||||
|             self.server.add_certificate(keyfile, certfile, "") | ||||
|  | ||||
|     def post(self, data, headers=None, path=None): | ||||
|         if headers is None: | ||||
|             headers = {} | ||||
|         if path is None: | ||||
|             path = self.path | ||||
|  | ||||
|         if self.cj: | ||||
|             (response, content) = self.server.crequest(path, method="POST", | ||||
|                                                         body=data, | ||||
|                                                         headers=headers) | ||||
|         else: | ||||
|             (response, content) = self.server.request(path, method="POST", | ||||
|                                                         body=data, | ||||
|                                                         headers=headers) | ||||
|  | ||||
|         if response.status == 200 or response.status == 201: | ||||
|             return content | ||||
| #        elif response.status == 302: # redirect | ||||
| #            return self.post(data, headers, response["location"]) | ||||
|         else: | ||||
|             self.response = response | ||||
|             self.error_description = content | ||||
|             return False | ||||
|  | ||||
|     def get(self, headers=None, path=None): | ||||
|         if path is None: | ||||
|             path = self.path | ||||
|  | ||||
|         if headers is None: | ||||
|             headers = {"content-type": "text/html"} | ||||
|  | ||||
|         (response, content) = self.server.crequest(path, method="GET", | ||||
|                                                      headers=headers) | ||||
|         if response.status == 200 or response.status == 201: | ||||
|             return content | ||||
| #        elif response.status == 302: # redirect | ||||
| #            return self.get(headers, response["location"]) | ||||
|         else: | ||||
|             self.response = response | ||||
|             self.error_description = content | ||||
|             return None | ||||
|  | ||||
|     def put(self, data, headers=None, path=None): | ||||
|         if headers is None: | ||||
|             headers = {} | ||||
|         if path is None: | ||||
|             path = self.path | ||||
|  | ||||
|         (response, content) = self.server.crequest(path, method="PUT", | ||||
|                                                     body=data, | ||||
|                                                     headers=headers) | ||||
|         if response.status == 200 or response.status == 201: | ||||
|             return content | ||||
|         else: | ||||
|             self.response = response | ||||
|             self.error_description = content | ||||
|             return False | ||||
|  | ||||
|     def delete(self, headers=None, path=None): | ||||
|         if headers is None: | ||||
|             headers = {} | ||||
|         if path is None: | ||||
|             path = self.path | ||||
|  | ||||
|         (response, content) = self.server.crequest(path, method="DELETE", | ||||
|                                                     headers=headers) | ||||
|         if response.status == 200 or response.status == 201: | ||||
|             return content | ||||
|         else: | ||||
|             self.response = response | ||||
|             self.error_description = content | ||||
|             return False | ||||
|  | ||||
|  | ||||
|     def add_credentials(self, name, passwd): | ||||
|         self.server.add_credentials(name, passwd) | ||||
|  | ||||
|     def clear_credentials(self): | ||||
|         self.server.clear_credentials() | ||||
|  | ||||
|  | ||||
| class SOAPClient(object): | ||||
|      | ||||
|     def __init__(self, server_url, keyfile=None, certfile=None, log=None, | ||||
|                  cookiejar=None, ca_certs="", | ||||
|                  disable_ssl_certificate_validation=True): | ||||
|         self.server = HTTPClient(server_url, keyfile, certfile, log, | ||||
|                 cookiejar, ca_certs=ca_certs, | ||||
|                 disable_ssl_certificate_validation=disable_ssl_certificate_validation) | ||||
|         self.log = log | ||||
|         self.response = None | ||||
|          | ||||
|     def send(self, request, path=None, headers=None, sign=None, sec=None): | ||||
|         if headers is None: | ||||
|             headers = {"content-type": "application/soap+xml"} | ||||
|         else: | ||||
|             headers.update({"content-type": "application/soap+xml"}) | ||||
|  | ||||
|         soap_message = make_soap_enveloped_saml_thingy(request) | ||||
|         if sign: | ||||
|             _signed = sec.sign_statement_using_xmlsec(soap_message, | ||||
|                                                       class_name(request), | ||||
|                                                       nodeid=request.id) | ||||
|             soap_message = _signed | ||||
|  | ||||
|         _response = self.server.post(soap_message, headers, path=path) | ||||
|  | ||||
|         self.response = _response | ||||
|         if _response: | ||||
|             if self.log: | ||||
|                 self.log.info("SOAP response: %s" % _response) | ||||
|             return parse_soap_enveloped_saml_response(_response) | ||||
|         else: | ||||
|             return False | ||||
|  | ||||
|     def add_credentials(self, name, passwd): | ||||
|         self.server.add_credentials(name, passwd) | ||||
|  | ||||
							
								
								
									
										292
									
								
								src/saml2/time_util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								src/saml2/time_util.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,292 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2009-2011 Umeå University | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #            http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| """  | ||||
| Implements some usefull functions when dealing with validity of  | ||||
| different types of information. | ||||
| """ | ||||
|  | ||||
| import calendar | ||||
| import re | ||||
| import time | ||||
| import sys | ||||
|  | ||||
| from datetime import timedelta | ||||
| from datetime import datetime | ||||
|  | ||||
| TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" | ||||
| TIME_FORMAT_WITH_FRAGMENT = re.compile( | ||||
|     "^(\d{4,4}-\d{2,2}-\d{2,2}T\d{2,2}:\d{2,2}:\d{2,2})\.\d*Z$") | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
| #I'm sure this is implemeted somewhere else cann't find it now though, so I | ||||
| #made an attempt. | ||||
| #Implemented according to  | ||||
| #http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/ | ||||
| #adding-durations-to-dateTimes | ||||
|  | ||||
| def f_quotient(arg0, arg1, arg2=0): | ||||
|     if arg2: | ||||
|         return int((arg0-arg1)/(arg2-arg1)) | ||||
|     elif not arg0: | ||||
|         return 0 | ||||
|     else: | ||||
|         return int(arg0/arg1) | ||||
|  | ||||
| def modulo(arg0, arg1, arg2=0): | ||||
|     if arg2: | ||||
|         return ((arg0 - arg1) % (arg2 - arg1)) + arg1 | ||||
|     else: | ||||
|         return arg0 % arg1 | ||||
|  | ||||
|  | ||||
| def maximum_day_in_month_for(year, month): | ||||
|     return calendar.monthrange(year, month)[1] | ||||
|  | ||||
|  | ||||
| D_FORMAT = [ | ||||
|     ("Y", "tm_year"), | ||||
|     ("M", "tm_mon"), | ||||
|     ("D", "tm_mday"), | ||||
|     ("T", None), | ||||
|     ("H", "tm_hour"), | ||||
|     ("M", "tm_min"), | ||||
|     ("S", "tm_sec") | ||||
| ] | ||||
|  | ||||
| def parse_duration(duration): | ||||
|     # (-)PnYnMnDTnHnMnS | ||||
|     index = 0 | ||||
|     if duration[0] == '-': | ||||
|         sign = '-' | ||||
|         index += 1 | ||||
|     else: | ||||
|         sign = '+' | ||||
|     assert duration[index] == "P" | ||||
|     index += 1 | ||||
|      | ||||
|     dic = dict([(typ, 0) for (code, typ) in D_FORMAT]) | ||||
|      | ||||
|     for code, typ in D_FORMAT: | ||||
|         #print duration[index:], code | ||||
|         if duration[index] == '-': | ||||
|             raise Exception("Negation not allowed on individual items") | ||||
|         if code == "T": | ||||
|             if duration[index] == "T": | ||||
|                 index += 1 | ||||
|                 if index == len(duration): | ||||
|                     raise Exception("Not allowed to end with 'T'") | ||||
|             else: | ||||
|                 raise Exception("Missing T") | ||||
|         else: | ||||
|             try: | ||||
|                 mod = duration[index:].index(code) | ||||
|                 try: | ||||
|                     dic[typ] = int(duration[index:index+mod]) | ||||
|                 except ValueError: | ||||
|                     if code == "S": | ||||
|                         try: | ||||
|                             dic[typ] = float(duration[index:index+mod]) | ||||
|                         except ValueError: | ||||
|                             raise Exception("Not a float") | ||||
|                     else: | ||||
|                         raise Exception( | ||||
|                                 "Fractions not allow on anything byt seconds") | ||||
|                 index = mod+index+1 | ||||
|             except ValueError: | ||||
|                 dic[typ] = 0 | ||||
|  | ||||
|         if index == len(duration): | ||||
|             break | ||||
|          | ||||
|     return sign, dic | ||||
|      | ||||
| def add_duration(tid, duration): | ||||
|      | ||||
|     (sign, dur) = parse_duration(duration) | ||||
|      | ||||
|     if sign == '+': | ||||
|         #Months | ||||
|         temp = tid.tm_mon + dur["tm_mon"] | ||||
|         month = modulo(temp, 1, 13) | ||||
|         carry = f_quotient(temp, 1, 13) | ||||
|         #Years | ||||
|         year = tid.tm_year + dur["tm_year"] + carry | ||||
|         # seconds | ||||
|         temp = tid.tm_sec + dur["tm_sec"] | ||||
|         secs = modulo(temp, 60) | ||||
|         carry = f_quotient(temp, 60) | ||||
|         # minutes | ||||
|         temp = tid.tm_min + dur["tm_min"] + carry | ||||
|         minutes = modulo(temp, 60) | ||||
|         carry = f_quotient(temp, 60) | ||||
|         # hours | ||||
|         temp = tid.tm_hour + dur["tm_hour"] + carry | ||||
|         hour = modulo(temp, 60) | ||||
|         carry = f_quotient(temp, 60) | ||||
|         # days | ||||
|         if dur["tm_mday"] > maximum_day_in_month_for(year, month): | ||||
|             temp_days = maximum_day_in_month_for(year, month) | ||||
|         elif dur["tm_mday"] < 1: | ||||
|             temp_days = 1 | ||||
|         else: | ||||
|             temp_days = dur["tm_mday"] | ||||
|         days = temp_days + tid.tm_mday + carry | ||||
|         while True: | ||||
|             if days < 1: | ||||
|                 pass | ||||
|             elif days > maximum_day_in_month_for(year, month): | ||||
|                 days = days - maximum_day_in_month_for(year, month) | ||||
|                 carry = 1 | ||||
|             else: | ||||
|                 break | ||||
|             temp = month + carry | ||||
|             month = modulo(temp, 1, 13) | ||||
|             year = year + f_quotient(temp, 1, 13) | ||||
|      | ||||
|         return time.localtime(time.mktime((year, month, days, hour, minutes,  | ||||
|                                 secs, 0, 0, -1))) | ||||
|     else: | ||||
|         pass | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| def time_in_a_while(days=0, seconds=0, microseconds=0, milliseconds=0, | ||||
|                     minutes=0, hours=0, weeks=0): | ||||
|     """ | ||||
|     format of timedelta: | ||||
|         timedelta([days[, seconds[, microseconds[, milliseconds[, | ||||
|                     minutes[, hours[, weeks]]]]]]]) | ||||
|     """ | ||||
|     delta = timedelta(days, seconds, microseconds, milliseconds, | ||||
|                       minutes, hours, weeks) | ||||
|     return datetime.utcnow() + delta | ||||
|  | ||||
|  | ||||
| def time_a_while_ago(days=0, seconds=0, microseconds=0, milliseconds=0, | ||||
|                      minutes=0, hours=0, weeks=0): | ||||
|     """ | ||||
|     format of timedelta: | ||||
|         timedelta([days[, seconds[, microseconds[, milliseconds[, | ||||
|                     minutes[, hours[, weeks]]]]]]]) | ||||
|     """ | ||||
|     delta = timedelta(days, seconds, microseconds, milliseconds, | ||||
|                       minutes, hours, weeks) | ||||
|     return datetime.utcnow() - delta | ||||
|  | ||||
|  | ||||
| def in_a_while(days=0, seconds=0, microseconds=0, milliseconds=0, | ||||
|                minutes=0, hours=0, weeks=0, format=TIME_FORMAT): | ||||
|     """ | ||||
|     format of timedelta: | ||||
|         timedelta([days[, seconds[, microseconds[, milliseconds[, | ||||
|                     minutes[, hours[, weeks]]]]]]]) | ||||
|     """ | ||||
|     if format is None: | ||||
|         format = TIME_FORMAT | ||||
|          | ||||
|     return time_in_a_while(days, seconds, microseconds, milliseconds, | ||||
|                            minutes, hours, weeks).strftime(format) | ||||
|  | ||||
|  | ||||
| def a_while_ago(days=0, seconds=0, microseconds=0, milliseconds=0, | ||||
|                 minutes=0, hours=0, weeks=0, format=TIME_FORMAT): | ||||
|     return time_a_while_ago(days, seconds, microseconds, milliseconds, | ||||
|                             minutes, hours, weeks).strftime(format) | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| def shift_time(dtime, shift): | ||||
|     """ Adds/deletes an integer amount of seconds from a datetime specification | ||||
|  | ||||
|     :param dtime: The datatime specification | ||||
|     :param shift: The wanted time shift (+/-) | ||||
|     :return: A shifted datatime specification | ||||
|     """ | ||||
|     return dtime + timedelta(seconds=shift) | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| def str_to_time(timestr): | ||||
|     if not timestr: | ||||
|         return 0 | ||||
|     try: | ||||
|         then = time.strptime(timestr, TIME_FORMAT) | ||||
|     except Exception: # assume it's a format problem | ||||
|         try: | ||||
|             elem = TIME_FORMAT_WITH_FRAGMENT.match(timestr) | ||||
|         except Exception, exc: | ||||
|             print >> sys.stderr, "Exception: %s on %s" % (exc, timestr) | ||||
|             raise | ||||
|         then = time.strptime(elem.groups()[0]+"Z", TIME_FORMAT) | ||||
|  | ||||
|     return time.gmtime(calendar.timegm(then)) | ||||
|  | ||||
|  | ||||
| def instant(format=TIME_FORMAT): | ||||
|     return time.strftime(format, time.gmtime()) | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| def utc_now(): | ||||
|     return calendar.timegm(time.gmtime()) | ||||
|  | ||||
| # --------------------------------------------------------------------------- | ||||
|  | ||||
| def before(point): | ||||
|     """ True if point datetime specification is before now """ | ||||
|     if not point: | ||||
|         return True | ||||
|  | ||||
|     if isinstance(point, basestring): | ||||
|         point = str_to_time(point) | ||||
|     elif isinstance(point, int): | ||||
|         point = time.gmtime(point) | ||||
|  | ||||
|     return time.gmtime() < point | ||||
|  | ||||
|  | ||||
| def after(point): | ||||
|     """ True if point datetime specification is equal or after now """ | ||||
|     if not point: | ||||
|         return True | ||||
|     else: | ||||
|         return not before(point) | ||||
|  | ||||
|  | ||||
| not_before = after | ||||
|  | ||||
| # 'not_on_or_after' is just an obscure name for 'before' | ||||
| not_on_or_after = before | ||||
|  | ||||
| # a point is valid if it is now or sometime in the future, in other words, | ||||
| # if it is not before now | ||||
| valid = before | ||||
|  | ||||
|  | ||||
| def later_than(then, that): | ||||
|     """ True if then is later or equal to that """ | ||||
|     if isinstance(then, basestring): | ||||
|         then = str_to_time(then) | ||||
|     elif isinstance(then, int): | ||||
|         then = time.gmtime(then) | ||||
|  | ||||
|     if isinstance(that, basestring): | ||||
|         that = str_to_time(that) | ||||
|     elif isinstance(that, int): | ||||
|         that = time.gmtime(that) | ||||
|  | ||||
|     return then >= that | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Roland Hedberg
					Roland Hedberg