Pyramid Starter project
+Welcome to demo_pyramid, a Pyramid application generated by
Cookiecutter.
From a42554a640abb4cc57acb988a83444a616c1cba8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pekka=20P=C3=B6yry?= !(t=8uUk?VW*nFi3j;vW|
zxKM;M)HFBQDik}!miY#WErQKTN!Q*z?wcS*9tmabvs|u;E>15Q2dUyGN-b@LD@g*q
zxa4N5vkh>HdiAekp$y
z1&u*-B!%bl&r%mubHA
m K3X&*R)ku>#Sn8&-cW
zK2
| Name | Values | + + + {% for attr in attributes %} +
|---|---|
| {{ attr.0 }} | +
|
Welcome to demo_pyramid, a Pyramid application generated by
Cookiecutter.
| Name | Values | + + + {% for attr in attributes %} +
|---|---|
| {{ attr.0 }} | +
|
+ No content
+ {% endblock content %} +
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64encode
from urllib import urlencode, quote
diff --git a/docs/saml2/_modules/saml2/authn_request.html b/docs/saml2/_modules/saml2/authn_request.html
index 674eae42..207ee7a6 100644
--- a/docs/saml2/_modules/saml2/authn_request.html
+++ b/docs/saml2/_modules/saml2/authn_request.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.authn_request
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64encode
from datetime import datetime
diff --git a/docs/saml2/_modules/saml2/constants.html b/docs/saml2/_modules/saml2/constants.html
index 3b098931..306ec1b3 100644
--- a/docs/saml2/_modules/saml2/constants.html
+++ b/docs/saml2/_modules/saml2/constants.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.constants
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
[docs]class OneLogin_Saml2_Constants:
diff --git a/docs/saml2/_modules/saml2/errors.html b/docs/saml2/_modules/saml2/errors.html
index e33ebb39..ef958f31 100644
--- a/docs/saml2/_modules/saml2/errors.html
+++ b/docs/saml2/_modules/saml2/errors.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.errors
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
[docs]class OneLogin_Saml2_Error(Exception):
diff --git a/docs/saml2/_modules/saml2/logout_request.html b/docs/saml2/_modules/saml2/logout_request.html
index b6cddbc1..1b690dd4 100644
--- a/docs/saml2/_modules/saml2/logout_request.html
+++ b/docs/saml2/_modules/saml2/logout_request.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.logout_request
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64decode
from datetime import datetime
diff --git a/docs/saml2/_modules/saml2/logout_response.html b/docs/saml2/_modules/saml2/logout_response.html
index dc6037d1..3ea3bbf7 100644
--- a/docs/saml2/_modules/saml2/logout_response.html
+++ b/docs/saml2/_modules/saml2/logout_response.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.logout_response
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64decode
from datetime import datetime
diff --git a/docs/saml2/_modules/saml2/metadata.html b/docs/saml2/_modules/saml2/metadata.html
index 055db317..7b0a1be5 100644
--- a/docs/saml2/_modules/saml2/metadata.html
+++ b/docs/saml2/_modules/saml2/metadata.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.metadata
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from time import gmtime, strftime
from datetime import datetime
diff --git a/docs/saml2/_modules/saml2/response.html b/docs/saml2/_modules/saml2/response.html
index 219be1b8..d3fee4e3 100644
--- a/docs/saml2/_modules/saml2/response.html
+++ b/docs/saml2/_modules/saml2/response.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.response
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64decode
from copy import deepcopy
diff --git a/docs/saml2/_modules/saml2/settings.html b/docs/saml2/_modules/saml2/settings.html
index c18ebdf9..42800b51 100644
--- a/docs/saml2/_modules/saml2/settings.html
+++ b/docs/saml2/_modules/saml2/settings.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.settings
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from datetime import datetime
import json
diff --git a/docs/saml2/_modules/saml2/utils.html b/docs/saml2/_modules/saml2/utils.html
index 726ba9dd..613d2a09 100644
--- a/docs/saml2/_modules/saml2/utils.html
+++ b/docs/saml2/_modules/saml2/utils.html
@@ -51,8 +51,8 @@ Navigation
Source code for saml2.utils
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import base64
from datetime import datetime
diff --git a/setup.py b/setup.py
index 2182ad9e..ef9ab63f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,8 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from setuptools import setup
diff --git a/src/onelogin/__init__.py b/src/onelogin/__init__.py
index 110bc9df..ba664a65 100644
--- a/src/onelogin/__init__.py
+++ b/src/onelogin/__init__.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
"""
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Add SAML support to your Python softwares using this library.
Forget those complicated libraries and use that open source
diff --git a/src/onelogin/saml2/__init__.py b/src/onelogin/saml2/__init__.py
index 110bc9df..ba664a65 100644
--- a/src/onelogin/saml2/__init__.py
+++ b/src/onelogin/saml2/__init__.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
"""
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Add SAML support to your Python softwares using this library.
Forget those complicated libraries and use that open source
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 25f3e5b9..df732701 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Auth class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Main class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index ee6e1d2b..2a1eda88 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Authn_Request class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
AuthNRequest class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/compat.py b/src/onelogin/saml2/compat.py
index ce0296ea..13fe5a6f 100644
--- a/src/onelogin/saml2/compat.py
+++ b/src/onelogin/saml2/compat.py
@@ -2,8 +2,8 @@
""" py3 compatibility class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
"""
diff --git a/src/onelogin/saml2/constants.py b/src/onelogin/saml2/constants.py
index a570b884..edc765bf 100644
--- a/src/onelogin/saml2/constants.py
+++ b/src/onelogin/saml2/constants.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Constants class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Constants class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/errors.py b/src/onelogin/saml2/errors.py
index 22f2a926..140b1ed3 100644
--- a/src/onelogin/saml2/errors.py
+++ b/src/onelogin/saml2/errors.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Error class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Error class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 7fc11958..093cf92f 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
""" OneLogin_Saml2_IdPMetadataParser class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Metadata class of OneLogin's Python Toolkit.
"""
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index 2ca540c0..152765e4 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Logout_Request class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Logout Request class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index e568d6e0..75dc089c 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Logout_Response class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Logout Response class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py
index e66e542d..9d58e5b7 100644
--- a/src/onelogin/saml2/metadata.py
+++ b/src/onelogin/saml2/metadata.py
@@ -2,8 +2,8 @@
""" OneLoginSaml2Metadata class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Metadata class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 1177bc31..fe7b9f71 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Response class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
SAML Response class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index f38343c1..4dd6b220 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Settings class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Setting class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index ce226cbb..07dfe9d4 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Utils class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Auxiliary class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/xml_templates.py b/src/onelogin/saml2/xml_templates.py
index 6be5c536..99025546 100644
--- a/src/onelogin/saml2/xml_templates.py
+++ b/src/onelogin/saml2/xml_templates.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_Auth class
-Copyright (c) 2014, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Main class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/xml_utils.py b/src/onelogin/saml2/xml_utils.py
index 4d9937de..03d826a6 100644
--- a/src/onelogin/saml2/xml_utils.py
+++ b/src/onelogin/saml2/xml_utils.py
@@ -2,8 +2,8 @@
""" OneLogin_Saml2_XML class
-Copyright (c) 2015, OneLogin, Inc.
-All rights reserved.
+Copyright (c) 2010-2018 OneLogin, Inc.
+MIT License
Auxiliary class of OneLogin's Python Toolkit.
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index d851f126..87730a20 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64decode, b64encode
import json
diff --git a/tests/src/OneLogin/saml2_tests/authn_request_test.py b/tests/src/OneLogin/saml2_tests/authn_request_test.py
index abbded20..c0dcbf2c 100644
--- a/tests/src/OneLogin/saml2_tests/authn_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/authn_request_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import json
from os.path import dirname, join, exists
diff --git a/tests/src/OneLogin/saml2_tests/error_test.py b/tests/src/OneLogin/saml2_tests/error_test.py
index 8758f736..cd7546b7 100644
--- a/tests/src/OneLogin/saml2_tests/error_test.py
+++ b/tests/src/OneLogin/saml2_tests/error_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import unittest
from onelogin.saml2.errors import OneLogin_Saml2_Error
diff --git a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
index f919e1f2..952f9b18 100644
--- a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
+++ b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
try:
from urllib.error import URLError
diff --git a/tests/src/OneLogin/saml2_tests/logout_request_test.py b/tests/src/OneLogin/saml2_tests/logout_request_test.py
index 843ccb7b..d606ca89 100644
--- a/tests/src/OneLogin/saml2_tests/logout_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_request_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import json
from os.path import dirname, join, exists
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index f273f6fb..eda0b9f3 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import json
from os.path import dirname, join, exists
diff --git a/tests/src/OneLogin/saml2_tests/metadata_test.py b/tests/src/OneLogin/saml2_tests/metadata_test.py
index edee2321..2c926f8a 100644
--- a/tests/src/OneLogin/saml2_tests/metadata_test.py
+++ b/tests/src/OneLogin/saml2_tests/metadata_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import json
from os.path import dirname, join, exists
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index bd9a4280..0f33c0a5 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64decode
from lxml import etree
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index f33ec10e..75031009 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import json
from os.path import dirname, join, exists, sep
diff --git a/tests/src/OneLogin/saml2_tests/signed_response_test.py b/tests/src/OneLogin/saml2_tests/signed_response_test.py
index ec493014..45afb505 100644
--- a/tests/src/OneLogin/saml2_tests/signed_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/signed_response_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import json
from os.path import dirname, join, exists
diff --git a/tests/src/OneLogin/saml2_tests/utils_test.py b/tests/src/OneLogin/saml2_tests/utils_test.py
index a1d7817e..366a125b 100644
--- a/tests/src/OneLogin/saml2_tests/utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/utils_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
from base64 import b64decode
import json
diff --git a/tests/src/OneLogin/saml2_tests/xml_utils_test.py b/tests/src/OneLogin/saml2_tests/xml_utils_test.py
index 7043b4e4..507575f1 100644
--- a/tests/src/OneLogin/saml2_tests/xml_utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/xml_utils_test.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2014, OneLogin, Inc.
-# All rights reserved.
+# Copyright (c) 2010-2018 OneLogin, Inc.
+# MIT License
import json
import unittest
From a4e998358113ef496d2cd1356800a97641c7e5c2 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 23 Apr 2018 19:22:05 +0200
Subject: [PATCH 083/331] Update coveralls and coverage dependency version
---
setup.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index ef9ab63f..76ff881d 100644
--- a/setup.py
+++ b/setup.py
@@ -42,12 +42,12 @@
dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'],
extras_require={
'test': (
- 'coverage==3.7.1',
+ 'coverage>=3.6',
'freezegun==0.3.5',
'pylint==1.3.1',
'pep8==1.5.7',
'pyflakes==0.8.1',
- 'coveralls==0.4.4',
+ 'coveralls==1.1',
),
},
keywords='saml saml2 xmlsec django flask pyramid python3',
From 73f646cdf8f4ea78f7db4f327a4e18f4c72571fc Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 24 Apr 2018 12:23:42 +0200
Subject: [PATCH 084/331] Fix #94. Add ID to EntityDescriptor before sign it on
add_sign method
---
src/onelogin/saml2/utils.py | 20 +++++++++++++------
.../src/OneLogin/saml2_tests/metadata_test.py | 2 ++
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 07dfe9d4..9daf17d2 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -728,9 +728,7 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
raise Exception('Empty string supplied as input')
elem = OneLogin_Saml2_XML.to_etree(xml)
- xmlsec.enable_debug_trace(debug)
- xmlsec.tree.add_ids(elem, ["ID"])
- # Sign the metadata with our private key.
+
sign_algorithm_transform_map = {
OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1,
OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.Transform.RSA_SHA1,
@@ -746,16 +744,26 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
if len(issuer) > 0:
issuer = issuer[0]
issuer.addnext(signature)
+ elem_to_sign = issuer.getparent()
else:
entity_descriptor = OneLogin_Saml2_XML.query(elem, '//md:EntityDescriptor')
if len(entity_descriptor) > 0:
elem.insert(0, signature)
else:
elem[0].insert(0, signature)
+ elem_to_sign = elem
- elem_id = elem.get('ID', None)
- if elem_id:
- elem_id = '#' + elem_id
+ elem_id = elem_to_sign.get('ID', None)
+ if elem_id is not None:
+ if elem_id:
+ elem_id = '#' + elem_id
+ else:
+ generated_id = generated_id = OneLogin_Saml2_Utils.generate_unique_id()
+ elem_id = '#' + generated_id
+ elem_to_sign.attrib['ID'] = generated_id
+
+ xmlsec.enable_debug_trace(debug)
+ xmlsec.tree.add_ids(elem_to_sign, ["ID"])
digest_algorithm_transform_map = {
OneLogin_Saml2_Constants.SHA1: xmlsec.Transform.SHA1,
diff --git a/tests/src/OneLogin/saml2_tests/metadata_test.py b/tests/src/OneLogin/saml2_tests/metadata_test.py
index 2c926f8a..07dca694 100644
--- a/tests/src/OneLogin/saml2_tests/metadata_test.py
+++ b/tests/src/OneLogin/saml2_tests/metadata_test.py
@@ -208,6 +208,7 @@ def testSignMetadata(self):
self.assertIn('
Date: Tue, 24 Apr 2018 18:27:30 +0200
Subject: [PATCH 085/331] Add test for generated signed_metadata
---
tests/src/OneLogin/saml2_tests/metadata_test.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/src/OneLogin/saml2_tests/metadata_test.py b/tests/src/OneLogin/saml2_tests/metadata_test.py
index 07dca694..8c6acb1b 100644
--- a/tests/src/OneLogin/saml2_tests/metadata_test.py
+++ b/tests/src/OneLogin/saml2_tests/metadata_test.py
@@ -13,6 +13,7 @@
from onelogin.saml2.metadata import OneLogin_Saml2_Metadata
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.constants import OneLogin_Saml2_Constants
+from onelogin.saml2.utils import OneLogin_Saml2_Utils
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML
@@ -205,6 +206,7 @@ def testSignMetadata(self):
cert = self.file_contents(join(cert_path, 'sp.crt'))
signed_metadata = compat.to_string(OneLogin_Saml2_Metadata.sign_metadata(metadata, key, cert))
+ self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(signed_metadata, cert))
self.assertIn('
Date: Wed, 25 Apr 2018 16:00:43 +0200
Subject: [PATCH 086/331] Release 1.4.1
---
changelog.md | 4 ++++
setup.py | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index 71f85885..bed7b450 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,8 @@
# python3-saml changelog
+### 1.4.1 (Apr 25, 2018)
+* Add ID to EntityDescriptor before sign it on add_sign method.
+* Update defusedxml, coveralls and coverage dependencies
+* Update copyright and license reference
### 1.4.0 (Feb 27, 2018)
* Fix vulnerability [CVE-2017-11427](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11427). Process text of nodes properly, ignoring comments
diff --git a/setup.py b/setup.py
index 76ff881d..7b822c93 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.4.0',
+ version='1.4.1',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From 3da2298a2f6d4f8f841b4f3a9ab8eac43466d040 Mon Sep 17 00:00:00 2001
From: Jeff Franklin
Date: Mon, 28 May 2018 18:15:23 -0700
Subject: [PATCH 087/331] Check that the response has all of the AuthnContexts
that we provided in the request.
---
README.md | 4 +-
src/onelogin/saml2/auth.py | 9 +++++
src/onelogin/saml2/errors.py | 1 +
src/onelogin/saml2/response.py | 22 +++++++++++
src/onelogin/saml2/settings.py | 1 +
tests/src/OneLogin/saml2_tests/auth_test.py | 14 +++++++
.../src/OneLogin/saml2_tests/response_test.py | 38 +++++++++++++++++++
7 files changed, 88 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9a3b2ec2..e4505f4f 100644
--- a/README.md
+++ b/README.md
@@ -396,11 +396,13 @@ In addition to the required settings data (idp, sp), extra settings can be defin
// Authentication context.
// Set to false and no AuthContext will be sent in the AuthNRequest,
- // Set true or don't present thi parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
+ // Set true or don't present this parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
// Set an array with the possible auth context values: array ('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'),
"requestedAuthnContext": true,
// Allows the authn comparison parameter to be set, defaults to 'exact' if the setting is not present.
"requestedAuthnContextComparison": "exact",
+ // Set to true to check that the AuthnContext received matches the one requested.
+ "failOnAuthnContextMismatch": false,
// In some environment you will need to set how long the published metadata of the Service Provider gonna be valid.
// is possible to not set the 2 following parameters (or set to null) and default values will be set (2 days, 1 week)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index df732701..44717f08 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -63,6 +63,7 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
self.__last_request_id = None
self.__last_message_id = None
self.__last_assertion_id = None
+ self.__last_authn_contexts = []
self.__last_request = None
self.__last_response = None
self.__last_assertion_not_on_or_after = None
@@ -110,6 +111,7 @@ def process_response(self, request_id=None):
self.__session_expiration = response.get_session_not_on_or_after()
self.__last_message_id = response.get_id()
self.__last_assertion_id = response.get_assertion_id()
+ self.__last_authn_contexts = response.get_authn_contexts()
self.__authenticated = True
self.__last_assertion_not_on_or_after = response.get_assertion_not_on_or_after()
@@ -318,6 +320,13 @@ def get_last_assertion_id(self):
"""
return self.__last_assertion_id
+ def get_last_authn_contexts(self):
+ """
+ :returns: The list of authentication contexts sent in the last SAML resposne.
+ :rtype: list
+ """
+ return self.__last_authn_contexts
+
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True):
"""
Initiates the SSO process.
diff --git a/src/onelogin/saml2/errors.py b/src/onelogin/saml2/errors.py
index 140b1ed3..6faba2e2 100644
--- a/src/onelogin/saml2/errors.py
+++ b/src/onelogin/saml2/errors.py
@@ -109,6 +109,7 @@ class OneLogin_Saml2_ValidationError(Exception):
INVALID_SIGNATURE = 42
WRONG_NUMBER_OF_SIGNATURES = 43
RESPONSE_EXPIRED = 44
+ AUTHN_CONTEXT_MISMATCH = 45
def __init__(self, message, code=0, errors=None):
"""
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index fe7b9f71..4da2e652 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -161,6 +161,18 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_AUTHSTATEMENTS
)
+ # Checks that the response has all of the AuthnContexts that we provided in the request.
+ # Only check if failOnAuthnContextMismatch is true and requestedAuthnContext is set to a list.
+ requested_authn_contexts = security['requestedAuthnContext']
+ if security['failOnAuthnContextMismatch'] and requested_authn_contexts and requested_authn_contexts is not True:
+ authn_contexts = self.get_authn_contexts()
+ unmatched_contexts = set(requested_authn_contexts).difference(authn_contexts)
+ if unmatched_contexts:
+ raise OneLogin_Saml2_ValidationError(
+ 'The AuthnContext "%s" didn\'t include requested context "%s"' % (', '.join(authn_contexts), ', '.join(unmatched_contexts)),
+ OneLogin_Saml2_ValidationError.AUTHN_CONTEXT_MISMATCH
+ )
+
# Checks that there is at least one AttributeStatement if required
attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement')
if security.get('wantAttributeStatement', True) and not attribute_statement_nodes:
@@ -361,6 +373,16 @@ def get_audiences(self):
audience_nodes = self.__query_assertion('/saml:Conditions/saml:AudienceRestriction/saml:Audience')
return [OneLogin_Saml2_XML.element_text(node) for node in audience_nodes if OneLogin_Saml2_XML.element_text(node) is not None]
+ def get_authn_contexts(self):
+ """
+ Gets the authentication contexts
+
+ :returns: The authentication classes for the SAML Response
+ :rtype: list
+ """
+ authn_context_nodes = self.__query_assertion('/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef')
+ return [OneLogin_Saml2_XML.element_text(node) for node in authn_context_nodes]
+
def get_issuers(self):
"""
Gets the issuers (from message and from assertion)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 4dd6b220..003aa0ad 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -310,6 +310,7 @@ def __add_default_values(self):
self.__sp.setdefault('privateKey', '')
self.__security.setdefault('requestedAuthnContext', True)
+ self.__security.setdefault('failOnAuthnContextMismatch', False)
def check_settings(self, settings):
"""
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 87730a20..2a2b557c 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -1231,6 +1231,20 @@ def testGetLastAuthnRequest(self):
)
self.assertIn(expectedFragment, auth.get_last_request_xml())
+ def testGetLastAuthnContexts(self):
+ settings = self.loadSettingsJSON()
+ request_data = self.get_request()
+ message = self.file_contents(
+ join(self.data_path, 'responses', 'valid_response.xml.base64'))
+ del request_data['get_data']
+ request_data['post_data'] = {
+ 'SAMLResponse': message
+ }
+ auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
+
+ auth.process_response()
+ self.assertEqual(auth.get_last_authn_contexts(), ['urn:oasis:names:tc:SAML:2.0:ac:classes:Password'])
+
def testGetLastLogoutRequest(self):
settings = self.loadSettingsJSON()
auth = OneLogin_Saml2_Auth({'http_host': 'localhost', 'script_name': 'thing'}, old_settings=settings)
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 0f33c0a5..a0c61a69 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -932,6 +932,44 @@ def testIsInValidAudience(self):
self.assertFalse(response_2.is_valid(request_data))
self.assertIn('is not a valid audience for this Response', response_2.get_error())
+ def testIsInValidAuthenticationContext(self):
+ """
+ Tests that requestedAuthnContext, when set, is compared against the
+ response AuthnContext, which is what you use for two-factor
+ authentication. Without this check you can get back a valid response
+ that didn't complete the two-factor step.
+ """
+ request_data = self.get_request_data()
+ message = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64'))
+ two_factor_context = 'urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken'
+ password_context = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
+ settings_dict = self.loadSettingsJSON()
+ settings_dict['security']['requestedAuthnContext'] = [two_factor_context]
+ settings_dict['security']['failOnAuthnContextMismatch'] = True
+ settings_dict['strict'] = True
+ settings = OneLogin_Saml2_Settings(settings_dict)
+
+ # check that we catch when the contexts don't match
+ response = OneLogin_Saml2_Response(settings, message)
+ self.assertFalse(response.is_valid(request_data))
+ self.assertIn('The AuthnContext "%s" didn\'t include requested context "%s"' % (password_context, two_factor_context), response.get_error())
+
+ # now drop in the expected AuthnContextClassRef and see that it passes
+ original_message = compat.to_string(OneLogin_Saml2_Utils.b64decode(message))
+ two_factor_message = original_message.replace(password_context, two_factor_context)
+ two_factor_message = OneLogin_Saml2_Utils.b64encode(two_factor_message)
+ response = OneLogin_Saml2_Response(settings, two_factor_message)
+ response.is_valid(request_data)
+ # check that we got as far as destination validation, which comes later
+ self.assertIn('The response was received at', response.get_error())
+
+ # with the default setting, check that we succeed with our original context
+ settings_dict['security']['requestedAuthnContext'] = True
+ settings = OneLogin_Saml2_Settings(settings_dict)
+ response = OneLogin_Saml2_Response(settings, message)
+ response.is_valid(request_data)
+ self.assertIn('The response was received at', response.get_error())
+
def testIsInValidIssuer(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response class
From 2fec2a8270b68f5f902f7986a07661854ac7ca98 Mon Sep 17 00:00:00 2001
From: SamvanLeipsig
Date: Tue, 5 Jun 2018 11:20:20 +0200
Subject: [PATCH 088/331] fix confusing alternative naming of python3-saml in
readme warning
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 9a3b2ec2..4f9408a4 100644
--- a/README.md
+++ b/README.md
@@ -14,11 +14,11 @@ This version supports Python3, There is a separate version that only support Pyt
#### Warning ####
-Update python-saml to 1.4.0, this version includes a fix for the [CVE-2017-11427](https://www.cvedetails.com/cve/CVE-2017-11427/) vulnerability.
+Update python3-saml to 1.4.0, this version includes a fix for the [CVE-2017-11427](https://www.cvedetails.com/cve/CVE-2017-11427/) vulnerability.
This version also changes how the calculate fingerprint method works, and will expect as input a formatted x509 certificate
-Update python-saml3 to 1.2.6 that adds the use defusedxml that will prevent XEE and other attacks based on the abuse of XML. (CVE-2017-9672)
+Update python3-saml to 1.2.6 that adds the use defusedxml that will prevent XEE and other attacks based on the abuse of XML. (CVE-2017-9672)
Update python3-saml to >= 1.2.1, 1.2.0 had a bug on signature validation process (when using wantAssertionsSigned and wantMessagesSigned). [CVE-2016-1000251](https://github.com/distributedweaknessfiling/DWF-Database-Artifacts/blob/master/DWF/2016/1000251/CVE-2016-1000251.json)
From 09b543cdc2e13bebc112ff0338a50cdcc1dd1ae5 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 26 Jul 2018 13:44:50 +0200
Subject: [PATCH 089/331] Adapt renders from Django demo for Django 1.11
version
---
demo-django/demo/views.py | 19 ++++++-------------
1 file changed, 6 insertions(+), 13 deletions(-)
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index a74e6c68..398de41a 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -2,8 +2,7 @@
from django.urls import reverse
from django.http import (HttpResponse, HttpResponseRedirect,
HttpResponseServerError)
-from django.shortcuts import render_to_response
-from django.template import RequestContext
+from django.shortcuts import render
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.settings import OneLogin_Saml2_Settings
@@ -78,13 +77,8 @@ def index(request):
if len(request.session['samlUserdata']) > 0:
attributes = request.session['samlUserdata'].items()
- context = RequestContext(request, {'errors': errors,
- 'not_auth_warn': not_auth_warn,
- 'success_slo': success_slo,
- 'attributes': attributes,
- 'paint_logout': paint_logout}).flatten()
- return render_to_response('index.html',
- context=context)
+ return render(request, 'index.html', {'errors': errors, 'not_auth_warn': not_auth_warn, 'success_slo': success_slo,
+ 'attributes': attributes, 'paint_logout': paint_logout})
def attrs(request):
@@ -95,10 +89,9 @@ def attrs(request):
paint_logout = True
if len(request.session['samlUserdata']) > 0:
attributes = request.session['samlUserdata'].items()
-
- return render_to_response('attrs.html',
- context=RequestContext(request, {'paint_logout': paint_logout,
- 'attributes': attributes}).flatten())
+ return render(request, 'attrs.html',
+ {'paint_logout': paint_logout,
+ 'attributes': attributes})
def metadata(request):
From 20e839d758d4974254b049e790dce34dd82ecd3b Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 26 Jul 2018 14:16:52 +0200
Subject: [PATCH 090/331] Update pylint dependency to 1.9.1
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 7b822c93..ae68aad7 100644
--- a/setup.py
+++ b/setup.py
@@ -44,7 +44,7 @@
'test': (
'coverage>=3.6',
'freezegun==0.3.5',
- 'pylint==1.3.1',
+ 'pylint==1.9.1',
'pep8==1.5.7',
'pyflakes==0.8.1',
'coveralls==1.1',
From 4a30dc50c656ea8eee9d43d781715c2b2786b04b Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sun, 5 Aug 2018 21:13:48 +0200
Subject: [PATCH 091/331] Discourage the use of the fingerprint on production
environments
---
README.md | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 4f9408a4..598d5c23 100644
--- a/README.md
+++ b/README.md
@@ -120,6 +120,8 @@ Security warning
In production, the **strict** parameter MUST be set as **"true"**. Otherwise
your environment is not secure and will be exposed to attacks.
+In production also we highly recommend to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment.
+
Getting started
---------------
@@ -301,8 +303,11 @@ This is the settings.json file:
// Public x509 certificate of the IdP
"x509cert": ""
/*
- * Instead of using the whole x509cert you can use a fingerprint in
- * order to validate a SAMLResponse.
+ * Instead of using the whole x509cert you can use a fingerprint in order to
+ * validate a SAMLResponse (but you still need the x509cert to validate LogoutRequest and LogoutResponse using the HTTP-Redirect binding).
+ * But take in mind that the fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass,
+ * that why we don't recommend it use for production environments.
+ *
* (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
* or add for example the -sha256 , -sha384 or -sha512 parameter)
*
From 547a25d3e530d7b2c8c744d08def8189dfb1be57 Mon Sep 17 00:00:00 2001
From: Nick Barrett
Date: Tue, 14 Aug 2018 15:45:15 +0100
Subject: [PATCH 092/331] Handle nested `NameID` children inside attributes.
---
src/onelogin/saml2/response.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index fe7b9f71..9d07bf9d 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -531,6 +531,15 @@ def get_attributes(self):
if attr_text:
values.append(attr_text)
+ # Parse any nested NameID children
+ for nameid in attr.iterchildren('{%s}NameID' % OneLogin_Saml2_Constants.NSMAP['saml']):
+ values.append({
+ 'NameID': {
+ 'Format': nameid.get('Format'),
+ 'NameQualifier': nameid.get('NameQualifier'),
+ 'value': nameid.text
+ }
+ })
attributes[attr_name] = values
return attributes
From e7c9f75c87611464c27e13b510e24cb713e28d33 Mon Sep 17 00:00:00 2001
From: Nick Barrett
Date: Tue, 14 Aug 2018 15:45:27 +0100
Subject: [PATCH 093/331] Add tests for the nested `NameID` attributes.
---
...ponse_with_nested_nameid_values.xml.base64 | 71 +++++++++++++++++++
.../src/OneLogin/saml2_tests/response_test.py | 20 ++++++
2 files changed, 91 insertions(+)
create mode 100644 tests/data/responses/response_with_nested_nameid_values.xml.base64
diff --git a/tests/data/responses/response_with_nested_nameid_values.xml.base64 b/tests/data/responses/response_with_nested_nameid_values.xml.base64
new file mode 100644
index 00000000..3092c3cf
--- /dev/null
+++ b/tests/data/responses/response_with_nested_nameid_values.xml.base64
@@ -0,0 +1,71 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDph
+c3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9j
+b2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50
+PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4KICA8c2Ft
+bHA6U3RhdHVzPgogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0
+YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPgogIDxzYW1sOkFzc2Vy
+dGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhz
+aT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIu
+MCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MiIgSXNzdWVJbnN0
+YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+CiAgICA8c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAu
+b25lbG9naW4uY29tL3NhbWwvbWV0YWRhdGEvMTM1OTA8L3NhbWw6SXNzdWVyPgogICAgPGRzOlNp
+Z25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAg
+ICAgIDxkczpTaWduZWRJbmZvPgogICAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFs
+Z29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CiAgICAg
+ICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAv
+MDkveG1sZHNpZyNyc2Etc2hhMSIvPgogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2
+NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4KICAgICAgICAgIDxkczpUcmFuc2Zv
+cm1zPgogICAgICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5v
+cmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KICAgICAgICAgICAgPGRz
+OlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1j
+MTRuIyIvPgogICAgICAgICAgPC9kczpUcmFuc2Zvcm1zPgogICAgICAgICAgPGRzOkRpZ2VzdE1l
+dGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+
+CiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwv
+ZHM6RGlnZXN0VmFsdWU+CiAgICAgICAgPC9kczpSZWZlcmVuY2U+CiAgICAgIDwvZHM6U2lnbmVk
+SW5mbz4KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9j
+QTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRit6
+eEd5ZWw1Z09vd21veVhKcitBT3Ira1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lF
+YXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPgogICAg
+ICA8ZHM6S2V5SW5mbz4KICAgICAgICA8ZHM6WDUwOURhdGE+CiAgICAgICAgICA8ZHM6WDUwOUNl
+cnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxW
+VE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVw
+WTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5h
+VzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1B
+a0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZO
+aGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3
+Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JB
+T2pTdTFmalB5OGQ1dzRReUwxK3pkNGhJdzFNa2tmZjRXWS9UTEc4T1prVTVZVFNXbW1IUEQ1a3ZZ
+SDV1b1hTLzZxUTgxcVhwUjJ3VjhDVG93WkpVTGcwOWRkUmRSbjhRc3FqMUZ5T0M1c2xFM3kyYloy
+b0Z1YTcyb2YvNDlmcHVqbkZUNktuUTYxQ0JNcWxEb1RRcU9UNjJ2R0o4blA2TVpXdkE2c3hxdWQ1
+QWdNQkFBRXdBd1lCQUFNQkFBPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT4KICAgICAgICA8L2RzOlg1
+MDlEYXRhPgogICAgICA8L2RzOktleUluZm8+CiAgICA8L2RzOlNpZ25hdHVyZT4KICAgIDxzYW1s
+OlN1YmplY3Q+CiAgICAgIDxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpT
+QU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c3VwcG9ydEBvbmVsb2dpbi5jb208
+L3NhbWw6TmFtZUlEPgogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJu
+Om9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+CiAgICAgICAgPHNhbWw6U3ViamVj
+dENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDEwLTExLTE4VDIyOjAyOjM3WiIgUmVj
+aXBpZW50PSJ7cmVjaXBpZW50fSIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPgogICAgPC9z
+YW1sOlN1YmplY3Q+CiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQy
+MTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPgogICAgICA8c2Ft
+bDpBdWRpZW5jZVJlc3RyaWN0aW9uPgogICAgICAgIDxzYW1sOkF1ZGllbmNlPnthdWRpZW5jZX08
+L3NhbWw6QXVkaWVuY2U+CiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPgogICAgPC9z
+YW1sOkNvbmRpdGlvbnM+CiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIw
+MTAtMTEtMThUMjE6NTc6MzdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDEwLTExLTE5VDIxOjU3
+OjM3WiIgU2Vzc2lvbkluZGV4PSJfNTMxYzMyZDI4M2JkZmY3ZTA0ZTQ4N2JjZGJjNGRkOGQiPgog
+ICAgICA8c2FtbDpBdXRobkNvbnRleHQ+CiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NS
+ZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6
+QXV0aG5Db250ZXh0Q2xhc3NSZWY+CiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+CiAgICA8L3Nh
+bWw6QXV0aG5TdGF0ZW1lbnQ+CiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+CiAgICAgIDxz
+YW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiPgogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHht
+bG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRw
+Oi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmlu
+ZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4KICAg
+ICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImFub3RoZXJfdmFsdWUiPgogICAgICAgIDxzYW1sOkF0
+dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIg
+eG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNp
+OnR5cGU9InhzOnN0cmluZyI+CiAgICAgICAgICAgIDxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpv
+YXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiIE5hbWVRdWFs
+aWZpZXI9Imh0dHBzOi8vaWRwSUQiIFNQTmFtZVF1YWxpZmllcj0iaHR0cHM6Ly9zcElEIj52YWx1
+ZTwvc2FtbDpOYW1lSUQ+CiAgICAgICAgPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPgogICAgICA8L3Nh
+bWw6QXR0cmlidXRlPgogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4KICA8L3NhbWw6QXNz
+ZXJ0aW9uPgo8L3NhbWxwOlJlc3BvbnNlPgo=
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 0f33c0a5..29793b86 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -610,6 +610,26 @@ def testGetAttributes(self):
response_3 = OneLogin_Saml2_Response(settings, xml_3)
self.assertEqual({}, response_3.get_attributes())
+ def testGetNestedNameIDAttributes(self):
+ """
+ Tests the getAttributes method of the OneLogin_Saml2_Response with nested
+ nameID data
+ """
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+ xml = self.file_contents(join(self.data_path, 'responses', 'response_with_nested_nameid_values.xml.base64'))
+ response = OneLogin_Saml2_Response(settings, xml)
+ expected_attributes = {
+ 'uid': ['demo'],
+ 'another_value': [{
+ 'NameID': {
+ 'Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ 'NameQualifier': 'https://idpID',
+ 'value': 'value'
+ }
+ }]
+ }
+ self.assertEqual(expected_attributes, response.get_attributes())
+
def testOnlyRetrieveAssertionWithIDThatMatchesSignatureReference(self):
"""
Tests the get_nameid method of the OneLogin_Saml2_Response
From 5001ece419526019deab654b19b0cdc37d88c861 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 27 Sep 2018 16:55:04 +0200
Subject: [PATCH 094/331] Fix DSA constant
---
src/onelogin/saml2/constants.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/constants.py b/src/onelogin/saml2/constants.py
index edc765bf..7b3f0085 100644
--- a/src/onelogin/saml2/constants.py
+++ b/src/onelogin/saml2/constants.py
@@ -100,7 +100,7 @@ class OneLogin_Saml2_Constants(object):
SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
- DSA_SHA1 = 'http://www.w3.org/2000/09/xmld/sig#dsa-sha1'
+ DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'
RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
From a246326f3b38515ff4fb540893b1dd75350c5e01 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 17 Oct 2018 01:21:06 +0200
Subject: [PATCH 095/331] If debug enable, print reason for the SAMLResponse
invalidation
---
demo-django/demo/views.py | 7 ++++++-
demo-django/templates/index.html | 3 +++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 398de41a..648e5e0a 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -33,6 +33,7 @@ def index(request):
req = prepare_django_request(request)
auth = init_saml_auth(req)
errors = []
+ error_reason = None
not_auth_warn = False
success_slo = False
attributes = False
@@ -56,12 +57,16 @@ def index(request):
auth.process_response()
errors = auth.get_errors()
not_auth_warn = not auth.is_authenticated()
+
if not errors:
request.session['samlUserdata'] = auth.get_attributes()
request.session['samlNameId'] = auth.get_nameid()
request.session['samlSessionIndex'] = auth.get_session_index()
if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))
+ else:
+ if auth.get_settings().is_debug_active():
+ error_reason = auth.get_last_error_reason()
elif 'sls' in req['get_data']:
dscb = lambda: request.session.flush()
url = auth.process_slo(delete_session_cb=dscb)
@@ -77,7 +82,7 @@ def index(request):
if len(request.session['samlUserdata']) > 0:
attributes = request.session['samlUserdata'].items()
- return render(request, 'index.html', {'errors': errors, 'not_auth_warn': not_auth_warn, 'success_slo': success_slo,
+ return render(request, 'index.html', {'errors': errors, 'error_reason': error_reason, 'not_auth_warn': not_auth_warn, 'success_slo': success_slo,
'attributes': attributes, 'paint_logout': paint_logout})
diff --git a/demo-django/templates/index.html b/demo-django/templates/index.html
index f7d51101..87f2f08b 100644
--- a/demo-django/templates/index.html
+++ b/demo-django/templates/index.html
@@ -10,6 +10,9 @@
{{err}}
{% endfor %}
+ {% if error_reason %}
+ Reason: {{error_reason}}
+ {% endif %}
{% endif %}
From 69c9b13ed0c6a37f9d2258b44fb927a208e46325 Mon Sep 17 00:00:00 2001
From: Matthew Shin
Date: Thu, 18 Oct 2018 18:53:24 -0500
Subject: [PATCH 096/331] Add expected/received in WRONG_ISSUER error
---
src/onelogin/saml2/logout_request.py | 6 +++++-
src/onelogin/saml2/logout_response.py | 6 +++++-
src/onelogin/saml2/response.py | 6 +++++-
tests/src/OneLogin/saml2_tests/logout_response_test.py | 2 +-
4 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index 152765e4..9dfb0c3e 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -322,7 +322,11 @@ def is_valid(self, request_data, raise_exceptions=False):
issuer = OneLogin_Saml2_Logout_Request.get_issuer(root)
if issuer is not None and issuer != idp_entity_id:
raise OneLogin_Saml2_ValidationError(
- 'Invalid issuer in the Logout Request',
+ 'Invalid issuer in the Logout Request (expected %(idpEntityId)s, got %(issuer)s)' %
+ {
+ 'idpEntityId': idp_entity_id,
+ 'issuer': issuer
+ },
OneLogin_Saml2_ValidationError.WRONG_ISSUER
)
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 75dc089c..006d4e6b 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -106,7 +106,11 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
issuer = self.get_issuer()
if issuer is not None and issuer != idp_entity_id:
raise OneLogin_Saml2_ValidationError(
- 'Invalid issuer in the Logout Request',
+ 'Invalid issuer in the Logout Response (expected %(idpEntityId)s, got %(issuer)s)' %
+ {
+ 'idpEntityId': idp_entity_id,
+ 'issuer': issuer
+ },
OneLogin_Saml2_ValidationError.WRONG_ISSUER
)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 7a12ccda..c44b5a46 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -218,7 +218,11 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
for issuer in issuers:
if issuer is None or issuer != idp_entity_id:
raise OneLogin_Saml2_ValidationError(
- 'Invalid issuer in the Assertion/Response',
+ 'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)' %
+ {
+ 'idpEntityId': idp_entity_id,
+ 'issuer': issuer
+ },
OneLogin_Saml2_ValidationError.WRONG_ISSUER
)
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index eda0b9f3..5a7994c0 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -208,7 +208,7 @@ def testIsInValidIssuer(self):
settings.set_strict(True)
response_2 = OneLogin_Saml2_Logout_Response(settings, message)
- with self.assertRaisesRegex(Exception, 'Invalid issuer in the Logout Request'):
+ with self.assertRaisesRegex(Exception, 'Invalid issuer in the Logout Response'):
response_2.is_valid(request_data, raise_exceptions=True)
def testIsInValidDestination(self):
From 01166a135ac5c50806c57ce9d1819ee1d187b689 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 2 Nov 2018 12:43:06 +0100
Subject: [PATCH 097/331] Fix #116. Don't require compression on LogoutResponse
messages by relaxing the decode_base64_and_inflate call
---
src/onelogin/saml2/logout_response.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 006d4e6b..67a72f35 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -38,7 +38,7 @@ def __init__(self, settings, response=None):
self.id = None
if response is not None:
- self.__logout_response = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(response))
+ self.__logout_response = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(response, ignore_zip=True))
self.document = OneLogin_Saml2_XML.to_etree(self.__logout_response)
self.id = self.document.get('ID', None)
From 3ae8d01370e86763056086fac30660f0fb00ce57 Mon Sep 17 00:00:00 2001
From: Yuqing
Date: Tue, 13 Nov 2018 16:09:03 +0100
Subject: [PATCH 098/331] Fixed UNEXPECTED_SIGNED_ELEMENT should be
UNEXPECTED_SIGNED_ELEMENTS
---
src/onelogin/saml2/response.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index c44b5a46..dd10771f 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -652,7 +652,7 @@ def process_signed_elements(self):
if not self.validate_signed_elements(signed_elements, raise_exceptions=True):
raise OneLogin_Saml2_ValidationError(
'Found an unexpected Signature Element. SAML Response rejected',
- OneLogin_Saml2_ValidationError.UNEXPECTED_SIGNED_ELEMENT
+ OneLogin_Saml2_ValidationError.UNEXPECTED_SIGNED_ELEMENTS
)
return signed_elements
From f026d9bbdeee1d08863a52de55f2c60529841898 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sat, 15 Dec 2018 23:28:37 +0100
Subject: [PATCH 099/331] Update Shibboleth XML URL
---
.../src/OneLogin/saml2_tests/idp_metadata_parser_test.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
index 952f9b18..6e1aaa1d 100644
--- a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
+++ b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
@@ -51,7 +51,7 @@ def testGetMetadata(self):
data = OneLogin_Saml2_IdPMetadataParser.get_metadata('http://google.es')
try:
- data = OneLogin_Saml2_IdPMetadataParser.get_metadata('https://www.testshib.org/metadata/testshib-providers.xml')
+ data = OneLogin_Saml2_IdPMetadataParser.get_metadata('https://idp.testshib.org/idp/shibboleth')
self.assertTrue(data is not None and data is not {})
except URLError:
pass
@@ -64,7 +64,7 @@ def testParseRemote(self):
data = OneLogin_Saml2_IdPMetadataParser.parse_remote('http://google.es')
try:
- data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://www.testshib.org/metadata/testshib-providers.xml')
+ data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://idp.testshib.org/idp/shibboleth')
except URLError:
xml = self.file_contents(join(self.data_path, 'metadata', 'testshib-providers.xml'))
data = OneLogin_Saml2_IdPMetadataParser.parse(xml)
@@ -148,7 +148,7 @@ def test_parse_testshib_required_binding_sso_redirect(self):
"""
try:
xmldoc = OneLogin_Saml2_IdPMetadataParser.get_metadata(
- 'https://www.testshib.org/metadata/testshib-providers.xml')
+ 'https://idp.testshib.org/idp/shibboleth')
except URLError:
xmldoc = self.file_contents(join(self.data_path, 'metadata', 'testshib-providers.xml'))
@@ -186,7 +186,7 @@ def test_parse_testshib_required_binding_sso_post(self):
"""
try:
xmldoc = OneLogin_Saml2_IdPMetadataParser.get_metadata(
- 'https://www.testshib.org/metadata/testshib-providers.xml')
+ 'https://idp.testshib.org/idp/shibboleth')
except URLError:
xmldoc = self.file_contents(join(self.data_path, 'metadata', 'testshib-providers.xml'))
From ca5c0829c150af972b64d3e35f99eaf290dd1068 Mon Sep 17 00:00:00 2001
From: cclauss
Date: Fri, 18 Jan 2019 10:40:49 +0100
Subject: [PATCH 100/331] Py3.7 & flake8 is a superset of pep8 and pyflakes
---
.travis.yml | 8 ++++++--
demo-django/demo/settings.py | 3 +--
demo-django/demo/views.py | 2 +-
demo-django/demo/wsgi.py | 2 +-
setup.cfg | 5 +++++
setup.py | 11 +++++------
src/onelogin/saml2/idp_metadata_parser.py | 2 +-
src/onelogin/saml2/utils.py | 4 ++--
tests/pep8.rc | 3 ---
tests/src/OneLogin/saml2_tests/authn_request_test.py | 4 ++--
.../OneLogin/saml2_tests/idp_metadata_parser_test.py | 2 +-
tests/src/OneLogin/saml2_tests/logout_request_test.py | 6 +++---
.../src/OneLogin/saml2_tests/logout_response_test.py | 2 +-
13 files changed, 29 insertions(+), 25 deletions(-)
delete mode 100644 tests/pep8.rc
diff --git a/.travis.yml b/.travis.yml
index 39b74bb1..8023585e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,11 @@ python:
- '3.5'
- '3.6'
+matrix:
+ include:
+ - python: '3.7'
+ dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
+
install:
- sudo apt-get update -qq
- sudo apt-get install -qq swig python-dev libxml2-dev libxmlsec1-dev
@@ -15,7 +20,6 @@ script:
- 'coverage run --source=src/onelogin/saml2 --rcfile=tests/coverage.rc setup.py test'
- 'coverage report -m --rcfile=tests/coverage.rc'
# - 'pylint src/onelogin/saml2 --rcfile=tests/pylint.rc'
- - 'pep8 tests/src/OneLogin/saml2_tests/*.py demo-flask/*.py demo-django/*.py src/onelogin/saml2/*.py --config=tests/pep8.rc'
- - 'pyflakes src/onelogin/saml2 demo-django demo-flask tests/src/OneLogin/saml2_tests'
+ - 'flake8 .'
after_success: 'coveralls'
diff --git a/demo-django/demo/settings.py b/demo-django/demo/settings.py
index fcc3cea2..17bbf6d4 100644
--- a/demo-django/demo/settings.py
+++ b/demo-django/demo/settings.py
@@ -80,8 +80,7 @@
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR, 'templates')]
- ,
+ 'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'debug': True,
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 648e5e0a..535b42af 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -83,7 +83,7 @@ def index(request):
attributes = request.session['samlUserdata'].items()
return render(request, 'index.html', {'errors': errors, 'error_reason': error_reason, 'not_auth_warn': not_auth_warn, 'success_slo': success_slo,
- 'attributes': attributes, 'paint_logout': paint_logout})
+ 'attributes': attributes, 'paint_logout': paint_logout})
def attrs(request):
diff --git a/demo-django/demo/wsgi.py b/demo-django/demo/wsgi.py
index bd706154..b1c7db13 100644
--- a/demo-django/demo/wsgi.py
+++ b/demo-django/demo/wsgi.py
@@ -10,5 +10,5 @@
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
-from django.core.wsgi import get_wsgi_application
+from django.core.wsgi import get_wsgi_application # noqa: E402
application = get_wsgi_application()
diff --git a/setup.cfg b/setup.cfg
index e66de0fb..e9d41598 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,7 @@
+[flake8]
+ignore = E731,W504
+max-complexity = 48
+max-line-length = 1900
+
[wheel]
python-tag = py27
diff --git a/setup.py b/setup.py
index ae68aad7..631a8bc2 100644
--- a/setup.py
+++ b/setup.py
@@ -42,12 +42,11 @@
dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'],
extras_require={
'test': (
- 'coverage>=3.6',
- 'freezegun==0.3.5',
- 'pylint==1.9.1',
- 'pep8==1.5.7',
- 'pyflakes==0.8.1',
- 'coveralls==1.1',
+ 'coverage>=4.5.2',
+ 'freezegun==0.3.11',
+ 'pylint==1.9.4',
+ 'flake8==3.6.0',
+ 'coveralls==1.5.1',
),
},
keywords='saml saml2 xmlsec django flask pyramid python3',
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 093cf92f..036028a2 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -55,7 +55,7 @@ def get_metadata(url, validate_cert=True):
idp_descriptor_nodes = OneLogin_Saml2_XML.query(dom, '//md:IDPSSODescriptor')
if idp_descriptor_nodes:
valid = True
- except:
+ except Exception:
pass
if not valid:
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 9daf17d2..488a8374 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -391,7 +391,7 @@ def generate_unique_id():
@staticmethod
def parse_time_to_SAML(time):
- """
+ r"""
Converts a UNIX timestamp to SAML2 timestamp on the form
yyyy-mm-ddThh:mm:ss(\.s+)?Z.
@@ -406,7 +406,7 @@ def parse_time_to_SAML(time):
@staticmethod
def parse_SAML_to_time(timestr):
- """
+ r"""
Converts a SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(\.s+)?Z
to a UNIX timestamp. The sub-second part is ignored.
diff --git a/tests/pep8.rc b/tests/pep8.rc
deleted file mode 100644
index 27d6de8d..00000000
--- a/tests/pep8.rc
+++ /dev/null
@@ -1,3 +0,0 @@
-[pep8]
-ignore = E501,E731
-max-line-length = 160
\ No newline at end of file
diff --git a/tests/src/OneLogin/saml2_tests/authn_request_test.py b/tests/src/OneLogin/saml2_tests/authn_request_test.py
index c0dcbf2c..71c99539 100644
--- a/tests/src/OneLogin/saml2_tests/authn_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/authn_request_test.py
@@ -267,7 +267,7 @@ def testCreateDeflatedSAMLRequestURLParameter(self):
'SAMLRequest': authn_request.get_request()
}
auth_url = OneLogin_Saml2_Utils.redirect('http://idp.example.com/SSOService.php', parameters, True)
- self.assertRegex(auth_url, '^http://idp\.example\.com\/SSOService\.php\?SAMLRequest=')
+ self.assertRegex(auth_url, r'^http://idp\.example\.com\/SSOService\.php\?SAMLRequest=')
exploded = urlparse(auth_url)
exploded = parse_qs(exploded[4])
payload = exploded['SAMLRequest'][0]
@@ -295,7 +295,7 @@ def testCreateEncSAMLRequest(self):
'SAMLRequest': authn_request.get_request()
}
auth_url = OneLogin_Saml2_Utils.redirect('http://idp.example.com/SSOService.php', parameters, True)
- self.assertRegex(auth_url, '^http://idp\.example\.com\/SSOService\.php\?SAMLRequest=')
+ self.assertRegex(auth_url, r'^http://idp\.example\.com\/SSOService\.php\?SAMLRequest=')
exploded = urlparse(auth_url)
exploded = parse_qs(exploded[4])
payload = exploded['SAMLRequest'][0]
diff --git a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
index 6e1aaa1d..f4859244 100644
--- a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
+++ b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
@@ -5,7 +5,7 @@
try:
from urllib.error import URLError
-except:
+except ImportError:
from urllib2 import URLError
from copy import deepcopy
diff --git a/tests/src/OneLogin/saml2_tests/logout_request_test.py b/tests/src/OneLogin/saml2_tests/logout_request_test.py
index d606ca89..1ea9e7f7 100644
--- a/tests/src/OneLogin/saml2_tests/logout_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_request_test.py
@@ -65,7 +65,7 @@ def testConstructor(self):
parameters = {'SAMLRequest': logout_request.get_request()}
logout_url = OneLogin_Saml2_Utils.redirect('http://idp.example.com/SingleLogoutService.php', parameters, True)
- self.assertRegex(logout_url, '^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLRequest=')
+ self.assertRegex(logout_url, r'^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLRequest=')
url_parts = urlparse(logout_url)
exploded = parse_qs(url_parts.query)
payload = exploded['SAMLRequest'][0]
@@ -82,7 +82,7 @@ def testCreateDeflatedSAMLLogoutRequestURLParameter(self):
parameters = {'SAMLRequest': logout_request.get_request()}
logout_url = OneLogin_Saml2_Utils.redirect('http://idp.example.com/SingleLogoutService.php', parameters, True)
- self.assertRegex(logout_url, '^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLRequest=')
+ self.assertRegex(logout_url, r'^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLRequest=')
url_parts = urlparse(logout_url)
exploded = parse_qs(url_parts.query)
payload = exploded['SAMLRequest'][0]
@@ -139,7 +139,7 @@ def testConstructorEncryptIdUsingX509certMulti(self):
parameters = {'SAMLRequest': logout_request.get_request()}
logout_url = OneLogin_Saml2_Utils.redirect('http://idp.example.com/SingleLogoutService.php', parameters, True)
- self.assertRegex(logout_url, '^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLRequest=')
+ self.assertRegex(logout_url, r'^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLRequest=')
url_parts = urlparse(logout_url)
exploded = parse_qs(url_parts.query)
payload = exploded['SAMLRequest'][0]
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index 5a7994c0..eb6f5e60 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -77,7 +77,7 @@ def testCreateDeflatedSAMLLogoutResponseURLParameter(self):
logout_url = OneLogin_Saml2_Utils.redirect('http://idp.example.com/SingleLogoutService.php', parameters, True)
- self.assertRegex(logout_url, '^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLResponse=')
+ self.assertRegex(logout_url, r'^http://idp\.example\.com\/SingleLogoutService\.php\?SAMLResponse=')
url_parts = urlparse(logout_url)
exploded = parse_qs(url_parts.query)
inflated = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(exploded['SAMLResponse'][0]))
From 01054802679dbda963cda4e2e8e5697151b69eae Mon Sep 17 00:00:00 2001
From: cclauss
Date: Fri, 18 Jan 2019 20:43:57 +0100
Subject: [PATCH 101/331] setup.py: Add the Python 3.7 trove classifier for
PyPI
Now that #124 is merged and Python 3.7 is passing the Travis tests.
---
setup.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/setup.py b/setup.py
index 631a8bc2..a6bbd9c0 100644
--- a/setup.py
+++ b/setup.py
@@ -20,6 +20,7 @@
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
],
author='OneLogin',
author_email='support@onelogin.com',
From 61eacb44d5789bd96edd11309a2bcae66e0d725f Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 29 Jan 2019 13:34:11 +0100
Subject: [PATCH 102/331] Security improvements. Use of tagid to prevent XPath
injection. Disable DTD on _parse_etree (fromstring defusedxml) method.
---
src/onelogin/saml2/response.py | 18 ++++++++++++------
src/onelogin/saml2/utils.py | 3 +--
src/onelogin/saml2/xml_utils.py | 16 ++++++++++++----
3 files changed, 25 insertions(+), 12 deletions(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index dd10771f..a291a841 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -737,6 +737,7 @@ def __query_assertion(self, xpath_expr):
signature_expr = '/ds:Signature/ds:SignedInfo/ds:Reference'
signed_assertion_query = '/samlp:Response' + assertion_expr + signature_expr
assertion_reference_nodes = self.__query(signed_assertion_query)
+ tagid = None
if not assertion_reference_nodes:
# Check if the message is signed
@@ -744,23 +745,28 @@ def __query_assertion(self, xpath_expr):
message_reference_nodes = self.__query(signed_message_query)
if message_reference_nodes:
message_id = message_reference_nodes[0].get('URI')
- final_query = "/samlp:Response[@ID='%s']/" % message_id[1:]
+ final_query = "/samlp:Response[@ID=$tagid]/"
+ tagid = message_id[1:]
else:
final_query = "/samlp:Response"
final_query += assertion_expr
else:
assertion_id = assertion_reference_nodes[0].get('URI')
- final_query = '/samlp:Response' + assertion_expr + "[@ID='%s']" % assertion_id[1:]
+ final_query = '/samlp:Response' + assertion_expr + "[@ID=$tagid]"
+ tagid = assertion_id[1:]
final_query += xpath_expr
- return self.__query(final_query)
+ return self.__query(final_query, tagid)
- def __query(self, query):
+ def __query(self, query, tagid=None):
"""
Extracts nodes that match the query from the Response
:param query: Xpath Expresion
:type query: String
+ :param tagid: Tag ID
+ :type query: String
+
:returns: The queried nodes
:rtype: list
"""
@@ -768,7 +774,7 @@ def __query(self, query):
document = self.decrypted_document
else:
document = self.document
- return OneLogin_Saml2_XML.query(document, query)
+ return OneLogin_Saml2_XML.query(document, query, None, tagid)
def __decrypt_assertion(self, xml):
"""
@@ -817,7 +823,7 @@ def __decrypt_assertion(self, xml):
if not uri.startswith('#'):
break
uri = uri.split('#')[1]
- encrypted_key = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]')
+ encrypted_key = OneLogin_Saml2_XML.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id=$tagid]', None, uri)
if encrypted_key:
keyinfo.append(encrypted_key[0])
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 488a8374..4647c230 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -13,7 +13,6 @@
from copy import deepcopy
import calendar
from datetime import datetime
-from defusedxml.lxml import fromstring
from hashlib import sha1, sha256, sha384, sha512
from isodate import parse_duration as duration_parser
import re
@@ -684,7 +683,7 @@ def decrypt_element(encrypted_data, key, debug=False, inplace=False):
"""
if isinstance(encrypted_data, Element):
- encrypted_data = fromstring(str(encrypted_data.toxml()))
+ encrypted_data = OneLogin_Saml2_XML.to_etree(str(encrypted_data.toxml()))
if not inplace and isinstance(encrypted_data, OneLogin_Saml2_XML._element_class):
encrypted_data = deepcopy(encrypted_data)
elif isinstance(encrypted_data, OneLogin_Saml2_XML._text_class):
diff --git a/src/onelogin/saml2/xml_utils.py b/src/onelogin/saml2/xml_utils.py
index 03d826a6..7257ac69 100644
--- a/src/onelogin/saml2/xml_utils.py
+++ b/src/onelogin/saml2/xml_utils.py
@@ -62,7 +62,7 @@ def to_etree(xml):
if isinstance(xml, OneLogin_Saml2_XML._element_class):
return xml
if isinstance(xml, OneLogin_Saml2_XML._text_class):
- return OneLogin_Saml2_XML._parse_etree(xml)
+ return OneLogin_Saml2_XML._parse_etree(xml, forbid_dtd=True)
raise ValueError('unsupported type %r' % type(xml))
@@ -101,7 +101,7 @@ def validate_xml(xml, schema, debug=False):
return xml
@staticmethod
- def query(dom, query, context=None):
+ def query(dom, query, context=None, tagid=None):
"""
Extracts nodes that match the query from the Element
@@ -114,13 +114,21 @@ def query(dom, query, context=None):
:param context: Context Node
:type: DOMElement
+ :param tagid: Tag ID
+ :type query: String
+
:returns: The queried nodes
:rtype: list
"""
if context is None:
- return dom.xpath(query, namespaces=OneLogin_Saml2_Constants.NSMAP)
+ source = dom
+ else:
+ source = context
+
+ if tagid is None:
+ return source.xpath(query, namespaces=OneLogin_Saml2_Constants.NSMAP)
else:
- return context.xpath(query, namespaces=OneLogin_Saml2_Constants.NSMAP)
+ return source.xpath(query, tagid=tagid, namespaces=OneLogin_Saml2_Constants.NSMAP)
@staticmethod
def cleanup_namespaces(tree_or_element, top_nsmap=None, keep_ns_prefixes=None):
From 29e1b51c1d830e1dd4c08455fcdb268d20bf7f36 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 29 Jan 2019 18:14:20 +0100
Subject: [PATCH 103/331] Release 1.5.0
---
README.md | 2 ++
changelog.md | 10 ++++++++++
setup.py | 2 +-
3 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index de699ca6..f55241ac 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ This version supports Python3, There is a separate version that only support Pyt
#### Warning ####
+Update python3-saml to 1.5.0, this version includes security improvements for preventing XEE and Xpath Injections.
+
Update python3-saml to 1.4.0, this version includes a fix for the [CVE-2017-11427](https://www.cvedetails.com/cve/CVE-2017-11427/) vulnerability.
This version also changes how the calculate fingerprint method works, and will expect as input a formatted x509 certificate
diff --git a/changelog.md b/changelog.md
index bed7b450..32a386e7 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,14 @@
# python3-saml changelog
+### 1.5.0 (Jan 29, 2019)
+* Security improvements. Use of tagid to prevent XPath injection. Disable DTD on fromstring defusedxml method
+* [#97](https://github.com/onelogin/python3-saml/pull/97) Check that the response has all of the AuthnContexts that we provided
+* Adapt renders from Django demo for Django 1.11 version
+* Update pylint dependency to 1.9.1
+* If debug enable, print reason for the SAMLResponse invalidation
+* Fix DSA constant
+* [#106](https://github.com/onelogin/python3-saml/pull/106) Support NameID children inside of AttributeValue elements
+* Start using flake8 for code quality
+
### 1.4.1 (Apr 25, 2018)
* Add ID to EntityDescriptor before sign it on add_sign method.
* Update defusedxml, coveralls and coverage dependencies
diff --git a/setup.py b/setup.py
index a6bbd9c0..2946d386 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.4.1',
+ version='1.5.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From b70832d6a3de324e143875c37927e661d93c959d Mon Sep 17 00:00:00 2001
From: O-P Lamminen
Date: Wed, 30 Jan 2019 09:16:12 +0200
Subject: [PATCH 104/331] Fixed setting NameFormat attribute for AttributeValue
tags in metadata XML
---
src/onelogin/saml2/metadata.py | 2 +-
tests/src/OneLogin/saml2_tests/metadata_test.py | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py
index 9d58e5b7..0aab105c 100644
--- a/src/onelogin/saml2/metadata.py
+++ b/src/onelogin/saml2/metadata.py
@@ -134,7 +134,7 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
if 'nameFormat' in req_attribs.keys() and req_attribs['nameFormat']:
req_attr_nameformat_str = " NameFormat=\"%s\"" % req_attribs['nameFormat']
if 'friendlyName' in req_attribs.keys() and req_attribs['friendlyName']:
- req_attr_nameformat_str = " FriendlyName=\"%s\"" % req_attribs['friendlyName']
+ req_attr_friendlyname_str = " FriendlyName=\"%s\"" % req_attribs['friendlyName']
if 'isRequired' in req_attribs.keys() and req_attribs['isRequired']:
req_attr_isrequired_str = " isRequired=\"%s\"" % 'true' if req_attribs['isRequired'] else 'false'
if 'attributeValue' in req_attribs.keys() and req_attribs['attributeValue']:
diff --git a/tests/src/OneLogin/saml2_tests/metadata_test.py b/tests/src/OneLogin/saml2_tests/metadata_test.py
index 8c6acb1b..58afb5b9 100644
--- a/tests/src/OneLogin/saml2_tests/metadata_test.py
+++ b/tests/src/OneLogin/saml2_tests/metadata_test.py
@@ -157,11 +157,11 @@ def testBuilderAttributeConsumingService(self):
self.assertIn("""
Test Service
Test Service
-
-
-
-
-
+
+
+
+
+
""", metadata)
def testBuilderAttributeConsumingServiceWithMultipleAttributeValue(self):
@@ -183,7 +183,7 @@ def testBuilderAttributeConsumingServiceWithMultipleAttributeValue(self):
userType
admin
-
+
""", metadata)
def testSignMetadata(self):
From a82cbdcb961e3d737488074d59a7cb069ccb3814 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 31 Jan 2019 12:25:45 +0100
Subject: [PATCH 105/331] Update README with get_last_authn_contexts method
---
README.md | 1 +
src/onelogin/saml2/auth.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f55241ac..130fc1d4 100644
--- a/README.md
+++ b/README.md
@@ -915,6 +915,7 @@ Main class of OneLogin Python Toolkit
* ***get_sso_url*** Gets the SSO url.
* ***get_slo_url*** Gets the SLO url.
* ***get_last_request_id*** The ID of the last Request SAML message generated (AuthNRequest, LogoutRequest).
+* ***get_last_authn_contexts*** Returns the list of authentication contexts sent in the last SAML Response.
* ***build_request_signature*** Builds the Signature of the SAML Request.
* ***build_response_signature*** Builds the Signature of the SAML Response.
* ***get_settings*** Returns the settings info.
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 44717f08..106aabd8 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -322,7 +322,7 @@ def get_last_assertion_id(self):
def get_last_authn_contexts(self):
"""
- :returns: The list of authentication contexts sent in the last SAML resposne.
+ :returns: The list of authentication contexts sent in the last SAML Response.
:rtype: list
"""
return self.__last_authn_contexts
From b0e7046cf9dc3b7bf01086b613d09e8a6296a78e Mon Sep 17 00:00:00 2001
From: O-P Lamminen
Date: Tue, 19 Feb 2019 16:48:43 +0200
Subject: [PATCH 106/331] Fix for lxml issues parsing SLO XML messages
specifying encoding. - Adding bytes_type to compat - Changing
OneLogin_Saml2_XML.to_etree() to always pass bytes to _parse_etree() - Adding
test cases for SLO messages with XML encoding specified
---
src/onelogin/saml2/compat.py | 2 ++
src/onelogin/saml2/xml_utils.py | 5 ++-
.../logout_request_with_encoding.xml | 13 ++++++++
.../logout_response_with_encoding.xml | 14 ++++++++
...response_with_encoding_deflated.xml.base64 | 1 +
.../saml2_tests/logout_request_test.py | 32 +++++++++++++++++++
.../saml2_tests/logout_response_test.py | 28 ++++++++++++++++
7 files changed, 94 insertions(+), 1 deletion(-)
create mode 100644 tests/data/logout_requests/logout_request_with_encoding.xml
create mode 100644 tests/data/logout_responses/logout_response_with_encoding.xml
create mode 100644 tests/data/logout_responses/logout_response_with_encoding_deflated.xml.base64
diff --git a/src/onelogin/saml2/compat.py b/src/onelogin/saml2/compat.py
index 13fe5a6f..b69e61e4 100644
--- a/src/onelogin/saml2/compat.py
+++ b/src/onelogin/saml2/compat.py
@@ -22,6 +22,7 @@
if isinstance(b'', type('')): # py 2.x
text_types = (basestring,) # noqa
+ bytes_type = bytes
str_type = basestring # noqa
def utf8(data):
@@ -42,6 +43,7 @@ def to_bytes(data):
else: # py 3.x
text_types = (bytes, str)
+ bytes_type = bytes
str_type = str
def utf8(data):
diff --git a/src/onelogin/saml2/xml_utils.py b/src/onelogin/saml2/xml_utils.py
index 7257ac69..c7274298 100644
--- a/src/onelogin/saml2/xml_utils.py
+++ b/src/onelogin/saml2/xml_utils.py
@@ -25,6 +25,7 @@ class OneLogin_Saml2_XML(object):
_parse_etree = staticmethod(fromstring)
_schema_class = etree.XMLSchema
_text_class = compat.text_types
+ _bytes_class = compat.bytes_type
_unparse_etree = staticmethod(tostring)
dump = staticmethod(etree.dump)
@@ -61,8 +62,10 @@ def to_etree(xml):
"""
if isinstance(xml, OneLogin_Saml2_XML._element_class):
return xml
- if isinstance(xml, OneLogin_Saml2_XML._text_class):
+ if isinstance(xml, OneLogin_Saml2_XML._bytes_class):
return OneLogin_Saml2_XML._parse_etree(xml, forbid_dtd=True)
+ if isinstance(xml, OneLogin_Saml2_XML._text_class):
+ return OneLogin_Saml2_XML._parse_etree(compat.to_bytes(xml), forbid_dtd=True)
raise ValueError('unsupported type %r' % type(xml))
diff --git a/tests/data/logout_requests/logout_request_with_encoding.xml b/tests/data/logout_requests/logout_request_with_encoding.xml
new file mode 100644
index 00000000..44891df7
--- /dev/null
+++ b/tests/data/logout_requests/logout_request_with_encoding.xml
@@ -0,0 +1,13 @@
+
+
+ http://idp.example.com/
+ ONELOGIN_1e442c129e1f822c8096086a1103c5ee2c7cae1c
+
diff --git a/tests/data/logout_responses/logout_response_with_encoding.xml b/tests/data/logout_responses/logout_response_with_encoding.xml
new file mode 100644
index 00000000..4354c3e6
--- /dev/null
+++ b/tests/data/logout_responses/logout_response_with_encoding.xml
@@ -0,0 +1,14 @@
+
+
+ http://idp.example.com/
+
+
+
+
diff --git a/tests/data/logout_responses/logout_response_with_encoding_deflated.xml.base64 b/tests/data/logout_responses/logout_response_with_encoding_deflated.xml.base64
new file mode 100644
index 00000000..eaf313b9
--- /dev/null
+++ b/tests/data/logout_responses/logout_response_with_encoding_deflated.xml.base64
@@ -0,0 +1 @@
+fVLdS8MwEH8X/B9K3tcmaxvWsFbELwZzAzd98EXS5KqFNgm9VPbnO6pjm1jzFO5+X8nd/GrXNsEndFhbkxMWUhKAUVbX5j0nz9v7yYxcFZcXc5Rt48TSvtvePwE6axCCPdegGFo56TsjrMQahZEtoPBKbK4fl2IaUuE6662yDbm8CP48R6X/hSQidH6fdVRpcZuTtyoD4KzUmS4rHnPKKykzCXHJaJrMdFrGPOVVOUuzUZ2Xw5/sbcfNEHtYGPTS+D2SsnjCphNGtzQRcSZi9jpKvQX0tZF+8Pjw3okoQt9XVahsG4HRztbG48kNGwzdhxsPYw6D2dqcrFd3y/XDYvU2ZeksUUpXUsmYS5akErKMJslUK53xsgJOWcpgVLf4bgwrIIYXd8VP4Fq7EHaydQ0MsefRKeiE58TGS99jcTQ5q99YDcGLbHr4f/44oMWmVwoQSRAdTKJfLofC+cYWXw==
diff --git a/tests/src/OneLogin/saml2_tests/logout_request_test.py b/tests/src/OneLogin/saml2_tests/logout_request_test.py
index 1ea9e7f7..3d724d82 100644
--- a/tests/src/OneLogin/saml2_tests/logout_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_request_test.py
@@ -414,6 +414,38 @@ def testIsValid(self):
logout_request5 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
self.assertTrue(logout_request5.is_valid(request_data))
+ def testIsValidWithXMLEncoding(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest
+ """
+ request_data = {
+ 'http_host': 'example.com',
+ 'script_name': 'index.html'
+ }
+ request = self.file_contents(join(self.data_path, 'logout_requests', 'logout_request_with_encoding.xml'))
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+
+ logout_request = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertTrue(logout_request.is_valid(request_data))
+
+ settings.set_strict(True)
+ logout_request2 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertFalse(logout_request2.is_valid(request_data))
+
+ settings.set_strict(False)
+ dom = parseString(request)
+ logout_request3 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml()))
+ self.assertTrue(logout_request3.is_valid(request_data))
+
+ settings.set_strict(True)
+ logout_request4 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml()))
+ self.assertFalse(logout_request4.is_valid(request_data))
+
+ current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
+ request = request.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url)
+ logout_request5 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertTrue(logout_request5.is_valid(request_data))
+
def testIsValidRaisesExceptionWhenRaisesArgumentIsTrue(self):
request = OneLogin_Saml2_Utils.b64encode('invalid ')
request_data = {
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index eb6f5e60..054e9762 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -276,6 +276,34 @@ def testIsValid(self):
response_3 = OneLogin_Saml2_Logout_Response(settings, message_3)
self.assertTrue(response_3.is_valid(request_data))
+ def testIsValidWithXMLEncoding(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_LogoutResponse
+ """
+ request_data = {
+ 'http_host': 'example.com',
+ 'script_name': 'index.html',
+ 'get_data': {}
+ }
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+ message = self.file_contents(join(self.data_path, 'logout_responses', 'logout_response_with_encoding_deflated.xml.base64'))
+
+ response = OneLogin_Saml2_Logout_Response(settings, message)
+ self.assertTrue(response.is_valid(request_data))
+
+ settings.set_strict(True)
+ response_2 = OneLogin_Saml2_Logout_Response(settings, message)
+ with self.assertRaisesRegex(Exception, 'The LogoutResponse was received at'):
+ response_2.is_valid(request_data, raise_exceptions=True)
+
+ plain_message = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(message))
+ current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
+ plain_message = plain_message.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url)
+ message_3 = OneLogin_Saml2_Utils.deflate_and_base64_encode(plain_message)
+
+ response_3 = OneLogin_Saml2_Logout_Response(settings, message_3)
+ self.assertTrue(response_3.is_valid(request_data))
+
def testIsValidRaisesExceptionWhenRaisesArgumentIsTrue(self):
message = OneLogin_Saml2_Utils.deflate_and_base64_encode('invalid ')
request_data = {
From eb9680adca69e542a501dff3ad4aac2595316309 Mon Sep 17 00:00:00 2001
From: James McClune
Date: Wed, 27 Feb 2019 23:03:43 -0500
Subject: [PATCH 107/331] README: added inline markup to important references
Also fixed minor grammatical errors.
Signed-off-by: James McClune
---
README.md | 314 +++++++++++++++++++++++++++---------------------------
1 file changed, 157 insertions(+), 157 deletions(-)
diff --git a/README.md b/README.md
index 130fc1d4..55b1d7e3 100644
--- a/README.md
+++ b/README.md
@@ -10,23 +10,23 @@ Add SAML support to your Python software using this library.
Forget those complicated libraries and use the open source library provided
and supported by OneLogin Inc.
-This version supports Python3, There is a separate version that only support Python2: [python-saml](https://pypi.python.org/pypi/python-saml)
+This version supports Python3. There is a separate version that only support Python2: [python-saml](https://pypi.python.org/pypi/python-saml)
#### Warning ####
-Update python3-saml to 1.5.0, this version includes security improvements for preventing XEE and Xpath Injections.
+Update ``python3-saml`` to ``1.5.0``, this version includes security improvements for preventing XEE and Xpath Injections.
-Update python3-saml to 1.4.0, this version includes a fix for the [CVE-2017-11427](https://www.cvedetails.com/cve/CVE-2017-11427/) vulnerability.
+Update ``python3-saml`` to ``1.4.0``, this version includes a fix for the [CVE-2017-11427](https://www.cvedetails.com/cve/CVE-2017-11427/) vulnerability.
-This version also changes how the calculate fingerprint method works, and will expect as input a formatted x509 certificate
+This version also changes how the calculate fingerprint method works, and will expect as input a formatted X.509 certificate.
-Update python3-saml to 1.2.6 that adds the use defusedxml that will prevent XEE and other attacks based on the abuse of XML. (CVE-2017-9672)
+Update ``python3-saml`` to ``1.2.6`` that adds the use defusedxml that will prevent XEE and other attacks based on the abuse of XML. (CVE-2017-9672)
-Update python3-saml to >= 1.2.1, 1.2.0 had a bug on signature validation process (when using wantAssertionsSigned and wantMessagesSigned). [CVE-2016-1000251](https://github.com/distributedweaknessfiling/DWF-Database-Artifacts/blob/master/DWF/2016/1000251/CVE-2016-1000251.json)
+Update ``python3-saml`` to ``>= 1.2.1``, ``1.2.0`` had a bug on signature validation process (when using ``wantAssertionsSigned`` and ``wantMessagesSigned``). [CVE-2016-1000251](https://github.com/distributedweaknessfiling/DWF-Database-Artifacts/blob/master/DWF/2016/1000251/CVE-2016-1000251.json)
-1.2.0 version includes a security patch that contains extra validations that will prevent signature wrapping attacks.
+``1.2.0`` version includes a security patch that contains extra validations that will prevent signature wrapping attacks.
-python3-saml < v1.2.0 is vulnerable and allows signature wrapping!
+``python3-saml < v1.2.0`` is vulnerable and allows signature wrapping!
#### Security Guidelines ####
@@ -55,23 +55,23 @@ since 2002, but lately it is becoming popular due its advantages:
* **Opportunity** - B2B cloud vendor should support SAML to facilitate the
integration of their product.
-General description
+General Description
-------------------
OneLogin's SAML Python toolkit lets you turn your Python application into a SP
(Service Provider) that can be connected to an IdP (Identity Provider).
-Supports:
+**Supports:**
* SSO and SLO (SP-Initiated and IdP-Initiated).
* Assertion and nameId encryption.
* Assertion signatures.
- * Message signatures: AuthNRequest, LogoutRequest, LogoutResponses.
+ * Message signatures: ``AuthNRequest``, ``LogoutRequest``, ``LogoutResponses``.
* Enable an Assertion Consumer Service endpoint.
* Enable a Single Logout Service endpoint.
* Publish the SP metadata (which can be signed).
-Key features:
+**Key Features:**
* **saml2int** - Implements the SAML 2.0 Web Browser SSO Profile.
* **Session-less** - Forget those common conflicts between the SP and
@@ -79,7 +79,7 @@ Key features:
* **Easy to use** - Programmer will be allowed to code high-level and
low-level programming, 2 easy to use APIs are available.
* **Tested** - Thoroughly tested.
- * **Popular** - OneLogin's customers use it. Add easy support to your django/flask web projects.
+ * **Popular** - OneLogin's customers use it. Add easy support to your Django/Flask web projects.
Installation
------------
@@ -92,22 +92,22 @@ Installation
duration parser and formatter
* [defusedxml](https://pypi.python.org/pypi/defusedxml) XML bomb protection for Python stdlib modules
-Review the setup.py file to know the version of the library that python3-saml is using
+Review the ``setup.py`` file to know the version of the library that ``python3-saml`` is using
### Code ###
-#### Option 1. Download from github ####
+#### Option 1. Download from GitHub ####
-The toolkit is hosted on github. You can download it from:
+The toolkit is hosted on GitHub. You can download it from:
* Lastest release: https://github.com/onelogin/python3-saml/releases/latest
* Master repo: https://github.com/onelogin/python3-saml/tree/master
-Copy the core of the library (src/onelogin/saml2 folder) and merge the setup.py inside the python application. (each application has its structure so take your time to locate the Python SAML toolkit in the best place).
+Copy the core of the library ``(src/onelogin/saml2 folder)`` and merge the ``setup.py`` inside the Python application. (Each application has its structure so take your time to locate the Python SAML toolkit in the best place).
#### Option 2. Download from pypi ####
-The toolkit is hosted in pypi, you can find the python3-saml package at https://pypi.python.org/pypi/python3-saml
+The toolkit is hosted in pypi, you can find the ``python3-saml`` package at https://pypi.python.org/pypi/python3-saml
You can install it executing:
```
@@ -116,7 +116,7 @@ $ pip install python3-saml
If you want to know how a project can handle python packages review this [guide](https://packaging.python.org/en/latest/tutorial.html) and review this [sampleproject](https://github.com/pypa/sampleproject)
-Security warning
+Security Warning
----------------
In production, the **strict** parameter MUST be set as **"true"**. Otherwise
@@ -124,12 +124,12 @@ your environment is not secure and will be exposed to attacks.
In production also we highly recommend to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment.
-Getting started
+Getting Started
---------------
### Knowing the toolkit ###
-The new OneLogin SAML Toolkit contains different folders (certs, lib, demo-django, demo-flask and tests) and some files.
+The new OneLogin SAML Toolkit contains different folders (``certs``, ``lib``, ``demo-django``, ``demo-flask`` and ``tests``) and some files.
Let's start describing them:
@@ -140,37 +140,37 @@ the classes and methods that are described in a later section.
#### demo-django ####
-This folder contains a Django project that will be used as demo to show how to add SAML support to the Django Framework. 'demo' is the main folder of the django project (with its settings.py, views.py, urls.py), 'templates' is the django templates of the project and 'saml' is a folder that contains the 'certs' folder that could be used to store the x509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
+This folder contains a Django project that will be used as demo to show how to add SAML support to the Django Framework. **demo** is the main folder of the Django project (with its ``settings.py``, ``views.py``, ``urls.py``), **templates** is the Django templates of the project and **saml** is a folder that contains the ``certs`` folder that could be used to store the X.509 public and private key, and the SAML toolkit settings (``settings.json`` and ``advanced_settings.json``).
***Notice about certs***
-SAML requires a x.509 cert to sign and encrypt elements like NameID, Message, Assertion, Metadata.
+SAML requires a X.509 cert to sign and encrypt elements like ``NameID``, ``Message``, ``Assertion``, ``Metadata``.
-If our environment requires sign or encrypt support, the certs folder may contain the x509 cert and the private key that the SP will use:
+If our environment requires sign or encrypt support, the certs folder may contain the X.509 cert and the private key that the SP will use:
* sp.crt The public cert of the SP
-* sp.key The privake key of the SP
+* sp.key The private key of the SP
-Or also we can provide those data in the setting file at the 'x509cert' and the privateKey' json parameters of the 'sp' element.
+Or also we can provide those data in the setting file at the ``X.509cert`` and the ``privateKey`` JSON parameters of the ``sp`` element.
-Sometimes we could need a signature on the metadata published by the SP, in this case we could use the x.509 cert previously mentioned or use a new x.509 cert: metadata.crt and metadata.key.
+Sometimes we could need a signature on the metadata published by the SP, in this case we could use the X.509 cert previously mentioned or use a new X.509 cert: ``metadata.crt`` and ``metadata.key``.
-Use `sp_new.crt` if you are in a key rollover process and you want to
-publish that x509certificate on Service Provider metadata.
+Use ``sp_new.crt`` if you are in a key rollover process and you want to
+publish that X.509 certificate on Service Provider metadata.
If you want to create self-signed certs, you can do it at the https://www.samltool.com/self_signed_certs.php service, or using the command:
```bash
-openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout saml.key
+openssl req -new -X.509 -days 3652 -nodes -out sp.crt -keyout saml.key
```
#### demo-flask ####
-This folder contains a Flask project that will be used as demo to show how to add SAML support to the Flask Framework. 'index.py' is the main flask file that has all the code, this file uses the templates stored at the 'templates' folder. In the 'saml' folder we found the 'certs' folder to store the x509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
+This folder contains a Flask project that will be used as demo to show how to add SAML support to the Flask Framework. ``index.py`` is the main Flask file that has all the code, this file uses the templates stored at the ``templates`` folder. In the ``saml`` folder we found the ``certs`` folder to store the X.509 public and private key, and the SAML toolkit settings (``settings.json`` and ``advanced_settings.json``).
#### demo_pyramid ####
-This folder contains a Pyramid project that will be used as demo to show how to add SAML support to the [Pyramid Web Framework](http://docs.pylonsproject.org/projects/pyramid/en/latest/). '\_\_init__.py' is the main file that configures the app and its routes, 'views.py' is where all the logic and SAML handling takes place, and the templates are stored in the 'templates' folder. The 'saml' folder is the same as in the other two demos.
+This folder contains a Pyramid project that will be used as demo to show how to add SAML support to the [Pyramid Web Framework](http://docs.pylonsproject.org/projects/pyramid/en/latest/). ``\_\_init__.py`` is the main file that configures the app and its routes, ``views.py`` is where all the logic and SAML handling takes place, and the templates are stored in the ``templates`` folder. The ``saml`` folder is the same as in the other two demos.
#### setup.py ####
@@ -191,9 +191,9 @@ The previous line will run the tests for the whole toolkit. You can also run the
python setup.py test --test-suite tests.src.OneLogin.saml2_tests.auth_test.OneLogin_Saml2_Auth_Test
```
-With the --test-suite parameter you can specify the module to test. You'll find all the module available and their class names at tests/src/OneLogin/saml2_tests/
+With the ``--test-suite`` parameter you can specify the module to test. You'll find all the module available and their class names at ``tests/src/OneLogin/saml2_tests/``.
-### How it works ###
+### How It Works ###
#### Settings ####
@@ -201,13 +201,13 @@ First of all we need to configure the toolkit. The SP's info, the IdP's info, an
There are two ways to provide the settings information:
-* Use a settings.json file that we should locate in any folder, but indicates its path with the 'custom_base_path' parameter.
+* Use a ``settings.json`` file that we should locate in any folder, but indicates its path with the ``custom_base_path`` parameter.
-* Use a json object with the setting data and provide it directly to the constructor of the class (if your toolkit integation requires certs, remember to provide the 'custom_base_path' as part of the settings or as a parameter in the constructor.
+* Use a JSON object with the setting data and provide it directly to the constructor of the class (if your toolkit integation requires certs, remember to provide the ``custom_base_path`` as part of the settings or as a parameter in the constructor).
-In the demo-django and in the demo-flask folders you will find a 'saml' folder, inside there is a 'certs' folder and a settings.json and a advanced_settings.json files. Those files contain the settings for the saml toolkit. Copy them in your project and set the correct values.
+In the demo-django and in the demo-flask folders you will find a ``saml`` folder, inside there is a ``certs`` folder and a ``settings.json`` and ``advanced_settings.json`` file. Those files contain the settings for the SAML toolkit. Copy them in your project and set the correct values.
-This is the settings.json file:
+This is the ``settings.json`` file:
```javascript
{
@@ -264,15 +264,15 @@ This is the settings.json file:
// represent the requested subject.
// Take a look on src/onelogin/saml2/constants.py to see the NameIdFormat that are supported.
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
- // Usually x509cert and privateKey of the SP are provided by files placed at
+ // Usually X.509cert and privateKey of the SP are provided by files placed at
// the certs folder. But we can also provide them with the following parameters
"x509cert": "",
"privateKey": ""
/*
* Key rollover
- * If you plan to update the SP x509cert and privateKey
- * you can define here the new x509cert and it will be
+ * If you plan to update the SP X.509cert and privateKey
+ * you can define here the new X.509cert and it will be
* published on the SP metadata so Identity Providers can
* read them and get ready for rollover.
*/
@@ -302,15 +302,15 @@ This is the settings.json file:
// only for this endpoint.
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
- // Public x509 certificate of the IdP
+ // Public X.509 certificate of the IdP
"x509cert": ""
/*
- * Instead of using the whole x509cert you can use a fingerprint in order to
- * validate a SAMLResponse (but you still need the x509cert to validate LogoutRequest and LogoutResponse using the HTTP-Redirect binding).
+ * Instead of using the whole X.509cert you can use a fingerprint in order to
+ * validate a SAMLResponse (but you still need the X.509cert to validate LogoutRequest and LogoutResponse using the HTTP-Redirect binding).
* But take in mind that the fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass,
* that why we don't recommend it use for production environments.
*
- * (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
+ * (openssl X.509 -noout -fingerprint -in "idp.crt" to generate it,
* or add for example the -sha256 , -sha384 or -sha512 parameter)
*
* If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
@@ -319,7 +319,7 @@ This is the settings.json file:
* 'sha1' is the default value.
*
* Notice that if you want to validate any SAML Message sent by the HTTP-Redirect binding, you
- * will need to provide the whole x509cert.
+ * will need to provide the whole X.509cert.
*/
// "certFingerprint": "",
// "certFingerprintAlgorithm": "sha1",
@@ -328,7 +328,7 @@ This is the settings.json file:
* signing/encryption, or is under key rollover phase and
* more than one certificate is published on IdP metadata.
* In order to handle that the toolkit offers that parameter.
- * (when used, 'x509cert' and 'certFingerprint' values are
+ * (when used, 'X.509cert' and 'certFingerprint' values are
* ignored).
*/
// 'x509certMulti': {
@@ -343,7 +343,7 @@ This is the settings.json file:
}
```
-In addition to the required settings data (idp, sp), extra settings can be defined in `advanced_settings.json`:
+In addition to the required settings data (IdP, SP), extra settings can be defined in `advanced_settings.json`:
```javascript
{
@@ -459,11 +459,11 @@ In addition to the required settings data (idp, sp), extra settings can be defin
}
```
-In the security section, you can set the way that the SP will handle the messages and assertions. Contact the admin of the IdP and ask them what the IdP expects, and decide what validations will handle the SP and what requirements the SP will have and communicate them to the IdP's admin too.
+In the ``security`` section, you can set the way that the SP will handle the messages and assertions. Contact the admin of the IdP and ask them what the IdP expects, and decide what validations will handle the SP and what requirements the SP will have and communicate them to the IdP's admin too.
Once we know what kind of data could be configured, let's talk about the way settings are handled within the toolkit.
-The settings files described (settings.json and advanced_settings.json) are loaded by the toolkit if not other dict with settings info is provided in the constructors of the toolkit. Let's see some examples.
+The settings files described (``settings.json`` and ``advanced_settings.json``) are loaded by the toolkit if not other dict with settings info is provided in the constructors of the toolkit. Let's see some examples.
```python
# Initializes toolkit with settings.json & advanced_settings.json files.
@@ -483,7 +483,7 @@ auth = OneLogin_Saml2_Auth(req, settings_data)
settings = OneLogin_Saml2_Settings(settings_data)
```
-You can declare the settings_data in the file that contains the constructor execution or locate them in any file and load the file in order to get the dict available as we see in the following example:
+You can declare the ``settings_data`` in the file that contains the constructor execution or locate them in any file and load the file in order to get the dict available as we see in the following example:
```python
filename = "/var/www/django-project/custom_settings.json" # The custom_settings.json contains a
@@ -506,9 +506,9 @@ Using ````parse_remote```` IdP metadata can be obtained and added to the setting
idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata')
``
-If the Metadata contains several entities, the relevant EntityDescriptor can be specified when retrieving the settings from the IdpMetadataParser by its Entity Id value:
+If the Metadata contains several entities, the relevant ``EntityDescriptor`` can be specified when retrieving the settings from the ``IdpMetadataParser`` by its ``entityId`` value:
-idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(https://example.com/metadatas, entity_id='idp_entity_id')
+``idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(https://example.com/metadatas, entity_id='idp_entity_id')``
#### How load the library ####
@@ -524,7 +524,7 @@ from onelogin.saml2.utils import OneLogin_Saml2_Utils
#### The Request ####
-Building a OneLogin\_Saml2\_Auth object requires a 'request' parameter.
+Building a ``OneLogin\_Saml2\_Auth`` object requires a ``request`` parameter:
```python
auth = OneLogin_Saml2_Auth(req)
@@ -548,7 +548,7 @@ req = {
}
```
-Each python framework builds its own request object, you may map its data to match what the saml toolkit expects.
+Each Python framework builds its own ``request`` object, you may map its data to match what the SAML toolkit expects.
Let`s see some examples:
```python
@@ -574,7 +574,7 @@ def prepare_from_flask_request(request):
An explanation of some advanced request parameters:
-* `https` - Defaults to "off". Set this to "on" if you receive responses over HTTPS.
+* `https` - Defaults to ``off``. Set this to ``on`` if you receive responses over HTTPS.
* `lowercase_urlencoding` - Defaults to `false`. ADFS users should set this to `true`.
@@ -585,7 +585,7 @@ An explanation of some advanced request parameters:
#### Initiate SSO ####
-In order to send an AuthNRequest to the IdP:
+In order to send an ``AuthNRequest`` to the IdP:
```python
from onelogin.saml2.auth import OneLogin_Saml2_Auth
@@ -597,11 +597,11 @@ auth = OneLogin_Saml2_Auth(req) # Constructor of the SP, loads settings.json
auth.login() # Method that builds and sends the AuthNRequest
```
-The AuthNRequest will be sent signed or unsigned based on the security info of the advanced_settings.json ('authnRequestsSigned').
+The ``AuthNRequest`` will be sent signed or unsigned based on the security info of the ``advanced_settings.json`` file (i.e. ``authnRequestsSigned``).
-The IdP will then return the SAML Response to the user's client. The client is then forwarded to the Attribute Consumer Service of the SP with this information.
+The IdP will then return the SAML Response to the user's client. The client is then forwarded to the **Attribute Consumer Service (ACS)** of the SP with this information.
-We can set a 'return_to' url parameter to the login function and that will be converted as a 'RelayState' parameter:
+We can set a ``return_to`` url parameter to the login function and that will be converted as a ``RelayState`` parameter:
```python
target_url = 'https://example.com'
@@ -609,13 +609,13 @@ auth.login(return_to=target_url)
```
The login method can recieve 3 more optional parameters:
-* force_authn When true the AuthNReuqest will set the ForceAuthn='true'
-* is_passive When true the AuthNReuqest will set the Ispassive='true'
-* set_nameid_policy When true the AuthNReuqest will set a nameIdPolicy element.
+* ``force_authn`` When ``true``, the ``AuthNReuqest`` will set the ``ForceAuthn='true'``
+* ``is_passive`` When true, the ``AuthNReuqest`` will set the ``Ispassive='true'``
+* ``set_nameid_policy`` When true, the ``AuthNReuqest`` will set a ``nameIdPolicy`` element.
-If a match on the future SAMLResponse ID and the AuthNRequest ID to be sent is required, that AuthNRequest ID must to be extracted and stored for future validation, we can get that ID by
+If a match on the future ``SAMLResponse`` ID and the ``AuthNRequest`` ID to be sent is required, that ``AuthNRequest`` ID must to be extracted and stored for future validation, we can get that ID by
-auth.get_last_request_id()
+``auth.get_last_request_id()``
#### The SP Endpoints ####
@@ -638,7 +638,7 @@ else:
print("Error found on Metadata: %s" % (', '.join(errors)))
```
-The get_sp_metadata will return the metadata signed or not based on the security info of the advanced_settings.json ('signMetadata').
+The ``get_sp_metadata`` will return the metadata signed or not based on the security info of the ``advanced_settings.json`` (``signMetadata``).
Before the XML metadata is exposed, a check takes place to ensure that the info to be provided is valid.
@@ -646,9 +646,9 @@ Instead of using the Auth object, you can directly use
```
saml_settings = OneLogin_Saml2_Settings(settings=None, custom_base_path=None, sp_validation_only=True)
```
-to get the settings object and with the sp_validation_only=True parameter we will avoid the IdP Settings validation.
+to get the settings object and with the ``sp_validation_only=True`` parameter we will avoid the IdP settings validation.
-***Attribute Consumer Service(ACS)***
+***Attribute Consumer Service (ACS)***
This code handles the SAML response that the IdP forwards to the SP through the user's client.
@@ -676,10 +676,10 @@ The SAML response is processed and then checked that there are no errors. It als
At that point there are 2 possible alternatives:
-* If no RelayState is provided, we could show the user data in this view or however we wanted.
-* If RelayState is provided, a redirection takes place.
+* If no ``RelayState`` is provided, we could show the user data in this view or however we wanted.
+* If ``RelayState`` is provided, a redirection takes place.
-Notice that we saved the user data in the session before the redirection to have the user data available at the RelayState view.
+Notice that we saved the user data in the session before the redirection to have the user data available at the ``RelayState`` view.
In order to retrieve attributes we use:
@@ -687,7 +687,7 @@ In order to retrieve attributes we use:
attributes = auth.get_attributes();
```
-With this method we get a dict with all the user data provided by the IdP in the Assertion of the SAML Response.
+With this method we get a dict with all the user data provided by the IdP in the assertion of the SAML response.
If we execute print attributes we could get:
@@ -711,7 +711,7 @@ print(attributes['cn'])
print(auth.get_attribute('cn'))
```
-Before trying to get an attribute, check that the user is authenticated. If the user isn't authenticated, an empty dict will be returned. For example, if we call to get_attributes before a auth.process_response, the get_attributes() will return an empty dict.
+Before trying to get an attribute, check that the user is authenticated. If the user isn't authenticated, an empty dict will be returned. For example, if we call to ``get_attributes`` before a ``auth.process_response``, the ``get_attributes()`` will return an empty dict.
***Single Logout Service (SLS)***
@@ -772,7 +772,7 @@ else:
return self.redirect_to(self.get_slo_url(), parameters)
```
-If we don't want that process_slo to destroy the session, pass a true parameter to the process_slo method
+If we don't want that ``process_slo`` to destroy the session, pass a ``true`` parameter to the ``process_slo`` method:
```python
keepLocalSession = true
@@ -783,11 +783,11 @@ auth.process_slo(keep_local_session=keepLocalSession);
In order to send a Logout Request to the IdP:
-The Logout Request will be sent signed or unsigned based on the security info of the advanced_settings.json ('logoutRequestSigned').
+The Logout Request will be sent signed or unsigned based on the security info of the ``advanced_settings.json`` (``logoutRequestSigned``).
-The IdP will return the Logout Response through the user's client to the Single Logout Service of the SP.
+The IdP will return the Logout Response through the user's client to the Single Logout Service (SLS) of the SP.
-We can set a 'return_to' url parameter to the logout function and that will be converted as a 'RelayState' parameter:
+We can set a ``return_to`` url parameter to the logout function and that will be converted as a ``RelayState`` parameter:
```python
target_url = 'https://example.com'
@@ -796,17 +796,17 @@ auth.logout(return_to=target_url)
Also there are 4 optional parameters that can be set:
-* name_id. That will be used to build the LogoutRequest. If not name_id parameter is set and the auth object processed a
-SAML Response with a NameId, then this NameId will be used.
-* session_index. SessionIndex that identifies the session of the user.
-* nq. IDP Name Qualifier
-* name_id_format. The NameID Format that will be set in the LogoutRequest
+* ``name_id``: That will be used to build the ``LogoutRequest``. If no ``name_id`` parameter is set and the auth object processed a
+SAML Response with a ``NameId``, then this ``NameId`` will be used.
+* ``session_index``: ``SessionIndex`` that identifies the session of the user.
+* ``nq``: IDP Name Qualifier.
+* ``name_id_format``: The ``NameID`` Format that will be set in the ``LogoutRequest``.
-If no name_id is provided, the LogoutRequest will contain a NameID with the entity Format.
-If name_id is provided and no name_id_format is provided, the NameIDFormat of the settings will be used.
-If nq is provided, the SPNameQualifier will be also attached to the NameId.
+If no ``name_id`` is provided, the ``LogoutRequest`` will contain a ``NameID`` with the entity Format.
+If ``name_id`` is provided and no ``name_id_format`` is provided, the ``NameIDFormat`` of the settings will be used.
+If ``nq`` is provided, the ``SPNameQualifier`` will be also attached to the ``NameId``.
-If a match on the LogoutResponse ID and the LogoutRequest ID to be sent is required, that LogoutRequest ID must to be extracted and stored for future validation, we can get that ID by
+If a match on the ``LogoutResponse`` ID and the ``LogoutRequest`` ID to be sent is required, that ``LogoutRequest`` ID must to be extracted and stored for future validation, we can get that ID by:
```python
auth.get_last_request_id()
@@ -814,7 +814,7 @@ auth.get_last_request_id()
#### Example of a view that initiates the SSO request and handles the response (is the acs target) ####
-We can code a unique file that initiates the SSO process, handle the response, get the attributes, initiate the slo and processes the logout response.
+We can code a unique file that initiates the SSO process, handle the response, get the attributes, initiate the SLO and processes the logout response.
Note: Review the demos, in a later section we explain the demo use case further in detail.
@@ -865,7 +865,7 @@ else:
### SP Key rollover ###
-If you plan to update the SP x509cert and privateKey you can define the new x509cert as settings['sp']['x509certNew'] and it will be
+If you plan to update the SP ``X.509cert`` and ``privateKey`` you can define the new ``X.509cert`` as ``settings['sp']['X.509certNew']`` and it will be
published on the SP metadata so Identity Providers can read them and get ready for rollover.
@@ -874,20 +874,20 @@ published on the SP metadata so Identity Providers can read them and get ready f
In some scenarios the IdP uses different certificates for
signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata.
-In order to handle that the toolkit offers the settings['idp']['x509certMulti'] parameter.
+In order to handle that the toolkit offers the ``settings['idp']['X.509certMulti']`` parameter.
-When that parameter is used, 'x509cert' and 'certFingerprint' values will be ignored by the toolkit.
+When that parameter is used, ``X.509cert`` and ``certFingerprint`` values will be ignored by the toolkit.
-The 'x509certMulti' is an array with 2 keys:
-- 'signing'. An array of certs that will be used to validate IdP signature
-- 'encryption' An array with one unique cert that will be used to encrypt data to be sent to the IdP.
+The ``X.509certMulti`` is an array with 2 keys:
+- ``signing``: An array of certs that will be used to validate IdP signature
+- ``encryption``: An array with one unique cert that will be used to encrypt data to be sent to the IdP.
### Replay attacks ###
In order to avoid replay attacks, you can store the ID of the SAML messages already processed, to avoid processing them twice. Since the Messages expires and will be invalidated due that fact, you don't need to store those IDs longer than the time frame that you currently accepting.
-Get the ID of the last processed message/assertion with the get_last_message_id/get_last_assertion_id method of the Auth object.
+Get the ID of the last processed message/assertion with the ``get_last_message_id/get_last_assertion_id`` method of the ``Auth`` object.
### Main classes and methods ###
@@ -907,32 +907,32 @@ Main class of OneLogin Python Toolkit
* ***is_authenticated*** Checks if the user is authenticated or not.
* ***get_attributes*** Returns the set of SAML attributes.
* ***get_attribute*** Returns the requested SAML attribute.
-* ***get_nameid*** Returns the nameID.
-* ***get_session_index*** Gets the SessionIndex from the AuthnStatement.
-* ***get_session_expiration*** Gets the SessionNotOnOrAfter from the AuthnStatement.
+* ***get_nameid*** Returns the ``nameID``.
+* ***get_session_index*** Gets the ``SessionIndex`` from the ``AuthnStatement``.
+* ***get_session_expiration*** Gets the ``SessionNotOnOrAfter`` from the ``AuthnStatement``.
* ***get_errors*** Returns a list with code errors if something went wrong.
* ***get_last_error_reason*** Returns the reason of the last error
* ***get_sso_url*** Gets the SSO url.
* ***get_slo_url*** Gets the SLO url.
-* ***get_last_request_id*** The ID of the last Request SAML message generated (AuthNRequest, LogoutRequest).
+* ***get_last_request_id*** The ID of the last Request SAML message generated (``AuthNRequest``, ``LogoutRequest``).
* ***get_last_authn_contexts*** Returns the list of authentication contexts sent in the last SAML Response.
* ***build_request_signature*** Builds the Signature of the SAML Request.
* ***build_response_signature*** Builds the Signature of the SAML Response.
* ***get_settings*** Returns the settings info.
* ***set_strict*** Set the strict mode active/disable.
-* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (AuthNRequest, LogoutRequest)
-* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (SAMLResponse, LogoutResponse). If the SAMLResponse had an encrypted assertion, decrypts it.
+* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (``AuthNRequest``, ``LogoutRequest``)
+* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (``SAMLResponse``, ``LogoutResponse``). If the SAMLResponse had an encrypted assertion, decrypts it.
* ***get_last_message_id*** The ID of the last Response SAML message processed.
* ***get_last_assertion_id*** The ID of the last assertion processed.
-* ***get_last_assertion_not_on_or_after*** The NotOnOrAfter value of the valid SubjectConfirmationData node (if any) of the last assertion processed (is only calculated with strict = true)
+* ***get_last_assertion_not_on_or_after*** The ``NotOnOrAfter`` value of the valid ``SubjectConfirmationData`` node (if any) of the last assertion processed (is only calculated with strict = true)
#### OneLogin_Saml2_Auth - authn_request.py ####
SAML 2 Authentication Request class
-* `__init__` This class handles an AuthNRequest. It builds an AuthNRequest object.
-* ***get_request*** Returns unsigned AuthnRequest.
-* ***get_id*** Returns the AuthNRequest ID.
+* `__init__` This class handles an ``AuthNRequest``. It builds an ``AuthNRequest`` object.
+* ***get_request*** Returns unsigned ``AuthnRequest``.
+* ***get_id*** Returns the ``AuthNRequest`` ID.
* ***get_xml*** Returns the XML that will be sent as part of the request.
#### OneLogin_Saml2_Response - response.py ####
@@ -946,28 +946,28 @@ SAML 2 Authentication Response class
* ***get_issuers*** Gets the issuers (from message and from assertion)
* ***get_nameid_data*** Gets the NameID Data provided by the SAML Response from the IdP (returns a dict)
* ***get_nameid*** Gets the NameID provided by the SAML Response from the IdP (returns a string)
-* ***get_session_not_on_or_after*** Gets the SessionNotOnOrAfter from the AuthnStatement
-* ***get_session_index*** Gets the SessionIndex from the AuthnStatement
-* ***get_attributes*** Gets the Attributes from the AttributeStatement element.
+* ***get_session_not_on_or_after*** Gets the ``SessionNotOnOrAfter`` from the ``AuthnStatement``
+* ***get_session_index*** Gets the ``SessionIndex`` from the ``AuthnStatement``
+* ***get_attributes*** Gets the Attributes from the ``AttributeStatement`` element.
* ***validate_num_assertions*** Verifies that the document only contains a single Assertion (encrypted or not)
* ***validate_timestamps*** Verifies that the document is valid according to Conditions Element
* ***get_error*** After execute a validation process, if fails this method returns the cause
* ***get_xml_document*** Returns the SAML Response document (If contains an encrypted assertion, decrypts it).
* ***get_id*** the ID of the response
* ***get_assertion_id*** the ID of the assertion in the response
-* ***get_assertion_not_on_or_after*** the NotOnOrAfter value of the valid SubjectConfirmationData if any
+* ***get_assertion_not_on_or_after*** the ``NotOnOrAfter`` value of the valid ``SubjectConfirmationData`` if any
#### OneLogin_Saml2_LogoutRequest - logout_request.py ####
SAML 2 Logout Request class
* `__init__` Constructs the Logout Request object.
-* ***get_request*** Returns the Logout Request defated, base64encoded.
+* ***get_request*** Returns the Logout Request deflated, base64-encoded.
* ***get_id*** Returns the ID of the Logout Request. (If you have the object you can access to the id attribute)
* ***get_nameid_data*** Gets the NameID Data of the the Logout Request (returns a dict).
* ***get_nameid*** Gets the NameID of the Logout Request Message (returns a string).
* ***get_issuer*** Gets the Issuer of the Logout Request Message.
-* ***get_session_indexes*** Gets the SessionIndexes from the Logout Request.
+* ***get_session_indexes*** Gets the ``SessionIndexes`` from the Logout Request.
* ***is_valid*** Checks if the Logout Request recieved is valid.
* ***get_error*** After execute a validation process, if fails this method returns the cause.
* ***get_xml*** Returns the XML that will be sent as part of the request or that was received at the SP
@@ -979,7 +979,7 @@ SAML 2 Logout Response class
* `__init__` Constructs a Logout Response object.
* ***get_issuer*** Gets the Issuer of the Logout Response Message
* ***get_status*** Gets the Status of the Logout Response.
-* ***is_valid*** Determines if the SAML LogoutResponse is valid
+* ***is_valid*** Determines if the SAML ``LogoutResponse`` is valid
* ***build*** Creates a Logout Response object.
* ***get_response*** Returns a Logout Response object.
* ***get_error*** After execute a validation process, if fails this method returns the cause.
@@ -1001,11 +1001,11 @@ Configuration of the OneLogin Python Toolkit
* ***get_lib_path*** Returns lib path.
* ***get_ext_lib_path*** Returns external lib path.
* ***get_schemas_path*** Returns schema path.
-* ***check_sp_certs*** Checks if the x509 certs of the SP exists and are valid.
-* ***get_sp_key*** Returns the x509 private key of the SP.
-* ***get_sp_cert*** Returns the x509 public cert of the SP.
-* ***get_sp_cert_new*** Returns the future x509 public cert of the SP.
-* ***get_idp_cert*** Returns the x509 public cert of the IdP.
+* ***check_sp_certs*** Checks if the X.509 certs of the SP exists and are valid.
+* ***get_sp_key*** Returns the X.509 private key of the SP.
+* ***get_sp_cert*** Returns the X.509 public cert of the SP.
+* ***get_sp_cert_new*** Returns the future X.509 public cert of the SP.
+* ***get_idp_cert*** Returns the X.509 public cert of the IdP.
* ***get_sp_data*** Gets the SP data.
* ***get_idp_data*** Gets the IdP data.
* ***get_security_data*** Gets security data.
@@ -1017,7 +1017,7 @@ Configuration of the OneLogin Python Toolkit
* ***format_sp_cert_new*** Formats the SP cert new.
* ***format_sp_key*** Formats the private key.
* ***set_strict*** Activates or deactivates the strict mode.
-* ***is_strict*** Returns if the 'strict' mode is active.
+* ***is_strict*** Returns if the ``strict`` mode is active.
* ***is_debug_active*** Returns if the debug is active.
#### OneLogin_Saml2_Metadata - metadata.py ####
@@ -1026,7 +1026,7 @@ A class that contains functionality related to the metadata of the SP
* ***builder*** Generates the metadata of the SP based on the settings.
* ***sign_metadata*** Signs the metadata with the key/cert provided.
-* ***add_x509_key_descriptors*** Adds the x509 descriptors (sign/encryption) to the metadata
+* ***add_X.509_key_descriptors*** Adds the X.509 descriptors (sign/encryption) to the metadata
#### OneLogin_Saml2_Utils - utils.py ####
@@ -1034,7 +1034,7 @@ Auxiliary class that contains several methods
* ***decode_base64_and_inflate*** Base64 decodes and then inflates according to RFC1951.
* ***deflate_and_base64_encode*** Deflates and the base64 encodes a string.
-* ***format_cert*** Returns a x509 cert (adding header & footer if required).
+* ***format_cert*** Returns a X.509 cert (adding header & footer if required).
* ***format_private_key*** Returns a private key (adding header & footer if required).
* ***redirect*** Executes a redirection to the provided url (or return the target url).
* ***get_self_url_host*** Returns the protocol + the current host + the port (if different than common ports).
@@ -1050,7 +1050,7 @@ Auxiliary class that contains several methods
* ***parse_duration*** Interprets a ISO8601 duration value relative to a given timestamp.
* ***get_expire_time*** Compares 2 dates and returns the earliest.
* ***delete_local_session*** Deletes the local session.
-* ***calculate_x509_fingerprint*** Calculates the fingerprint of a x509cert.
+* ***calculate_X.509_fingerprint*** Calculates the fingerprint of a X.509 cert.
* ***format_finger_print*** Formates a fingerprint.
* ***generate_name_id*** Generates a nameID.
* ***get_status*** Gets Status from a Response.
@@ -1080,12 +1080,12 @@ A class that contains methods to obtain and parse metadata from IdP
* ***merge_settings*** Will update the settings with the provided new settings data extracted from the IdP metadata
-For more info, look at the source code; each method is documented and details about what does and how to use it are provided. Make sure to also check the doc folder where HTML documentation about the classes and methods is provided.
+For more info, look at the source code. Each method is documented and details about what does and how to use it are provided. Make sure to also check the doc folder where HTML documentation about the classes and methods is provided.
Demos included in the toolkit
-----------------------------
-The toolkit includes 2 demos to teach how use the toolkit (A django and a flask project), take a look on it.
+The toolkit includes 2 demos to teach how use the toolkit (A Django and a Flask project), take a look on it.
Demos require that SP and IdP are well configured before test it, so edit the settings files.
Notice that each python framework has it own way to handle routes/urls and process request, so focus on
@@ -1093,7 +1093,7 @@ how it deployed. New demos using other python frameworks are welcome as a contri
### Getting Started ###
-We said that this toolkit includes a django application demo and a flask application demo,
+We said that this toolkit includes a Django application demo and a Flask application demo,
let's see how fast is it to deploy them.
***Virtualenv***
@@ -1150,40 +1150,40 @@ The flask project contains:
* ***templates***. Is the folder where flask stores the templates of the project. It was implemented a base.html template that is extended by index.html and attrs.html, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
-* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the x509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
+* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
#### SP setup ####
-The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In the demo-flask it used the first method.
+The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-flask``, it uses the first method.
-In the index.py file we define the app.config['SAML_PATH'], that will target to the 'saml' folder. We require it in order to load the settings files.
+In the ``index.py`` file we define the ``app.config['SAML_PATH']``, that will target to the ``saml`` folder. We require it in order to load the settings files.
-First we need to edit the saml/settings.json, configure the SP part and review the metadata of the IdP and complete the IdP info. Later edit the saml/advanced_settings.json files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
+First we need to edit the ``saml/settings.json`` file, configure the SP part and review the metadata of the IdP and complete the IdP info. Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
#### IdP setup ####
-Once the SP is configured, the metadata of the SP is published at the /metadata url. Based on that info, configure the IdP.
+Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
#### How it works ####
-1. First time you access to the main view 'http://localhost:8000', you can select to login and return to the same view or login and be redirected to /?attrs (attrs view).
+1. First time you access to the main view (http://localhost:8000), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
2. When you click:
- 2.1 in the first link, we access to /?sso (index view). An AuthNRequest is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: /?acs. Notice that a RelayState parameter is set to the url that initiated the process, the index view.
+ 2.1 in the first link, we access to ``/?sso`` (index view). An ``AuthNRequest`` is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: ``/?acs``. Notice that a ``RelayState`` parameter is set to the url that initiated the process, the index view.
- 2.2 in the second link we access to /?attrs (attrs view), we will expetience have the same process described at 2.1 with the diference that as RelayState is set the attrs url.
+ 2.2 in the second link we access to ``/?attrs`` (attrs view), we will expetience have the same process described at 2.1 with the diference that as ``RelayState`` is set the ``attrs`` url.
- 3. The SAML Response is processed in the ACS /?acs, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the RelayState view. a) / or b) /?attrs
+ 3. The SAML Response is processed in the ACS ``/?acs``, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the ``RelayState`` view. a) / or b) ``/?attrs``
4. We are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.
- The single log out funcionality could be tested by 2 ways.
+ The single log out functionality could be tested by 2 ways.
- 5.1 SLO Initiated by SP. Click on the "logout" link at the SP, after that a Logout Request is sent to the IdP, the session at the IdP is closed and replies through the client to the SP with a Logout Response (sent to the Single Logout Service endpoint). The SLS endpoint /?sls of the SP process the Logout Response and if is valid, close the user session of the local app. Notice that the SLO Workflow starts and ends at the SP.
+ 5.1 SLO Initiated by SP. Click on the ``logout`` link at the SP, after that a Logout Request is sent to the IdP, the session at the IdP is closed and replies through the client to the SP with a Logout Response (sent to the Single Logout Service endpoint). The SLS endpoint ``/?sls`` of the SP process the Logout Response and if is valid, close the user session of the local app. Notice that the SLO Workflow starts and ends at the SP.
- 5.2 SLO Initiated by IdP. In this case, the action takes place on the IdP side, the logout process is initiated at the IdP, sends a Logout Request to the SP (SLS endpoint, /?sls). The SLS endpoint of the SP process the Logout Request and if is valid, close the session of the user at the local app and send a Logout Response to the IdP (to the SLS endpoint of the IdP). The IdP receives the Logout Response, process it and close the session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP.
+ 5.2 SLO Initiated by IdP. In this case, the action takes place on the IdP side, the logout process is initiated at the IdP, sends a Logout Request to the SP (SLS endpoint, ``/?sls``). The SLS endpoint of the SP process the Logout Request and if is valid, close the session of the user at the local app and send a Logout Response to the IdP (to the SLS endpoint of the IdP). The IdP receives the Logout Response, process it and close the session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP.
Notice that all the SAML Requests and Responses are handled at a unique view (index) and how GET parameters are used to know the action that must be done.
@@ -1214,37 +1214,37 @@ If you want to integrate a production django application, take a look on this SA
The django project contains:
-* ***manage.py***. A file that is automatically created in each Django project. Is a thin wrapper around django-admin.py that takes care of putting the project’s package on sys.path and sets the DJANGO_SETTINGS_MODULE environment variable.
+* ***manage.py***. A file that is automatically created in each Django project. Is a thin wrapper around django-admin.py that takes care of putting the project’s package on ``sys.path`` and sets the ``DJANGO_SETTINGS_MODULE`` environment variable.
-* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the x509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
+* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (``settings.json`` and ``advanced_settings.json``).
* ***demo*** Is the main folder of the django project, that contains the typical files:
- * ***settings.py*** Contains the default parameters of a django project except the SAML_FOLDER parameter, that may contain the path where is located the 'saml' folder.
- * ***urls.py*** A file that define url routes. In the demo we defined '/' that is related to the index view, '/attrs' that is related with the attrs view and '/metadata', related to th metadata view.
+ * ***settings.py*** Contains the default parameters of a django project except the ``SAML_FOLDER`` parameter, that may contain the path where is located the ``saml`` folder.
+ * ***urls.py*** A file that define url routes. In the demo we defined ``'/'`` that is related to the index view, ``'/attrs'`` that is related with the attrs view and ``'/metadata'``, related to the metadata view.
* ***views.py*** This file contains the views of the django project and some aux methods.
* ***wsgi.py*** A file that let as deploy django using WSGI, the Python standard for web servers and applications.
-* ***templates***. Is the folder where django stores the templates of the project. It was implemented a base.html template that is extended by index.html and attrs.html, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
+* ***templates***. Is the folder where django stores the templates of the project. It was implemented a ``base.html`` template that is extended by ``index.html`` and ``attrs.html``, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
#### SP setup ####
The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In the demo-django it used the first method.
-After set the SAML_FOLDER in the demo/settings.py, the settings of the python toolkit will be loaded on the django web.
+After set the ``SAML_FOLDER`` in the ``demo/settings.py``, the settings of the Python toolkit will be loaded on the Django web.
-First we need to edit the saml/settings.json, configure the SP part and review the metadata of the IdP and complete the IdP info. Later edit the saml/advanced_settings.json files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
+First we need to edit the ``saml/settings.json``, configure the SP part and review the metadata of the IdP and complete the IdP info. Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
#### IdP setup ####
-Once the SP is configured, the metadata of the SP is published at the /metadata url. Based on that info, configure the IdP.
+Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
#### How it works ####
-This demo works very similar to the flask-demo (We did it intentionally).
+This demo works very similar to the ``flask-demo`` (We did it intentionally).
### Getting up and running on Heroku ###
-Getting python3-saml up and running on Heroku will require some extra legwork: python3-saml depends on python-xmlsec which depends on headers from the xmlsec1-dev linux package to install correctly.
+Getting ``python3-saml`` up and running on Heroku will require some extra legwork: ``python3-saml`` depends on ``python-xmlsec`` which depends on headers from the ``xmlsec1-dev`` Linux package to install correctly.
First you will need to add the ```apt``` buildpack to your build server:
@@ -1253,7 +1253,7 @@ heroku buildpacks:set --index=1 -a your-app https://github.com/ABASystems/heroku
heroku buildpacks:set --index=2 -a your-app https://github.com/ABASystems/heroku-buildpack-python
```
-You can confirm the buildpacks have been added in the correct order with ```heroku buildpacks -a your-app```, you should see the apt buildpack first followed by the python buildpack.
+You can confirm the buildpacks have been added in the correct order with ```heroku buildpacks -a your-app```, you should see the apt buildpack first followed by the Python buildpack.
Then add an ```Aptfile``` into the root of your repository containing the ```libxmlsec1-dev``` package, the file should look like:
```
@@ -1261,7 +1261,7 @@ libxmlsec1-dev
```
-Finally, add python3-saml to your requrements.txt and ```git push``` to trigger a build.
+Finally, add ``python3-saml`` to your ``requirements.txt`` and ```git push``` to trigger a build.
### Demo Pyramid ###
@@ -1301,34 +1301,34 @@ The Pyramid project contains:
* ***views.py*** is where all the SAML handling takes place.
-* ***templates*** is the folder where Pyramid stores the templates of the project. It was implemented a layout.jinja2 template that is extended by index.jinja2 and attrs.jinja2, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
+* ***templates*** is the folder where Pyramid stores the templates of the project. It was implemented a ``layout.jinja2`` template that is extended by ``index.jinja2`` and ``attrs.jinja2``, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
-* ***saml*** is a folder that contains the 'certs' folder that could be used to store the x509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
+* ***saml*** is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (``settings.json`` and ``advanced_settings.json``).
#### SP setup ####
-The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In demo_pyramid the first method is used.
+The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In ``demo_pyramid`` the first method is used.
-In the views.py file we define the SAML_PATH, which will target the 'saml' folder. We require it in order to load the settings files.
+In the ``views.py`` file we define the ``SAML_PATH``, which will target the ``saml`` folder. We require it in order to load the settings files.
-First we need to edit the saml/settings.json, configure the SP part and review the metadata of the IdP and complete the IdP info. Later edit the saml/advanced_settings.json files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
+First we need to edit the ``saml/settings.json``, configure the SP part and review the metadata of the IdP and complete the IdP info. Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
#### IdP setup ####
-Once the SP is configured, the metadata of the SP is published at the /metadata/ url. Based on that info, configure the IdP.
+Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
#### How it works ####
-1. First time you access to the main view 'http://localhost:6543', you can select to login and return to the same view or login and be redirected to /?attrs (attrs view).
+1. First time you access to the main view (http://localhost:6543), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
2. When you click:
- 2.1 in the first link, we access to /?sso (index view). An AuthNRequest is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: /?acs. Notice that a RelayState parameter is set to the url that initiated the process, the index view.
+ 2.1 in the first link, we access to ``/?sso`` (index view). An ``AuthNRequest`` is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: ``/?acs``. Notice that a ``RelayState`` parameter is set to the url that initiated the process, the index view.
- 2.2 in the second link we access to /?attrs (attrs view), we will expetience have the same process described at 2.1 with the diference that as RelayState is set the attrs url.
+ 2.2 in the second link we access to ``/?attrs`` (attrs view), we will experience the same process described at 2.1 with the diference that as ``RelayState`` is set the ``attrs`` url.
- 3. The SAML Response is processed in the ACS /?acs, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the RelayState view. a) / or b) /?attrs
+ 3. The SAML Response is processed in the ACS ``/?acs``, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the ``RelayState`` view. a) ``/`` or b) ``/?attrs``
4. We are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.
From 771072e2ae1380acde4ec6af2d7b46b96dccfd2d Mon Sep 17 00:00:00 2001
From: O-P Lamminen
Date: Wed, 20 Feb 2019 15:08:45 +0200
Subject: [PATCH 108/331] Updating cert in testshib metadata to match the one
validated by unit tests, allowing remote IdP tests to pass also when offline.
---
tests/data/metadata/testshib-providers.xml | 39 ++++++++++------------
1 file changed, 17 insertions(+), 22 deletions(-)
diff --git a/tests/data/metadata/testshib-providers.xml b/tests/data/metadata/testshib-providers.xml
index c00a1b77..47c2a873 100644
--- a/tests/data/metadata/testshib-providers.xml
+++ b/tests/data/metadata/testshib-providers.xml
@@ -37,28 +37,23 @@
- MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEV
- MBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYD
- VQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4
- MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQI
- EwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRl
- c3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0B
- AQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7C
- yVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe
- 3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aT
- NPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614
- kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWH
- gWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0G
- A1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ86
- 9nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBl
- bm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNo
- aWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zAN
- BgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRL
- I4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo
- 93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4
- /SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAj
- Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr
- 8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA==
+ MIIDAzCCAeugAwIBAgIVAPX0G6LuoXnKS0Muei006mVSBXbvMA0GCSqGSIb3DQEB
+ CwUAMBsxGTAXBgNVBAMMEGlkcC50ZXN0c2hpYi5vcmcwHhcNMTYwODIzMjEyMDU0
+ WhcNMzYwODIzMjEyMDU0WjAbMRkwFwYDVQQDDBBpZHAudGVzdHNoaWIub3JnMIIB
+ IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg9C4J2DiRTEhJAWzPt1S3ryh
+ m3M2P3hPpwJwvt2q948vdTUxhhvNMuc3M3S4WNh6JYBs53R+YmjqJAII4ShMGNEm
+ lGnSVfHorex7IxikpuDPKV3SNf28mCAZbQrX+hWA+ann/uifVzqXktOjs6DdzdBn
+ xoVhniXgC8WCJwKcx6JO/hHsH1rG/0DSDeZFpTTcZHj4S9MlLNUtt5JxRzV/MmmB
+ 3ObaX0CMqsSWUOQeE4nylSlp5RWHCnx70cs9kwz5WrflnbnzCeHU2sdbNotBEeTH
+ ot6a2cj/pXlRJIgPsrL/4VSicPZcGYMJMPoLTJ8mdy6mpR6nbCmP7dVbCIm/DQID
+ AQABoz4wPDAdBgNVHQ4EFgQUUfaDa2mPi24x09yWp1OFXmZ2GPswGwYDVR0RBBQw
+ EoIQaWRwLnRlc3RzaGliLm9yZzANBgkqhkiG9w0BAQsFAAOCAQEASKKgqTxhqBzR
+ OZ1eVy++si+eTTUQZU4+8UywSKLia2RattaAPMAcXUjO+3cYOQXLVASdlJtt+8QP
+ dRkfp8SiJemHPXC8BES83pogJPYEGJsKo19l4XFJHPnPy+Dsn3mlJyOfAa8RyWBS
+ 80u5lrvAcr2TJXt9fXgkYs7BOCigxtZoR8flceGRlAZ4p5FPPxQR6NDYb645jtOT
+ MVr3zgfjP6Wh2dt+2p04LG7ENJn8/gEwtXVuXCsPoSCDx9Y0QmyXTJNdV1aB0AhO
+ RkWPlFYwp+zOyOIR+3m1+pqWFpn0eT/HrxpdKa74FA3R2kq4R7dXe4G0kUgXTdqX
+ MLRKhDgdmA==
From f610a8ad5f32e9c064dceb5ef1a631c6158bcb6d Mon Sep 17 00:00:00 2001
From: Heewa Barfchin
Date: Tue, 19 Mar 2019 12:04:00 -0400
Subject: [PATCH 109/331] Update Heroku instructions
* Use `heroku buildpacks:add` instead of `:set` so existing buildpacks aren't overwritten
* Replace deprecated ABASystems' apt buildpack (redirects to uptick/heroku-buildpack-apt, which hasn't been updated in 3 years, forked from ddollar/heroku-buildpack-apt, which explicitly says it's deprecated) with Heroku's (https://github.com/heroku/heroku-buildpack-apt)
* Replace ABASystems' python buildpack with Heroku's (heroku/python)
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 55b1d7e3..2b25f961 100644
--- a/README.md
+++ b/README.md
@@ -1249,8 +1249,8 @@ Getting ``python3-saml`` up and running on Heroku will require some extra legwor
First you will need to add the ```apt``` buildpack to your build server:
```
-heroku buildpacks:set --index=1 -a your-app https://github.com/ABASystems/heroku-buildpack-apt
-heroku buildpacks:set --index=2 -a your-app https://github.com/ABASystems/heroku-buildpack-python
+heroku buildpacks:add --index=1 -a your-app heroku-community/apt
+heroku buildpacks:add --index=2 -a your-app heroku/python
```
You can confirm the buildpacks have been added in the correct order with ```heroku buildpacks -a your-app```, you should see the apt buildpack first followed by the Python buildpack.
From 5946ea6398b45ae87cf8ab3a3ff3530baed950e3 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 2 Apr 2019 13:12:22 +0200
Subject: [PATCH 110/331] Add support for Subjects on AuthNRequests by the new
name_id_value_req parameter
---
README.md | 20 ++++----
src/onelogin/saml2/auth.py | 7 ++-
src/onelogin/saml2/authn_request.py | 14 +++++-
src/onelogin/saml2/xml_templates.py | 2 +-
tests/src/OneLogin/saml2_tests/auth_test.py | 46 +++++++++++++++++--
.../saml2_tests/authn_request_test.py | 31 +++++++++++++
6 files changed, 103 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index 2b25f961..404712e2 100644
--- a/README.md
+++ b/README.md
@@ -151,7 +151,7 @@ If our environment requires sign or encrypt support, the certs folder may contai
* sp.crt The public cert of the SP
* sp.key The private key of the SP
-Or also we can provide those data in the setting file at the ``X.509cert`` and the ``privateKey`` JSON parameters of the ``sp`` element.
+Or also we can provide those data in the setting file at the ``x509cert`` and the ``privateKey`` JSON parameters of the ``sp`` element.
Sometimes we could need a signature on the metadata published by the SP, in this case we could use the X.509 cert previously mentioned or use a new X.509 cert: ``metadata.crt`` and ``metadata.key``.
@@ -161,7 +161,7 @@ publish that X.509 certificate on Service Provider metadata.
If you want to create self-signed certs, you can do it at the https://www.samltool.com/self_signed_certs.php service, or using the command:
```bash
-openssl req -new -X.509 -days 3652 -nodes -out sp.crt -keyout saml.key
+openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout saml.key
```
#### demo-flask ####
@@ -264,7 +264,7 @@ This is the ``settings.json`` file:
// represent the requested subject.
// Take a look on src/onelogin/saml2/constants.py to see the NameIdFormat that are supported.
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
- // Usually X.509cert and privateKey of the SP are provided by files placed at
+ // Usually X.509 cert and privateKey of the SP are provided by files placed at
// the certs folder. But we can also provide them with the following parameters
"x509cert": "",
"privateKey": ""
@@ -310,7 +310,7 @@ This is the ``settings.json`` file:
* But take in mind that the fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass,
* that why we don't recommend it use for production environments.
*
- * (openssl X.509 -noout -fingerprint -in "idp.crt" to generate it,
+ * (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
* or add for example the -sha256 , -sha384 or -sha512 parameter)
*
* If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
@@ -343,7 +343,7 @@ This is the ``settings.json`` file:
}
```
-In addition to the required settings data (IdP, SP), extra settings can be defined in `advanced_settings.json`:
+In addition to the required settings data (idp, sp), extra settings can be defined in `advanced_settings.json`:
```javascript
{
@@ -865,7 +865,7 @@ else:
### SP Key rollover ###
-If you plan to update the SP ``X.509cert`` and ``privateKey`` you can define the new ``X.509cert`` as ``settings['sp']['X.509certNew']`` and it will be
+If you plan to update the SP ``x509cert`` and ``privateKey`` you can define the new ``x509cert`` as ``settings['sp']['x509certNew']`` and it will be
published on the SP metadata so Identity Providers can read them and get ready for rollover.
@@ -874,11 +874,11 @@ published on the SP metadata so Identity Providers can read them and get ready f
In some scenarios the IdP uses different certificates for
signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata.
-In order to handle that the toolkit offers the ``settings['idp']['X.509certMulti']`` parameter.
+In order to handle that the toolkit offers the ``settings['idp']['x509certMulti']`` parameter.
-When that parameter is used, ``X.509cert`` and ``certFingerprint`` values will be ignored by the toolkit.
+When that parameter is used, ``x509cert`` and ``certFingerprint`` values will be ignored by the toolkit.
-The ``X.509certMulti`` is an array with 2 keys:
+The ``x509certMulti`` is an array with 2 keys:
- ``signing``: An array of certs that will be used to validate IdP signature
- ``encryption``: An array with one unique cert that will be used to encrypt data to be sent to the IdP.
@@ -1026,7 +1026,7 @@ A class that contains functionality related to the metadata of the SP
* ***builder*** Generates the metadata of the SP based on the settings.
* ***sign_metadata*** Signs the metadata with the key/cert provided.
-* ***add_X.509_key_descriptors*** Adds the X.509 descriptors (sign/encryption) to the metadata
+* ***add_x509_key_descriptors*** Adds the X.509 descriptors (sign/encryption) to the metadata
#### OneLogin_Saml2_Utils - utils.py ####
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 106aabd8..5d6aa84f 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -327,7 +327,7 @@ def get_last_authn_contexts(self):
"""
return self.__last_authn_contexts
- def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True):
+ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
"""
Initiates the SSO process.
@@ -343,10 +343,13 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
:param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element.
:type set_nameid_policy: bool
+ :param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated
+ :type name_id_value_req: string
+
:returns: Redirection URL
:rtype: string
"""
- authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy)
+ authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
self.__last_request = authn_request.get_xml()
self.__last_request_id = authn_request.get_id()
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index 2a1eda88..57da1561 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -22,7 +22,7 @@ class OneLogin_Saml2_Authn_Request(object):
"""
- def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True):
+ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
"""
Constructs the AuthnRequest object.
@@ -37,6 +37,9 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
:param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element.
:type set_nameid_policy: bool
+
+ :param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated
+ :type name_id_value_req: string
"""
self.__settings = settings
@@ -71,6 +74,14 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
if is_passive is True:
is_passive_str = "\n" + ' IsPassive="true"'
+ subject_str = ''
+ if name_id_value_req:
+ subject_str = """
+
+ %s
+
+ """ % (sp_data['NameIDFormat'], name_id_value_req)
+
nameid_policy_str = ''
if set_nameid_policy:
name_id_policy_format = sp_data['NameIDFormat']
@@ -112,6 +123,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
'destination': destination,
'assertion_url': sp_data['assertionConsumerService']['url'],
'entity_id': sp_data['entityId'],
+ 'subject_str': subject_str,
'nameid_policy_str': nameid_policy_str,
'requested_authn_context_str': requested_authn_context_str,
'attr_consuming_service_str': attr_consuming_service_str,
diff --git a/src/onelogin/saml2/xml_templates.py b/src/onelogin/saml2/xml_templates.py
index 99025546..ec4f6260 100644
--- a/src/onelogin/saml2/xml_templates.py
+++ b/src/onelogin/saml2/xml_templates.py
@@ -29,7 +29,7 @@ class OneLogin_Saml2_Templates(object):
Destination="%(destination)s"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
AssertionConsumerServiceURL="%(assertion_url)s"%(attr_consuming_service_str)s>
- %(entity_id)s %(nameid_policy_str)s
+ %(entity_id)s %(subject_str)s%(nameid_policy_str)s
%(requested_authn_context_str)s
"""
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 2a2b557c..02193ee2 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -609,7 +609,7 @@ def testLoginSigned(self):
def testLoginForceAuthN(self):
"""
Tests the login method of the OneLogin_Saml2_Auth class
- Case Logout with no parameters. A AuthN Request is built with ForceAuthn and redirect executed
+ Case AuthN Request is built with ForceAuthn and redirect executed
"""
settings_info = self.loadSettingsJSON()
return_to = u'http://example.com/returnto'
@@ -642,7 +642,7 @@ def testLoginForceAuthN(self):
def testLoginIsPassive(self):
"""
Tests the login method of the OneLogin_Saml2_Auth class
- Case Logout with no parameters. A AuthN Request is built with IsPassive and redirect executed
+ Case AuthN Request is built with IsPassive and redirect executed
"""
settings_info = self.loadSettingsJSON()
return_to = u'http://example.com/returnto'
@@ -676,7 +676,7 @@ def testLoginIsPassive(self):
def testLoginSetNameIDPolicy(self):
"""
Tests the login method of the OneLogin_Saml2_Auth class
- Case Logout with no parameters. A AuthN Request is built with and without NameIDPolicy
+ Case AuthN Request is built with and without NameIDPolicy
"""
settings_info = self.loadSettingsJSON()
return_to = u'http://example.com/returnto'
@@ -707,6 +707,46 @@ def testLoginSetNameIDPolicy(self):
request_3 = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_3['SAMLRequest'][0]))
self.assertNotIn('', request)
+ self.assertNotIn('', request_2)
+ self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">testuser@example.com ', request_2)
+ self.assertIn('', request_2)
+
+ settings_info['sp']['NameIDFormat'] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
+ auth_3 = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
+ target_url_3 = auth_3.login(return_to, name_id_value_req='testuser@example.com')
+ parsed_query_3 = parse_qs(urlparse(target_url_3)[4])
+ self.assertIn(sso_url, target_url_3)
+ self.assertIn('SAMLRequest', parsed_query_3)
+ request_3 = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_3['SAMLRequest'][0]))
+ self.assertIn('', request_3)
+ self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">testuser@example.com', request_3)
+ self.assertIn('', request_3)
+
def testLogout(self):
"""
Tests the logout method of the OneLogin_Saml2_Auth class
diff --git a/tests/src/OneLogin/saml2_tests/authn_request_test.py b/tests/src/OneLogin/saml2_tests/authn_request_test.py
index 71c99539..3f1262f2 100644
--- a/tests/src/OneLogin/saml2_tests/authn_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/authn_request_test.py
@@ -257,6 +257,37 @@ def testCreateRequestSetNameIDPolicy(self):
self.assertRegex(inflated_3, '^', inflated)
+
+ authn_request_2 = OneLogin_Saml2_Authn_Request(settings, name_id_value_req='testuser@example.com')
+ authn_request_encoded_2 = authn_request_2.get_request()
+ inflated_2 = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(authn_request_encoded_2))
+ self.assertRegex(inflated_2, '^', inflated_2)
+ self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">testuser@example.com', inflated_2)
+ self.assertIn('', inflated_2)
+
+ saml_settings['sp']['NameIDFormat'] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
+ settings = OneLogin_Saml2_Settings(saml_settings)
+ authn_request_3 = OneLogin_Saml2_Authn_Request(settings, name_id_value_req='testuser@example.com')
+ authn_request_encoded_3 = authn_request_3.get_request()
+ inflated_3 = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(authn_request_encoded_3))
+ self.assertRegex(inflated_3, '^', inflated_3)
+ self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">testuser@example.com', inflated_3)
+ self.assertIn('', inflated_3)
+
def testCreateDeflatedSAMLRequestURLParameter(self):
"""
Tests the OneLogin_Saml2_Authn_Request Constructor.
From ce81bcb924de4cab85d84876d3b8979d32fa7a47 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 10 Apr 2019 00:27:34 +0200
Subject: [PATCH 111/331] Release 1.6.0
---
changelog.md | 5 +++++
setup.py | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index 32a386e7..42466c4b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,9 @@
# python3-saml changelog
+### 1.6.0 (Apr 10, 2019)
+* Add support for Subjects on AuthNRequests by the new name_id_value_req parameter
+* [#127](https://github.com/onelogin/python3-saml/pull/127) Fix for SLO when XML specifies encoding
+* [#126](https://github.com/onelogin/python3-saml/pull/126) Fixed setting NameFormat attribute for AttributeValue tags
+
### 1.5.0 (Jan 29, 2019)
* Security improvements. Use of tagid to prevent XPath injection. Disable DTD on fromstring defusedxml method
* [#97](https://github.com/onelogin/python3-saml/pull/97) Check that the response has all of the AuthnContexts that we provided
diff --git a/setup.py b/setup.py
index 2946d386..65ff747e 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.5.0',
+ version='1.6.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From 7b427fe284007574285f8a3076b58c2c8f78e668 Mon Sep 17 00:00:00 2001
From: davideZanin
Date: Tue, 23 Apr 2019 11:41:56 +0200
Subject: [PATCH 112/331] demo-tornado initial commit
---
demo-tornado/Docs/DEVELOPING.md | 82 +++++++++
demo-tornado/README.txt | 7 +
demo-tornado/Settings.py | 6 +
demo-tornado/requirements.txt | 13 ++
.../saml/advanced_settings (copia).json | 33 ++++
demo-tornado/saml/advanced_settings.json | 36 ++++
demo-tornado/saml/certs/README | 13 ++
demo-tornado/saml/settings (copia).json | 30 ++++
.../saml/settings (seconda copia).json | 30 ++++
demo-tornado/saml/settings.json | 30 ++++
demo-tornado/templates/attrs.html | 35 ++++
demo-tornado/templates/base.html | 26 +++
demo-tornado/templates/index.html | 66 +++++++
demo-tornado/views.py | 167 ++++++++++++++++++
14 files changed, 574 insertions(+)
create mode 100644 demo-tornado/Docs/DEVELOPING.md
create mode 100644 demo-tornado/README.txt
create mode 100644 demo-tornado/Settings.py
create mode 100644 demo-tornado/requirements.txt
create mode 100644 demo-tornado/saml/advanced_settings (copia).json
create mode 100644 demo-tornado/saml/advanced_settings.json
create mode 100644 demo-tornado/saml/certs/README
create mode 100644 demo-tornado/saml/settings (copia).json
create mode 100644 demo-tornado/saml/settings (seconda copia).json
create mode 100644 demo-tornado/saml/settings.json
create mode 100644 demo-tornado/templates/attrs.html
create mode 100644 demo-tornado/templates/base.html
create mode 100644 demo-tornado/templates/index.html
create mode 100644 demo-tornado/views.py
diff --git a/demo-tornado/Docs/DEVELOPING.md b/demo-tornado/Docs/DEVELOPING.md
new file mode 100644
index 00000000..27ba1243
--- /dev/null
+++ b/demo-tornado/Docs/DEVELOPING.md
@@ -0,0 +1,82 @@
+# OneLogin's SAML Python Toolkit (compatible with Python3)
+
+Installation
+------------
+
+### Dependencies ###
+
+ * python 3.6
+ * apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
+ * pip install xmlsec
+ * pip install isodate
+ * pip install defusedxml
+ * pip install python3-saml
+ * pip install tornado
+
+
+***Virtualenv***
+
+The use of virtualenv/virtualenvwrapper is highly recommended.
+
+### Create certificates ###
+
+in saml/cert run :
+ * openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout sp.key
+ * openssl req -new -x509 -days 3652 -nodes -out metadata.crt -keyout metadata.key
+
+### Useful extesion for SAML messages ###
+* [SAML Chrome Panel 1.8.9](https://chrome.google.com/webstore/detail/saml-chrome-panel/paijfdbeoenhembfhkhllainmocckace/related)
+
+
+
+# Test with keycloack idp
+
+Installation
+------------
+
+### Install Docker ###
+* sudo apt-get remove docker docker-engine docker.io containerd runc
+
+* sudo apt-get update
+
+* sudo apt-get install \
+ apt-transport-https \
+ ca-certificates \
+ curl \
+ gnupg-agent \
+ software-properties-common
+* curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+
+* sudo add-apt-repository \
+ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
+ $(lsb_release -cs) \
+ stable"
+
+* sudo apt-get update
+
+* sudo apt-get install docker-ce docker-ce-cli containerd.io
+
+* sudo docker run hello-world
+
+
+### Keycloack starting ###
+First run only:
+* docker run --name keycloackContainer -d -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_VENDOR=H2 jboss/keycloak
+
+After first run:
+* sudo docker start keycloackContainer
+
+Remember to stop keycloack after usage:
+* sudo docker stop keycloackContainer
+
+
+### Keycloack useful urls ###
+* master: http://localhost:8080/auth/admin
+* users: http://localhost:8080/auth/realms/idp_dacd/account/
+* saml request: http://localhost:8080/auth/realms/idp_dacd/protocol/saml
+* metadata: http://localhost:8080/auth/realms/idp_dacd/protocol/saml/descriptor
+
+
+
+
+
diff --git a/demo-tornado/README.txt b/demo-tornado/README.txt
new file mode 100644
index 00000000..dd4418ca
--- /dev/null
+++ b/demo-tornado/README.txt
@@ -0,0 +1,7 @@
+Fully-working tornado-demo.
+
+ABOUT ISSSUE
+This is only a demo, some issues about session still remain.
+
+PRODUCTION
+Remember also to disable debugging in production.
diff --git a/demo-tornado/Settings.py b/demo-tornado/Settings.py
new file mode 100644
index 00000000..4b80459a
--- /dev/null
+++ b/demo-tornado/Settings.py
@@ -0,0 +1,6 @@
+import os
+
+BASE_DIR = os.path.dirname(__file__)
+
+SAML_PATH = os.path.join(BASE_DIR, 'saml')
+TEMPLATE_PATH = os.path.join(BASE_DIR, 'templates')
diff --git a/demo-tornado/requirements.txt b/demo-tornado/requirements.txt
new file mode 100644
index 00000000..99192d20
--- /dev/null
+++ b/demo-tornado/requirements.txt
@@ -0,0 +1,13 @@
+Click==7.0
+defusedxml==0.5.0
+isodate==0.6.0
+itsdangerous==1.1.0
+Jinja2==2.10.1
+lxml==4.3.3
+MarkupSafe==1.1.1
+pkgconfig==1.5.1
+python3-saml==1.6.0
+six==1.12.0
+tornado==6.0.2
+Werkzeug==0.15.2
+xmlsec==1.3.3
diff --git a/demo-tornado/saml/advanced_settings (copia).json b/demo-tornado/saml/advanced_settings (copia).json
new file mode 100644
index 00000000..3115e17e
--- /dev/null
+++ b/demo-tornado/saml/advanced_settings (copia).json
@@ -0,0 +1,33 @@
+{
+ "security": {
+ "nameIdEncrypted": false,
+ "authnRequestsSigned": false,
+ "logoutRequestSigned": false,
+ "logoutResponseSigned": false,
+ "signMetadata": false,
+ "wantMessagesSigned": false,
+ "wantAssertionsSigned": false,
+ "wantNameId" : true,
+ "wantNameIdEncrypted": false,
+ "wantAssertionsEncrypted": false,
+ "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
+ "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
+ },
+ "contactPerson": {
+ "technical": {
+ "givenName": "technical_name",
+ "emailAddress": "technical@example.com"
+ },
+ "support": {
+ "givenName": "support_name",
+ "emailAddress": "support@example.com"
+ }
+ },
+ "organization": {
+ "en-US": {
+ "name": "sp_test",
+ "displayname": "SP test",
+ "url": "http://sp.example.com"
+ }
+ }
+}
\ No newline at end of file
diff --git a/demo-tornado/saml/advanced_settings.json b/demo-tornado/saml/advanced_settings.json
new file mode 100644
index 00000000..e14e1170
--- /dev/null
+++ b/demo-tornado/saml/advanced_settings.json
@@ -0,0 +1,36 @@
+{
+ "security": {
+ "nameIdEncrypted": false,
+ "authnRequestsSigned": true,
+ "logoutRequestSigned": true,
+ "logoutResponseSigned": true,
+ "signMetadata": {
+ "keyFileName": "metadata.key",
+ "certFileName": "metadata.crt"
+ },
+ "wantMessagesSigned": false,
+ "wantAssertionsSigned": true,
+ "wantNameId" : true,
+ "wantNameIdEncrypted": false,
+ "wantAssertionsEncrypted": false,
+ "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
+ "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
+ },
+ "contactPerson": {
+ "technical": {
+ "givenName": "technical_name",
+ "emailAddress": "technical@example.com"
+ },
+ "support": {
+ "givenName": "support_name",
+ "emailAddress": "support@example.com"
+ }
+ },
+ "organization": {
+ "en-US": {
+ "name": "sp_test",
+ "displayname": "SP test",
+ "url": "http://sp.example.com"
+ }
+ }
+}
diff --git a/demo-tornado/saml/certs/README b/demo-tornado/saml/certs/README
new file mode 100644
index 00000000..7e837fb9
--- /dev/null
+++ b/demo-tornado/saml/certs/README
@@ -0,0 +1,13 @@
+Take care of this folder that could contain private key. Be sure that this folder never is published.
+
+Onelogin Python Toolkit expects that certs for the SP could be stored in this folder as:
+
+ * sp.key Private Key
+ * sp.crt Public cert
+ * sp_new.crt Future Public cert
+
+
+Also you can use other cert to sign the metadata of the SP using the:
+
+ * metadata.key
+ * metadata.crt
diff --git a/demo-tornado/saml/settings (copia).json b/demo-tornado/saml/settings (copia).json
new file mode 100644
index 00000000..ec40b674
--- /dev/null
+++ b/demo-tornado/saml/settings (copia).json
@@ -0,0 +1,30 @@
+{
+ "strict": true,
+ "debug": true,
+ "sp": {
+ "entityId": "https:///metadata/",
+ "assertionConsumerService": {
+ "url": "https:///?acs",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ },
+ "singleLogoutService": {
+ "url": "https:///?sls",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
+ "x509cert": "",
+ "privateKey": ""
+ },
+ "idp": {
+ "entityId": "https://app.onelogin.com/saml/metadata/",
+ "singleSignOnService": {
+ "url": "https://app.onelogin.com/trust/saml2/http-post/sso/",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "singleLogoutService": {
+ "url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "x509cert": ""
+ }
+}
diff --git a/demo-tornado/saml/settings (seconda copia).json b/demo-tornado/saml/settings (seconda copia).json
new file mode 100644
index 00000000..49a626b6
--- /dev/null
+++ b/demo-tornado/saml/settings (seconda copia).json
@@ -0,0 +1,30 @@
+{
+ "strict": true,
+ "debug": true,
+ "sp": {
+ "entityId": "http://0.0.0.0:8000/metadata/",
+ "assertionConsumerService": {
+ "url": "http://0.0.0.0:8000/?acs",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ },
+ "singleLogoutService": {
+ "url": "http://0.0.0.0:8000/?sls",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
+ "x509cert": "",
+ "privateKey": ""
+ },
+ "idp": {
+ "entityId": "http://localhost:8080/auth/realms/idp_dacd",
+ "singleSignOnService": {
+ "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "singleLogoutService": {
+ "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "x509cert": "MIICnzCCAYcCBgFqC3f1FDANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhpZHBfZGFjZDAeFw0xOTA0MTEwODE0MzJaFw0yOTA0MTEwODE2MTJaMBMxETAPBgNVBAMMCGlkcF9kYWNkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuhRERlbbwUxabS4lnGFOpxBKACpcwQwOSvyCKD3bLDvzfQIRh1x3RDq+6xxBcdP86PKRYjwZc/64HYFUbe9FCDLf1SDQz7XTtpftydRrTUydVaj+3FHhyCLcLkJldMVneVMZWIXSzUISvcURL3sr6HVJ74Uy0KPPOo3vQQ6RRaNaB3RCdZaNbzxjbG5eBgSvWHGE+vkozHpS7y6LHXDP1wXenH65RRrat8PQ/Qp4WVi2U5rVfA2SFbcwohjaJ+vOYH+oARnk5a2IosAMHBtONorPnPrcV7MqUXX7q19hnlFRZA34YVcF6kkWWsS1V/mRm2kK6EE/mABvX4mrefWg+QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQARKkt9TKg5jLLc7HZSYxHD0MGqDQ8jMAXjpYYaEoIGyYnE31WOEG6HlzHFrkp5ioBT8vTIfejpGGkcL0qspSjQRBYH1eHOEToFo2vhmr0yDUtePNVcQtRN0ImAwNvKU/PciSi4dX0Y8Z/VfeRn8k979o0X82xZSYIxLhKlFp9toOHz0pNTU0Ie4h1zxcPd0L7KAn5eQPHoJwXsdWkM9aoZtCQCjbiexpfOdEOfnUn8QisinAWqcC/gKl9XG7XU4P5cevTeqZgnK0W+ilJVkkJX2zZWlAji+0PKLGr3BgJCfkwUcsm1C8U2ysTGkW1mZHfKVzK2NPA0bSlGgZwbvki+"
+ }
+}
diff --git a/demo-tornado/saml/settings.json b/demo-tornado/saml/settings.json
new file mode 100644
index 00000000..c91e5cc9
--- /dev/null
+++ b/demo-tornado/saml/settings.json
@@ -0,0 +1,30 @@
+{
+ "strict": true,
+ "debug": true,
+ "sp": {
+ "entityId": "http://localhost:8000/metadata/",
+ "assertionConsumerService": {
+ "url": "http://localhost:8000/?acs",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ },
+ "singleLogoutService": {
+ "url": "http://localhost:8000/?sls",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
+ "x509cert": "",
+ "privateKey": ""
+ },
+ "idp": {
+ "entityId": "http://localhost:8080/auth/realms/idp_dacd",
+ "singleSignOnService": {
+ "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "singleLogoutService": {
+ "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ },
+ "x509cert": "MIICnzCCAYcCBgFqC3f1FDANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhpZHBfZGFjZDAeFw0xOTA0MTEwODE0MzJaFw0yOTA0MTEwODE2MTJaMBMxETAPBgNVBAMMCGlkcF9kYWNkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuhRERlbbwUxabS4lnGFOpxBKACpcwQwOSvyCKD3bLDvzfQIRh1x3RDq+6xxBcdP86PKRYjwZc/64HYFUbe9FCDLf1SDQz7XTtpftydRrTUydVaj+3FHhyCLcLkJldMVneVMZWIXSzUISvcURL3sr6HVJ74Uy0KPPOo3vQQ6RRaNaB3RCdZaNbzxjbG5eBgSvWHGE+vkozHpS7y6LHXDP1wXenH65RRrat8PQ/Qp4WVi2U5rVfA2SFbcwohjaJ+vOYH+oARnk5a2IosAMHBtONorPnPrcV7MqUXX7q19hnlFRZA34YVcF6kkWWsS1V/mRm2kK6EE/mABvX4mrefWg+QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQARKkt9TKg5jLLc7HZSYxHD0MGqDQ8jMAXjpYYaEoIGyYnE31WOEG6HlzHFrkp5ioBT8vTIfejpGGkcL0qspSjQRBYH1eHOEToFo2vhmr0yDUtePNVcQtRN0ImAwNvKU/PciSi4dX0Y8Z/VfeRn8k979o0X82xZSYIxLhKlFp9toOHz0pNTU0Ie4h1zxcPd0L7KAn5eQPHoJwXsdWkM9aoZtCQCjbiexpfOdEOfnUn8QisinAWqcC/gKl9XG7XU4P5cevTeqZgnK0W+ilJVkkJX2zZWlAji+0PKLGr3BgJCfkwUcsm1C8U2ysTGkW1mZHfKVzK2NPA0bSlGgZwbvki+"
+ }
+}
diff --git a/demo-tornado/templates/attrs.html b/demo-tornado/templates/attrs.html
new file mode 100644
index 00000000..e2429739
--- /dev/null
+++ b/demo-tornado/templates/attrs.html
@@ -0,0 +1,35 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+{% if paint_logout %}
+ {% if attributes %}
+ You have the following attributes:
+
+
+ Name Values
+
+
+ {% for attr, i in attributes %}
+ {% if i == 0 %}
+ {{ attr }}
+
+ {% end %}
+ {% if i == 1 %}
+ {% for val in attr %}
+ - {{ val }}
+ {% end %}
+ {% end %}
+
+ {% end %}
+
+
+ {% else %}
+ You don't have any attributes
+ {% end %}
+ Logout
+{% else %}
+ Login and access again to this page
+{% end %}
+
+{% end %}
diff --git a/demo-tornado/templates/base.html b/demo-tornado/templates/base.html
new file mode 100644
index 00000000..e403a8ef
--- /dev/null
+++ b/demo-tornado/templates/base.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+ A Python SAML Toolkit by OneLogin demo
+
+
+
+
+
+
+
+
+
+ A Python SAML Toolkit by OneLogin demo
+
+ {% block content %}{% end %}
+
+
+
diff --git a/demo-tornado/templates/index.html b/demo-tornado/templates/index.html
new file mode 100644
index 00000000..52bee968
--- /dev/null
+++ b/demo-tornado/templates/index.html
@@ -0,0 +1,66 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+{% if errors %}
+
+ Errors:
+
+ {% for err in errors %}
+ - {{err}}
+ {% end %}
+
+
+{% end %}
+
+{% if not_auth_warn %}
+ Not authenticated
+{% end %}
+
+{% if success_slo %}
+ Successfully logged out
+{% end %}
+
+{% if paint_logout %}
+ {% if attributes %}
+
+
+ Name Values
+
+
+ {% for attr in attributes %}
+
+ {{ attr[0] }}
+
+
+ {% for elem in attr[1] %}
+ - {{ elem }}
+ {% end %}
+
+
+ {% end %}
+
+
+
+
+
+ {% else %}
+ You don't have any attributes
+ {% end %}
+ Logout
+{% else %}
+ Login Login and access to attrs page
+{% end %}
+
+{% end %}
diff --git a/demo-tornado/views.py b/demo-tornado/views.py
new file mode 100644
index 00000000..5ac3e03a
--- /dev/null
+++ b/demo-tornado/views.py
@@ -0,0 +1,167 @@
+import tornado.ioloop
+import tornado.web
+import Settings
+import tornado.httpserver
+import tornado.httputil
+
+from onelogin.saml2.auth import OneLogin_Saml2_Auth
+from onelogin.saml2.settings import OneLogin_Saml2_Settings
+from onelogin.saml2.utils import OneLogin_Saml2_Utils
+
+##Global session info
+session = {}
+
+class Application(tornado.web.Application):
+ def __init__(self):
+ handlers = [
+ (r"/", IndexHandler),
+ (r"/attrs", AttrsHandler),
+ (r"/metadata",MetadataHandler),
+ ]
+ settings = {
+ "template_path": Settings.TEMPLATE_PATH,
+ "saml_path": Settings.SAML_PATH,
+ "autorealod": True,
+ "debug": True
+ }
+ tornado.web.Application.__init__(self, handlers, **settings)
+
+
+class IndexHandler(tornado.web.RequestHandler):
+ def post(self):
+ req = prepare_tornado_request(self.request)
+ auth = init_saml_auth(req)
+ attributes = False
+ paint_logout = False
+
+ auth.process_response()
+ errors = auth.get_errors()
+ not_auth_warn = not auth.is_authenticated()
+
+ if len(errors) == 0:
+ session['samlUserdata'] = auth.get_attributes()
+ session['samlNameId'] = auth.get_nameid()
+ session['samlSessionIndex'] = auth.get_session_index()
+ self_url = OneLogin_Saml2_Utils.get_self_url(req)
+ if 'RelayState' in self.request.arguments and self_url != self.request.arguments['RelayState'][0].decode('utf-8'):
+ return self.redirect(self.request.arguments['RelayState'][0].decode('utf-8'))
+
+ if 'samlUserdata' in session:
+ paint_logout = True
+ if len(session['samlUserdata']) > 0:
+ attributes = session['samlUserdata'].items()
+
+ self.render('index.html',errors=errors,not_auth_warn=not_auth_warn,attributes=attributes,paint_logout=paint_logout)
+
+ def get(self):
+ req = prepare_tornado_request(self.request)
+ auth = init_saml_auth(req)
+ errors = []
+ not_auth_warn = False
+ success_slo = False
+ attributes = False
+ paint_logout = False
+
+ if 'sso' in req['get_data']:
+ print('-sso-')
+ return self.redirect(auth.login())
+ elif 'sso2' in req['get_data']:
+ print('-sso2-')
+ return_to = '%s/attrs' % self.request.host
+ return self.redirect(auth.login(return_to))
+ elif 'slo' in req['get_data']:
+ print('-slo-')
+ name_id = None
+ session_index = None
+ if 'samlNameId' in session:
+ name_id = session['samlNameId']
+ if 'samlSessionIndex' in session:
+ session_index = session['samlSessionIndex']
+ return self.redirect(auth.logout(name_id=name_id, session_index=session_index))
+ elif 'acs' in req['get_data']:
+ print('-acs-')
+ auth.process_response()
+ errors = auth.get_errors()
+ not_auth_warn = not auth.is_authenticated()
+ if len(errors) == 0:
+ session['samlUserdata'] = auth.get_attributes()
+ session['samlNameId'] = auth.get_nameid()
+ session['samlSessionIndex'] = auth.get_session_index()
+ self_url = OneLogin_Saml2_Utils.get_self_url(req)
+ if 'RelayState' in self.request.arguments and self_url != self.request.arguments['RelayState'][0].decode('utf-8'):
+ return self.redirect(auth.redirect_to(self.request.arguments['RelayState'][0].decode('utf-8')))
+ elif 'sls' in req['get_data']:
+ print('-sls-')
+ dscb = lambda: session.clear() ## clear out the session
+ url = auth.process_slo(delete_session_cb=dscb)
+ errors = auth.get_errors()
+ if len(errors) == 0:
+ if url is not None:
+ return self.redirect(url)
+ else:
+ success_slo = True
+
+ if 'samlUserdata' in session:
+ print('-samlUserdata-')
+ paint_logout = True
+ if len(session['samlUserdata']) > 0:
+ attributes = session['samlUserdata'].items()
+ print("ATTRIBUTES", attributes)
+ self.render('index.html',errors=errors,not_auth_warn=not_auth_warn,success_slo=success_slo,attributes=attributes,paint_logout=paint_logout)
+
+class AttrsHandler(tornado.web.RequestHandler):
+ def get(self):
+ paint_logout = False
+ attributes = False
+
+ if 'samlUserdata' in session:
+ paint_logout = True
+ if len(session['samlUserdata']) > 0:
+ attributes = session['samlUserdata'].items()
+
+ self.render('attrs.html',paint_logout=paint_logout,attributes=attributes)
+
+class MetadataHandler(tornado.web.RequestHandler):
+ def get(self):
+ req = prepare_tornado_request(self.request)
+ auth = init_saml_auth(req)
+ saml_settings = auth.get_settings()
+ #saml_settings = OneLogin_Saml2_Settings(settings=None, custom_base_path=settings.SAML_FOLDER, sp_validation_only=True)
+ metadata = saml_settings.get_sp_metadata()
+ errors = saml_settings.validate_metadata(metadata)
+
+ if len(errors) == 0:
+ #resp = HttpResponse(content=metadata, content_type='text/xml')
+ self.set_header('Content-Type','text/xml')
+ self.write(metadata)
+ else:
+ #resp = HttpResponseServerError(content=', '.join(errors))
+ self.write(', '.join(errors))
+ #return resp
+
+def prepare_tornado_request(request):
+
+ dataDict = {}
+ for key in request.arguments:
+ dataDict[key] = request.arguments[key][0].decode('utf-8')
+
+ result = {
+ 'https': 'on' if request == 'https' else 'off',
+ 'http_host': tornado.httputil.split_host_and_port(request.host)[0],
+ 'script_name': request.path,
+ 'server_port': tornado.httputil.split_host_and_port(request.host)[1],
+ 'get_data': dataDict,
+ 'post_data': dataDict,
+ 'query_string': request.query
+ }
+ return result
+
+def init_saml_auth(req):
+ auth = OneLogin_Saml2_Auth(req, custom_base_path=Settings.SAML_PATH)
+ return auth
+
+if __name__ == "__main__":
+ app = Application()
+ http_server = tornado.httpserver.HTTPServer(app)
+ http_server.listen(8000)
+ tornado.ioloop.IOLoop.instance().start()
From e46e3353c6e55c71cd000fc84170de50b17dcefc Mon Sep 17 00:00:00 2001
From: davideZanin
Date: Tue, 23 Apr 2019 14:32:18 +0200
Subject: [PATCH 113/331] docs updated
---
README.md | 105 +++++++++++++++++-
demo-tornado/Docs/DEVELOPING.md | 82 --------------
demo-tornado/requirements.txt | 5 -
.../saml/advanced_settings (copia).json | 33 ------
demo-tornado/saml/settings (copia).json | 30 -----
.../saml/settings (seconda copia).json | 30 -----
6 files changed, 104 insertions(+), 181 deletions(-)
delete mode 100644 demo-tornado/Docs/DEVELOPING.md
delete mode 100644 demo-tornado/saml/advanced_settings (copia).json
delete mode 100644 demo-tornado/saml/settings (copia).json
delete mode 100644 demo-tornado/saml/settings (seconda copia).json
diff --git a/README.md b/README.md
index 404712e2..993105c9 100644
--- a/README.md
+++ b/README.md
@@ -172,6 +172,9 @@ This folder contains a Flask project that will be used as demo to show how to ad
This folder contains a Pyramid project that will be used as demo to show how to add SAML support to the [Pyramid Web Framework](http://docs.pylonsproject.org/projects/pyramid/en/latest/). ``\_\_init__.py`` is the main file that configures the app and its routes, ``views.py`` is where all the logic and SAML handling takes place, and the templates are stored in the ``templates`` folder. The ``saml`` folder is the same as in the other two demos.
+#### demo-tornado ####
+
+This folder contains a Tornado project that will be used as demo to show how to add SAML support to the Tornado Framework. ``views.py`` (with its ``settings.py``) is the main Flask file that has all the code, this file uses the templates stored at the ``templates`` folder. In the ``saml`` folder we found the ``certs`` folder to store the X.509 public and private key, and the SAML toolkit settings (``settings.json`` and ``advanced_settings.json``).
#### setup.py ####
@@ -1085,7 +1088,7 @@ For more info, look at the source code. Each method is documented and details ab
Demos included in the toolkit
-----------------------------
-The toolkit includes 2 demos to teach how use the toolkit (A Django and a Flask project), take a look on it.
+The toolkit includes 3 demos to teach how use the toolkit (A Django, Flask and a Tornado project), take a look on it.
Demos require that SP and IdP are well configured before test it, so edit the settings files.
Notice that each python framework has it own way to handle routes/urls and process request, so focus on
@@ -1165,6 +1168,106 @@ First we need to edit the ``saml/settings.json`` file, configure the SP part and
Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
+#### How it works ####
+
+ 1. First time you access to the main view (http://localhost:8000), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
+
+ 2. When you click:
+
+ 2.1 in the first link, we access to ``/?sso`` (index view). An ``AuthNRequest`` is sent to the IdP, we authenticate at the IdP and then a Response is sent through the user's client to the SP, specifically the Assertion Consumer Service view: ``/?acs``. Notice that a ``RelayState`` parameter is set to the url that initiated the process, the index view.
+
+ 2.2 in the second link we access to ``/?attrs`` (attrs view), we will expetience have the same process described at 2.1 with the diference that as ``RelayState`` is set the ``attrs`` url.
+
+ 3. The SAML Response is processed in the ACS ``/?acs``, if the Response is not valid, the process stops here and a message is shown. Otherwise we are redirected to the ``RelayState`` view. a) / or b) ``/?attrs``
+
+ 4. We are logged in the app and the user attributes are showed. At this point, we can test the single log out functionality.
+
+ The single log out functionality could be tested by 2 ways.
+
+ 5.1 SLO Initiated by SP. Click on the ``logout`` link at the SP, after that a Logout Request is sent to the IdP, the session at the IdP is closed and replies through the client to the SP with a Logout Response (sent to the Single Logout Service endpoint). The SLS endpoint ``/?sls`` of the SP process the Logout Response and if is valid, close the user session of the local app. Notice that the SLO Workflow starts and ends at the SP.
+
+ 5.2 SLO Initiated by IdP. In this case, the action takes place on the IdP side, the logout process is initiated at the IdP, sends a Logout Request to the SP (SLS endpoint, ``/?sls``). The SLS endpoint of the SP process the Logout Request and if is valid, close the session of the user at the local app and send a Logout Response to the IdP (to the SLS endpoint of the IdP). The IdP receives the Logout Response, process it and close the session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP.
+
+Notice that all the SAML Requests and Responses are handled at a unique view (index) and how GET parameters are used to know the action that must be done.
+
+### Demo Tornado ###
+
+You'll need a virtualenv with the toolkit installed on it.
+
+First of all you need some packages, execute:
+```
+apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
+```
+
+To run the demo you need to install the requirements first. Load your
+virtualenv and execute:
+```
+ pip install -r demo-tornado/requirements.txt
+```
+
+
+This will install tornado and its dependencies. Once it has finished, you have to complete the configuration
+of the toolkit. You'll find it at `demo-tornado/saml/settings.json`
+
+Now, with the virtualenv loaded, you can run the demo like this:
+```
+ cd demo-tornado
+ python views.py
+```
+
+You'll have the demo running at http://localhost:8000
+
+#### Content ####
+
+The tornado project contains:
+
+* ***views.py*** Is the main flask file, where or the SAML handle take place.
+
+* ***settings.py*** Contains the base path and the path where is located the ``saml`` folder and the ``template`` folder
+
+* ***templates***. Is the folder where tornado stores the templates of the project. It was implemented a base.html template that is extended by index.html and attrs.html, the templates of our simple demo that shows messages, user attributes when available and login and logout links.
+
+* ***saml*** Is a folder that contains the 'certs' folder that could be used to store the X.509 public and private key, and the saml toolkit settings (settings.json and advanced_settings.json).
+
+#### SP setup ####
+
+The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-tornado``, it uses the first method.
+
+In the ``settings.py`` file we define the ``SAML_PATH``, that will target to the ``saml`` folder. We require it in order to load the settings files.
+
+First we need to edit the ``saml/settings.json`` file, configure the SP part and review the metadata of the IdP and complete the IdP info. Later edit the ``saml/advanced_settings.json`` files and configure the how the toolkit will work. Check the settings section of this document if you have any doubt.
+
+#### IdP setup ####
+
+Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
+
+#####Test with keycloack#####
+
+You can test your SP with every compatible IdP, for example Keycloack by Red Hat (Check if you need also authorization and not only authentication )
+
+###### Install Docker ######
+
+Install docker as suggested by [docker guide](https://docs.docker.com/install/linux/docker-ce/ubuntu/)
+
+###### Keycloack starting ######
+
+First run:
+* docker run --name keycloackContainer -d -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_VENDOR=H2 jboss/keycloak
+
+After first run:
+* sudo docker start keycloackContainer
+
+Remember to stop keycloack after usage:
+* sudo docker stop keycloackContainer
+
+
+###### Keycloack useful urls ######
+
+* master: http://localhost:8080/auth/admin
+* users: http://localhost:8080/auth/realms/idp_dacd/account/
+* saml request: http://localhost:8080/auth/realms/idp_dacd/protocol/saml
+* metadata: http://localhost:8080/auth/realms/idp_dacd/protocol/saml/descriptor
+
#### How it works ####
1. First time you access to the main view (http://localhost:8000), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
diff --git a/demo-tornado/Docs/DEVELOPING.md b/demo-tornado/Docs/DEVELOPING.md
deleted file mode 100644
index 27ba1243..00000000
--- a/demo-tornado/Docs/DEVELOPING.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# OneLogin's SAML Python Toolkit (compatible with Python3)
-
-Installation
-------------
-
-### Dependencies ###
-
- * python 3.6
- * apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl
- * pip install xmlsec
- * pip install isodate
- * pip install defusedxml
- * pip install python3-saml
- * pip install tornado
-
-
-***Virtualenv***
-
-The use of virtualenv/virtualenvwrapper is highly recommended.
-
-### Create certificates ###
-
-in saml/cert run :
- * openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout sp.key
- * openssl req -new -x509 -days 3652 -nodes -out metadata.crt -keyout metadata.key
-
-### Useful extesion for SAML messages ###
-* [SAML Chrome Panel 1.8.9](https://chrome.google.com/webstore/detail/saml-chrome-panel/paijfdbeoenhembfhkhllainmocckace/related)
-
-
-
-# Test with keycloack idp
-
-Installation
-------------
-
-### Install Docker ###
-* sudo apt-get remove docker docker-engine docker.io containerd runc
-
-* sudo apt-get update
-
-* sudo apt-get install \
- apt-transport-https \
- ca-certificates \
- curl \
- gnupg-agent \
- software-properties-common
-* curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
-
-* sudo add-apt-repository \
- "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
- $(lsb_release -cs) \
- stable"
-
-* sudo apt-get update
-
-* sudo apt-get install docker-ce docker-ce-cli containerd.io
-
-* sudo docker run hello-world
-
-
-### Keycloack starting ###
-First run only:
-* docker run --name keycloackContainer -d -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_VENDOR=H2 jboss/keycloak
-
-After first run:
-* sudo docker start keycloackContainer
-
-Remember to stop keycloack after usage:
-* sudo docker stop keycloackContainer
-
-
-### Keycloack useful urls ###
-* master: http://localhost:8080/auth/admin
-* users: http://localhost:8080/auth/realms/idp_dacd/account/
-* saml request: http://localhost:8080/auth/realms/idp_dacd/protocol/saml
-* metadata: http://localhost:8080/auth/realms/idp_dacd/protocol/saml/descriptor
-
-
-
-
-
diff --git a/demo-tornado/requirements.txt b/demo-tornado/requirements.txt
index 99192d20..66787013 100644
--- a/demo-tornado/requirements.txt
+++ b/demo-tornado/requirements.txt
@@ -1,13 +1,8 @@
-Click==7.0
defusedxml==0.5.0
isodate==0.6.0
-itsdangerous==1.1.0
-Jinja2==2.10.1
lxml==4.3.3
-MarkupSafe==1.1.1
pkgconfig==1.5.1
python3-saml==1.6.0
six==1.12.0
tornado==6.0.2
-Werkzeug==0.15.2
xmlsec==1.3.3
diff --git a/demo-tornado/saml/advanced_settings (copia).json b/demo-tornado/saml/advanced_settings (copia).json
deleted file mode 100644
index 3115e17e..00000000
--- a/demo-tornado/saml/advanced_settings (copia).json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "security": {
- "nameIdEncrypted": false,
- "authnRequestsSigned": false,
- "logoutRequestSigned": false,
- "logoutResponseSigned": false,
- "signMetadata": false,
- "wantMessagesSigned": false,
- "wantAssertionsSigned": false,
- "wantNameId" : true,
- "wantNameIdEncrypted": false,
- "wantAssertionsEncrypted": false,
- "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
- "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
- },
- "contactPerson": {
- "technical": {
- "givenName": "technical_name",
- "emailAddress": "technical@example.com"
- },
- "support": {
- "givenName": "support_name",
- "emailAddress": "support@example.com"
- }
- },
- "organization": {
- "en-US": {
- "name": "sp_test",
- "displayname": "SP test",
- "url": "http://sp.example.com"
- }
- }
-}
\ No newline at end of file
diff --git a/demo-tornado/saml/settings (copia).json b/demo-tornado/saml/settings (copia).json
deleted file mode 100644
index ec40b674..00000000
--- a/demo-tornado/saml/settings (copia).json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "strict": true,
- "debug": true,
- "sp": {
- "entityId": "https:///metadata/",
- "assertionConsumerService": {
- "url": "https:///?acs",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
- },
- "singleLogoutService": {
- "url": "https:///?sls",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
- },
- "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
- "x509cert": "",
- "privateKey": ""
- },
- "idp": {
- "entityId": "https://app.onelogin.com/saml/metadata/",
- "singleSignOnService": {
- "url": "https://app.onelogin.com/trust/saml2/http-post/sso/",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
- },
- "singleLogoutService": {
- "url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
- },
- "x509cert": ""
- }
-}
diff --git a/demo-tornado/saml/settings (seconda copia).json b/demo-tornado/saml/settings (seconda copia).json
deleted file mode 100644
index 49a626b6..00000000
--- a/demo-tornado/saml/settings (seconda copia).json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "strict": true,
- "debug": true,
- "sp": {
- "entityId": "http://0.0.0.0:8000/metadata/",
- "assertionConsumerService": {
- "url": "http://0.0.0.0:8000/?acs",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
- },
- "singleLogoutService": {
- "url": "http://0.0.0.0:8000/?sls",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
- },
- "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
- "x509cert": "",
- "privateKey": ""
- },
- "idp": {
- "entityId": "http://localhost:8080/auth/realms/idp_dacd",
- "singleSignOnService": {
- "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
- },
- "singleLogoutService": {
- "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
- "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
- },
- "x509cert": "MIICnzCCAYcCBgFqC3f1FDANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhpZHBfZGFjZDAeFw0xOTA0MTEwODE0MzJaFw0yOTA0MTEwODE2MTJaMBMxETAPBgNVBAMMCGlkcF9kYWNkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuhRERlbbwUxabS4lnGFOpxBKACpcwQwOSvyCKD3bLDvzfQIRh1x3RDq+6xxBcdP86PKRYjwZc/64HYFUbe9FCDLf1SDQz7XTtpftydRrTUydVaj+3FHhyCLcLkJldMVneVMZWIXSzUISvcURL3sr6HVJ74Uy0KPPOo3vQQ6RRaNaB3RCdZaNbzxjbG5eBgSvWHGE+vkozHpS7y6LHXDP1wXenH65RRrat8PQ/Qp4WVi2U5rVfA2SFbcwohjaJ+vOYH+oARnk5a2IosAMHBtONorPnPrcV7MqUXX7q19hnlFRZA34YVcF6kkWWsS1V/mRm2kK6EE/mABvX4mrefWg+QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQARKkt9TKg5jLLc7HZSYxHD0MGqDQ8jMAXjpYYaEoIGyYnE31WOEG6HlzHFrkp5ioBT8vTIfejpGGkcL0qspSjQRBYH1eHOEToFo2vhmr0yDUtePNVcQtRN0ImAwNvKU/PciSi4dX0Y8Z/VfeRn8k979o0X82xZSYIxLhKlFp9toOHz0pNTU0Ie4h1zxcPd0L7KAn5eQPHoJwXsdWkM9aoZtCQCjbiexpfOdEOfnUn8QisinAWqcC/gKl9XG7XU4P5cevTeqZgnK0W+ilJVkkJX2zZWlAji+0PKLGr3BgJCfkwUcsm1C8U2ysTGkW1mZHfKVzK2NPA0bSlGgZwbvki+"
- }
-}
From 43118e7c41b8f4a34232780cdf5c1700a164b8a8 Mon Sep 17 00:00:00 2001
From: davideZanin
Date: Tue, 23 Apr 2019 14:42:16 +0200
Subject: [PATCH 114/331] docs updated
---
demo-tornado/README.md | 9 +++++++++
demo-tornado/README.txt | 7 -------
2 files changed, 9 insertions(+), 7 deletions(-)
create mode 100644 demo-tornado/README.md
delete mode 100644 demo-tornado/README.txt
diff --git a/demo-tornado/README.md b/demo-tornado/README.md
new file mode 100644
index 00000000..aab7ef19
--- /dev/null
+++ b/demo-tornado/README.md
@@ -0,0 +1,9 @@
+#Tornado Demo#
+Fully-working tornado-demo.
+
+###ABOUT ISSUE###
+This is only a demo, some issues about session still remain.
+Actually the session is global.
+
+###PRODUCTION###
+Remember to disable debugging in production.
diff --git a/demo-tornado/README.txt b/demo-tornado/README.txt
deleted file mode 100644
index dd4418ca..00000000
--- a/demo-tornado/README.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-Fully-working tornado-demo.
-
-ABOUT ISSSUE
-This is only a demo, some issues about session still remain.
-
-PRODUCTION
-Remember also to disable debugging in production.
From 47ba1cad2dd44ab10f1962e67ba4d46c85c29e94 Mon Sep 17 00:00:00 2001
From: davideZanin
Date: Tue, 23 Apr 2019 14:52:46 +0200
Subject: [PATCH 115/331] solved formatting error
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 993105c9..6b8a7449 100644
--- a/README.md
+++ b/README.md
@@ -1241,7 +1241,7 @@ First we need to edit the ``saml/settings.json`` file, configure the SP part and
Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
-#####Test with keycloack#####
+##### Test with keycloack #####
You can test your SP with every compatible IdP, for example Keycloack by Red Hat (Check if you need also authorization and not only authentication )
From a46fddb33cbb0f2254fa7de09803b432a5fbd6f9 Mon Sep 17 00:00:00 2001
From: davideZanin
Date: Tue, 23 Apr 2019 15:35:25 +0200
Subject: [PATCH 116/331] ready to pull req
---
demo-tornado/README.md | 6 +++---
demo-tornado/saml/advanced_settings.json | 15 ++++++---------
demo-tornado/saml/settings.json | 16 ++++++++--------
3 files changed, 17 insertions(+), 20 deletions(-)
diff --git a/demo-tornado/README.md b/demo-tornado/README.md
index aab7ef19..428d192c 100644
--- a/demo-tornado/README.md
+++ b/demo-tornado/README.md
@@ -1,9 +1,9 @@
-#Tornado Demo#
+# Tornado Demo #
Fully-working tornado-demo.
-###ABOUT ISSUE###
+### About issues ###
This is only a demo, some issues about session still remain.
Actually the session is global.
-###PRODUCTION###
+### Production ###
Remember to disable debugging in production.
diff --git a/demo-tornado/saml/advanced_settings.json b/demo-tornado/saml/advanced_settings.json
index e14e1170..3115e17e 100644
--- a/demo-tornado/saml/advanced_settings.json
+++ b/demo-tornado/saml/advanced_settings.json
@@ -1,15 +1,12 @@
{
"security": {
"nameIdEncrypted": false,
- "authnRequestsSigned": true,
- "logoutRequestSigned": true,
- "logoutResponseSigned": true,
- "signMetadata": {
- "keyFileName": "metadata.key",
- "certFileName": "metadata.crt"
- },
+ "authnRequestsSigned": false,
+ "logoutRequestSigned": false,
+ "logoutResponseSigned": false,
+ "signMetadata": false,
"wantMessagesSigned": false,
- "wantAssertionsSigned": true,
+ "wantAssertionsSigned": false,
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
@@ -33,4 +30,4 @@
"url": "http://sp.example.com"
}
}
-}
+}
\ No newline at end of file
diff --git a/demo-tornado/saml/settings.json b/demo-tornado/saml/settings.json
index c91e5cc9..391b91c1 100644
--- a/demo-tornado/saml/settings.json
+++ b/demo-tornado/saml/settings.json
@@ -2,13 +2,13 @@
"strict": true,
"debug": true,
"sp": {
- "entityId": "http://localhost:8000/metadata/",
+ "entityId": "https:///metadata/",
"assertionConsumerService": {
- "url": "http://localhost:8000/?acs",
+ "url": "https:///?acs",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
"singleLogoutService": {
- "url": "http://localhost:8000/?sls",
+ "url": "https:///?sls",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
@@ -16,15 +16,15 @@
"privateKey": ""
},
"idp": {
- "entityId": "http://localhost:8080/auth/realms/idp_dacd",
+ "entityId": "https://app.onelogin.com/saml/metadata/",
"singleSignOnService": {
- "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
+ "url": "https://app.onelogin.com/trust/saml2/http-post/sso/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"singleLogoutService": {
- "url": "http://localhost:8080/auth/realms/idp_dacd/protocol/saml",
+ "url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
- "x509cert": "MIICnzCCAYcCBgFqC3f1FDANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhpZHBfZGFjZDAeFw0xOTA0MTEwODE0MzJaFw0yOTA0MTEwODE2MTJaMBMxETAPBgNVBAMMCGlkcF9kYWNkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuhRERlbbwUxabS4lnGFOpxBKACpcwQwOSvyCKD3bLDvzfQIRh1x3RDq+6xxBcdP86PKRYjwZc/64HYFUbe9FCDLf1SDQz7XTtpftydRrTUydVaj+3FHhyCLcLkJldMVneVMZWIXSzUISvcURL3sr6HVJ74Uy0KPPOo3vQQ6RRaNaB3RCdZaNbzxjbG5eBgSvWHGE+vkozHpS7y6LHXDP1wXenH65RRrat8PQ/Qp4WVi2U5rVfA2SFbcwohjaJ+vOYH+oARnk5a2IosAMHBtONorPnPrcV7MqUXX7q19hnlFRZA34YVcF6kkWWsS1V/mRm2kK6EE/mABvX4mrefWg+QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQARKkt9TKg5jLLc7HZSYxHD0MGqDQ8jMAXjpYYaEoIGyYnE31WOEG6HlzHFrkp5ioBT8vTIfejpGGkcL0qspSjQRBYH1eHOEToFo2vhmr0yDUtePNVcQtRN0ImAwNvKU/PciSi4dX0Y8Z/VfeRn8k979o0X82xZSYIxLhKlFp9toOHz0pNTU0Ie4h1zxcPd0L7KAn5eQPHoJwXsdWkM9aoZtCQCjbiexpfOdEOfnUn8QisinAWqcC/gKl9XG7XU4P5cevTeqZgnK0W+ilJVkkJX2zZWlAji+0PKLGr3BgJCfkwUcsm1C8U2ysTGkW1mZHfKVzK2NPA0bSlGgZwbvki+"
+ "x509cert": ""
}
-}
+}
\ No newline at end of file
From 064b7275fba1e5f39a9116ba1cdcc5d01fc34daa Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 26 Apr 2019 11:50:45 +0200
Subject: [PATCH 117/331] Update defusedxml
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 65ff747e..e93bccba 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@
install_requires=[
'isodate>=0.5.0',
'xmlsec>=0.6.0',
- 'defusedxml==0.5.0'
+ 'defusedxml>=0.5.0'
],
dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'],
extras_require={
From 527f4fe7d7ba15383c72678be6ecca1ab6a965d8 Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann"
Date: Wed, 8 May 2019 13:29:08 +0200
Subject: [PATCH 118/331] Make tests pass in 2020
moving this beyond 2038 requires fixes on 32-bit systems to avoid errors:
File "/home/abuild/rpmbuild/BUILD/python3-saml-1.6.0/src/onelogin/sam
.py", line 419, in parse_SAML_to_time
data = datetime.strptime(timestr, '%Y-%m-%dT%H:%M:%SZ')
TypeError: strptime() argument 1 must be string, not long
---
tests/data/metadata/metadata_settings1.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/data/metadata/metadata_settings1.xml b/tests/data/metadata/metadata_settings1.xml
index c9529f00..893e9679 100644
--- a/tests/data/metadata/metadata_settings1.xml
+++ b/tests/data/metadata/metadata_settings1.xml
@@ -1,6 +1,6 @@
From b0f2ac94e01dcb648abb2a8381070986c1410a65 Mon Sep 17 00:00:00 2001
From: Robert Ellegate
Date: Fri, 10 May 2019 11:20:41 -0400
Subject: [PATCH 119/331] Fix typos
Fixed typo in https://github.com/onelogin/python3-saml#option-1-download-from-github
Changed `Lastest release` to `Latest release`
Changed hyperlink to Python 2 version to match hyperlink in [onelogin/python-saml](https://github.com/onelogin/python-saml) repo
Changed hyperlink from `https://pypi.python.org/pypi/python-saml` to `https://github.com/onelogin/python-saml`
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 404712e2..b3f27804 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Add SAML support to your Python software using this library.
Forget those complicated libraries and use the open source library provided
and supported by OneLogin Inc.
-This version supports Python3. There is a separate version that only support Python2: [python-saml](https://pypi.python.org/pypi/python-saml)
+This version supports Python3. There is a separate version that only support Python2: [python-saml](https://github.com/onelogin/python-saml)
#### Warning ####
@@ -100,7 +100,7 @@ Review the ``setup.py`` file to know the version of the library that ``python3-s
The toolkit is hosted on GitHub. You can download it from:
- * Lastest release: https://github.com/onelogin/python3-saml/releases/latest
+ * Latest release: https://github.com/onelogin/python3-saml/releases/latest
* Master repo: https://github.com/onelogin/python3-saml/tree/master
Copy the core of the library ``(src/onelogin/saml2 folder)`` and merge the ``setup.py`` inside the Python application. (Each application has its structure so take your time to locate the Python SAML toolkit in the best place).
From 55b3f49be914de388a409b70478b10056c9eba24 Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Sun, 2 Jun 2019 01:06:05 -0400
Subject: [PATCH 120/331] Added get_in_response_to method to Response and
LogoutResponse classes
---
src/onelogin/saml2/logout_response.py | 5 ++++-
src/onelogin/saml2/response.py | 10 +++++++++-
tests/src/OneLogin/saml2_tests/response_test.py | 4 ++--
3 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 67a72f35..f8b81ff4 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -95,7 +95,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
security = self.__settings.get_security_data()
# Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided
- in_response_to = self.document.get('InResponseTo', None)
+ in_response_to = self.get_in_response_to()
if request_id is not None and in_response_to and in_response_to != request_id:
raise OneLogin_Saml2_ValidationError(
'The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id),
@@ -175,6 +175,9 @@ def build(self, in_response_to):
self.__logout_response = logout_response
+ def get_in_response_to(self):
+ return self.document.get('InResponseTo')
+
def get_response(self, deflate=True):
"""
Returns a Logout Response object.
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index a291a841..1ceaac32 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -122,7 +122,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
# Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
- in_response_to = self.document.get('InResponseTo', None)
+ in_response_to = self.get_in_response_to()
if in_response_to is not None and request_id is not None:
if in_response_to != request_id:
raise OneLogin_Saml2_ValidationError(
@@ -387,6 +387,14 @@ def get_authn_contexts(self):
authn_context_nodes = self.__query_assertion('/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef')
return [OneLogin_Saml2_XML.element_text(node) for node in authn_context_nodes]
+ def get_in_response_to(self):
+ """
+ Gets the ID of the request which this response is in response to
+ :returns: ID of AuthNRequest this Response is in response to or None if it is not present
+ :rtype: str
+ """
+ return self.document.get('InResponseTo')
+
def get_issuers(self):
"""
Gets the issuers (from message and from assertion)
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index b5937045..dfda45b8 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -75,8 +75,8 @@ def testGetXMLDocument(self):
xml = self.file_contents(join(self.data_path, 'responses', 'signed_message_response.xml.base64'))
response = OneLogin_Saml2_Response(settings, xml)
- prety_xml = self.file_contents(join(self.data_path, 'responses', 'pretty_signed_message_response.xml'))
- self.assertEqual(etree.tostring(response.get_xml_document(), encoding='unicode', pretty_print=True), prety_xml)
+ pretty_xml = self.file_contents(join(self.data_path, 'responses', 'pretty_signed_message_response.xml'))
+ self.assertEqual(etree.tostring(response.get_xml_document(), encoding='unicode', pretty_print=True), pretty_xml)
xml_2 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64'))
response_2 = OneLogin_Saml2_Response(settings, xml_2)
From 3002e0d32c73e86f0cc64acb8770bed3f7d14ed7 Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Sun, 2 Jun 2019 01:14:12 -0400
Subject: [PATCH 121/331] Added InResponseTo tests
---
.../src/OneLogin/saml2_tests/response_test.py | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index dfda45b8..d487c11b 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -407,7 +407,7 @@ def testGetNameIdData(self):
settings = OneLogin_Saml2_Settings(json_settings)
response_13 = OneLogin_Saml2_Response(settings, xml_6)
nameid_data_13 = response_13.get_nameid_data()
- nameid_data_13 = self.assertEqual(expected_nameid_data_5, nameid_data_13)
+ self.assertEqual(expected_nameid_data_5, nameid_data_13)
json_settings['strict'] = False
json_settings['security']['wantNameId'] = False
@@ -685,6 +685,22 @@ def testGetSessionNotOnOrAfter(self):
response_3 = OneLogin_Saml2_Response(settings, xml_3)
self.assertEqual(2696012228, response_3.get_session_not_on_or_after())
+ def testGetInResponseTo(self):
+ """
+ Tests the retrieval of the InResponseTo attribute
+ """
+
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+
+ # Response without an InResponseTo element should return None
+ xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
+ response = OneLogin_Saml2_Response(settings, xml)
+ self.assertIsNone(response.get_in_response_to())
+
+ xml_3 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64'))
+ response_3 = OneLogin_Saml2_Response(settings, xml_3)
+ self.assertEqual('ONELOGIN_be60b8caf8e9d19b7a3551b244f116c947ff247d', response_3.get_in_response_to())
+
def testIsInvalidXML(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response
From 754f6350259897ac858c035ab2b8ce0ac9bf136e Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Sun, 2 Jun 2019 01:17:09 -0400
Subject: [PATCH 122/331] Added method docstring
---
src/onelogin/saml2/logout_response.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index f8b81ff4..6dd0a6d8 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -176,6 +176,11 @@ def build(self, in_response_to):
self.__logout_response = logout_response
def get_in_response_to(self):
+ """
+ Gets the ID of the LogoutRequest which this response is in response to
+ :returns: ID of LogoutRequest this LogoutResponse is in response to or None if it is not present
+ :rtype: str
+ """
return self.document.get('InResponseTo')
def get_response(self, deflate=True):
From f4be65c0e649d2c34b27c54a82f12b9c3aa7b233 Mon Sep 17 00:00:00 2001
From: John Doe <34662+johndoe46@users.noreply.github.com>
Date: Tue, 18 Jun 2019 12:43:03 +0200
Subject: [PATCH 123/331] fix path in flask demo
---
demo-flask/index.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo-flask/index.py b/demo-flask/index.py
index 441dc83b..e7c88d75 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -11,7 +11,7 @@
app = Flask(__name__)
app.config['SECRET_KEY'] = 'onelogindemopytoolkit'
-app.config['SAML_PATH'] = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'saml')
+app.config['SAML_PATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'saml')
def init_saml_auth(req):
From ae90bac7c94c82c4a5625aff57163aab2a8098d4 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 26 Jun 2019 00:43:22 +0200
Subject: [PATCH 124/331] Adjusted acs endpoint to extract NameQualifier and
SPNameQualifier from SAMLResponse. Adjusted single logout service to provide
NameQualifier and SPNameQualifier to logout method. Add
getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from
Auth and LogoutRequest constructor to support SPNameQualifier parameter.
Align LogoutRequest constructor with SAML specs
---
README.md | 16 ++++-
demo-django/demo/views.py | 16 +++--
demo-flask/index.py | 17 +++--
src/onelogin/saml2/auth.py | 30 +++++++-
src/onelogin/saml2/logout_request.py | 23 ++++---
src/onelogin/saml2/response.py | 26 +++++++
...lid_response_with_namequalifier.xml.base64 | 1 +
tests/src/OneLogin/saml2_tests/auth_test.py | 67 ++++++++++++++++++
.../saml2_tests/logout_request_test.py | 1 -
.../src/OneLogin/saml2_tests/response_test.py | 68 +++++++++++++++++++
10 files changed, 242 insertions(+), 23 deletions(-)
create mode 100644 tests/data/responses/valid_response_with_namequalifier.xml.base64
diff --git a/README.md b/README.md
index b3f27804..52f3b923 100644
--- a/README.md
+++ b/README.md
@@ -794,17 +794,17 @@ target_url = 'https://example.com'
auth.logout(return_to=target_url)
```
-Also there are 4 optional parameters that can be set:
+Also there are another 5 optional parameters that can be set:
* ``name_id``: That will be used to build the ``LogoutRequest``. If no ``name_id`` parameter is set and the auth object processed a
SAML Response with a ``NameId``, then this ``NameId`` will be used.
* ``session_index``: ``SessionIndex`` that identifies the session of the user.
* ``nq``: IDP Name Qualifier.
* ``name_id_format``: The ``NameID`` Format that will be set in the ``LogoutRequest``.
+* ``spnq``: The ``NameID SP NameQualifier`` will be set in the ``LogoutRequest``.
If no ``name_id`` is provided, the ``LogoutRequest`` will contain a ``NameID`` with the entity Format.
If ``name_id`` is provided and no ``name_id_format`` is provided, the ``NameIDFormat`` of the settings will be used.
-If ``nq`` is provided, the ``SPNameQualifier`` will be also attached to the ``NameId``.
If a match on the ``LogoutResponse`` ID and the ``LogoutRequest`` ID to be sent is required, that ``LogoutRequest`` ID must to be extracted and stored for future validation, we can get that ID by:
@@ -830,7 +830,12 @@ elif 'sso2' in request.args: # Another SSO init action
return_to = '%sattrs/' % request.host_url # but set a custom RelayState URL
return redirect(auth.login(return_to))
elif 'slo' in request.args: # SLO action. Will sent a Logout Request to IdP
- return redirect(auth.logout())
+ nameid = request.session['samlNameId']
+ nameid_format = request.session['samlNameIdFormat']
+ nameid_nq = request.session['samlNameIdNameQualifier']
+ nameid_spnq = request.session['samlNameIdSPNameQualifier']
+ session_index = request.session['samlSessionIndex']
+ return redirect(auth.logout(None, nameid, session_index, nameid_nq, nameid_format, nameid_spnq))
elif 'acs' in request.args: # Assertion Consumer Service
auth.process_response() # Process the Response of the IdP
errors = auth.get_errors() # This method receives an array with the errors
@@ -839,6 +844,11 @@ elif 'acs' in request.args: # Assertion Consumer Service
msg = "Not authenticated" # data retrieved or not (user authenticated)
else:
request.session['samlUserdata'] = auth.get_attributes() # Retrieves user data
+ request.session['samlNameId'] = auth.get_nameid()
+ request.session['samlNameIdFormat'] = auth.get_nameid_format()
+ request.session['samlNameIdNameQualifier'] = auth.get_nameid_nq()
+ request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
+ request.session['samlSessionIndex'] = auth.get_session_index()
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in request.form and self_url != request.form['RelayState']:
return redirect(auth.redirect_to(request.form['RelayState'])) # Redirect if there is a relayState
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 535b42af..22e5f6aa 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -45,14 +45,19 @@ def index(request):
return_to = OneLogin_Saml2_Utils.get_self_url(req) + reverse('attrs')
return HttpResponseRedirect(auth.login(return_to))
elif 'slo' in req['get_data']:
- name_id = None
- session_index = None
+ name_id = session_index = name_id_format = name_id_nq = name_id_spnq = None
if 'samlNameId' in request.session:
name_id = request.session['samlNameId']
if 'samlSessionIndex' in request.session:
session_index = request.session['samlSessionIndex']
-
- return HttpResponseRedirect(auth.logout(name_id=name_id, session_index=session_index))
+ if 'samlNameIdFormat' in request.session:
+ name_id_format = request.session['samlNameIdFormat']
+ if 'samlNameIdNameQualifier' in request.session:
+ name_id_nq = request.session['samlNameIdNameQualifier']
+ if 'samlNameIdSPNameQualifier' in request.session:
+ name_id_spnq = request.session['samlNameIdSPNameQualifier']
+
+ return HttpResponseRedirect(auth.logout(name_id=name_id, session_index=session_index, nq=name_id_nq, name_id_format=name_id_format, spnq=name_id_spnq))
elif 'acs' in req['get_data']:
auth.process_response()
errors = auth.get_errors()
@@ -61,6 +66,9 @@ def index(request):
if not errors:
request.session['samlUserdata'] = auth.get_attributes()
request.session['samlNameId'] = auth.get_nameid()
+ request.session['samlNameIdFormat'] = auth.get_nameid_format()
+ request.session['samlNameIdNameQualifier'] = auth.get_nameid_nq()
+ request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
request.session['samlSessionIndex'] = auth.get_session_index()
if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))
diff --git a/demo-flask/index.py b/demo-flask/index.py
index e7c88d75..caf9a72a 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -50,21 +50,28 @@ def index():
return_to = '%sattrs/' % request.host_url
return redirect(auth.login(return_to))
elif 'slo' in request.args:
- name_id = None
- session_index = None
+ name_id = session_index = name_id_format = name_id_nq = name_id_spnq = None
if 'samlNameId' in session:
name_id = session['samlNameId']
if 'samlSessionIndex' in session:
session_index = session['samlSessionIndex']
-
- return redirect(auth.logout(name_id=name_id, session_index=session_index))
+ if 'samlNameIdFormat' in session:
+ name_id_format = session['samlNameIdFormat']
+ if 'samlNameIdNameQualifier' in session:
+ name_id_nq = session['samlNameIdNameQualifier']
+ if 'samlNameIdSPNameQualifier' in session:
+ name_id_spnq = session['samlNameIdSPNameQualifier']
+
+ return redirect(auth.logout(name_id=name_id, session_index=session_index, nq=name_id_nq, name_id_format=name_id_format, spnq=name_id_spnq))
elif 'acs' in request.args:
auth.process_response()
errors = auth.get_errors()
not_auth_warn = not auth.is_authenticated()
if len(errors) == 0:
- session['samlUserdata'] = auth.get_attributes()
session['samlNameId'] = auth.get_nameid()
+ session['samlNameIdFormat'] = auth.get_nameid_format()
+ session['samlNameIdNameQualifier'] = auth.get_nameid_nq()
+ session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
session['samlSessionIndex'] = auth.get_session_index()
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in request.form and self_url != request.form['RelayState']:
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 5d6aa84f..2a9b7cde 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -55,6 +55,8 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
self.__attributes = dict()
self.__nameid = None
self.__nameid_format = None
+ self.__nameid_nq = None
+ self.__nameid_spnq = None
self.__session_index = None
self.__session_expiration = None
self.__authenticated = False
@@ -107,6 +109,8 @@ def process_response(self, request_id=None):
self.__attributes = response.get_attributes()
self.__nameid = response.get_nameid()
self.__nameid_format = response.get_nameid_format()
+ self.__nameid_nq = response.get_nameid_nq()
+ self.__nameid_spnq = response.get_nameid_spnq()
self.__session_index = response.get_session_index()
self.__session_expiration = response.get_session_not_on_or_after()
self.__last_message_id = response.get_id()
@@ -245,6 +249,24 @@ def get_nameid_format(self):
"""
return self.__nameid_format
+ def get_nameid_nq(self):
+ """
+ Returns the nameID NameQualifier of the Assertion.
+
+ :returns: NameID NameQualifier
+ :rtype: string|None
+ """
+ return self.__nameid_nq
+
+ def get_nameid_spnq(self):
+ """
+ Returns the nameID SP NameQualifier of the Assertion.
+
+ :returns: NameID SP NameQualifier
+ :rtype: string|None
+ """
+ return self.__nameid_spnq
+
def get_session_index(self):
"""
Returns the SessionIndex from the AuthnStatement.
@@ -366,7 +388,7 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
self.add_request_signature(parameters, security['signatureAlgorithm'])
return self.redirect_to(self.get_sso_url(), parameters)
- def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name_id_format=None):
+ def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name_id_format=None, spnq=None):
"""
Initiates the SLO process.
@@ -385,6 +407,9 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name
:param name_id_format: The NameID Format that will be set in the LogoutRequest.
:type: string
+ :param spnq: SP Name Qualifier
+ :type: string
+
:returns: Redirection URL
"""
slo_url = self.get_slo_url()
@@ -405,7 +430,8 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name
name_id=name_id,
session_index=session_index,
nq=nq,
- name_id_format=name_id_format
+ name_id_format=name_id_format,
+ spnq=spnq
)
self.__last_request = logout_request.get_xml()
self.__last_request_id = logout_request.id
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index 9dfb0c3e..252726b7 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -25,7 +25,7 @@ class OneLogin_Saml2_Logout_Request(object):
"""
- def __init__(self, settings, request=None, name_id=None, session_index=None, nq=None, name_id_format=None):
+ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=None, name_id_format=None, spnq=None):
"""
Constructs the Logout Request object.
@@ -46,6 +46,9 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
:param name_id_format: The NameID Format that will be set in the LogoutRequest.
:type: string
+
+ :param spnq: SP Name Qualifier
+ :type: string
"""
self.__settings = settings
self.__error = None
@@ -75,19 +78,23 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
if not name_id_format and sp_data['NameIDFormat'] != OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED:
name_id_format = sp_data['NameIDFormat']
else:
+ name_id = idp_data['entityId']
name_id_format = OneLogin_Saml2_Constants.NAMEID_ENTITY
- sp_name_qualifier = None
- if name_id_format == OneLogin_Saml2_Constants.NAMEID_ENTITY:
- name_id = idp_data['entityId']
+ # From saml-core-2.0-os 8.3.6, when the entity Format is used:
+ # "The NameQualifier, SPNameQualifier, and SPProvidedID attributes
+ # MUST be omitted.
+ if name_id_format and name_id_format == OneLogin_Saml2_Constants.NAMEID_ENTITY:
nq = None
- elif nq is not None:
- # We only gonna include SPNameQualifier if NameQualifier is provided
- sp_name_qualifier = sp_data['entityId']
+ spnq = None
+
+ # NameID Format UNSPECIFIED omitted
+ if name_id_format and name_id_format == OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED:
+ name_id_format = None
name_id_obj = OneLogin_Saml2_Utils.generate_name_id(
name_id,
- sp_name_qualifier,
+ spnq,
name_id_format,
cert,
False,
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 1ceaac32..8aa309c5 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -506,6 +506,32 @@ def get_nameid_format(self):
nameid_format = nameid_data['Format']
return nameid_format
+ def get_nameid_nq(self):
+ """
+ Gets the NameID NameQualifier provided by the SAML Response from the IdP
+
+ :returns: NameID NameQualifier
+ :rtype: string|None
+ """
+ nameid_nq = None
+ nameid_data = self.get_nameid_data()
+ if nameid_data and 'NameQualifier' in nameid_data.keys():
+ nameid_nq = nameid_data['NameQualifier']
+ return nameid_nq
+
+ def get_nameid_spnq(self):
+ """
+ Gets the NameID SP NameQualifier provided by the SAML response from the IdP.
+
+ :returns: NameID SP NameQualifier
+ :rtype: string|None
+ """
+ nameid_spnq = None
+ nameid_data = self.get_nameid_data()
+ if nameid_data and 'SPNameQualifier' in nameid_data.keys():
+ nameid_spnq = nameid_data['SPNameQualifier']
+ return nameid_spnq
+
def get_session_not_on_or_after(self):
"""
Gets the SessionNotOnOrAfter from the AuthnStatement
diff --git a/tests/data/responses/valid_response_with_namequalifier.xml.base64 b/tests/data/responses/valid_response_with_namequalifier.xml.base64
new file mode 100644
index 00000000..a98de3ff
--- /dev/null
+++ b/tests/data/responses/valid_response_with_namequalifier.xml.base64
@@ -0,0 +1 @@
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDhmZWI5YWNkLTFlODYtYWMxMi05MDIzLTEzYjg0NDc5YjI1YiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDhmZWI5YWNkLTFlODYtYWMxMi05MDIzLTEzYjg0NDc5YjI1YiI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+NVRWZURYbGQ3YzhURmtybVlDeFpuL2ZHRTRzPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5hZlFaVUE2REpHa0hLNjVMMENBaTJBSDJkOWNwbExuekNPTHBCYm9hUmVmaWdtVC92L0tJZGcyYXpWRzY2Ykk1aFA1NTBNR0c2ZVVzaWJ1N2N3ZytFbG9tejVBalE3dzlGZG8waHdWWWhib3JaSkN2TUxLUzBEWkFzc01XZnZ3RGNUNmhra3UreXFlS2RhZ1BBOTYwQ25YcUMxeHpjMk43WS82dlBCU081bVU9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDQxN2ZiOTc2LTk0NGEtNDNiZi05ZTUyLWZiOWM1OTYxNzYxZiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng0MTdmYjk3Ni05NDRhLTQzYmYtOWU1Mi1mYjljNTk2MTc2MWYiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmxSbTJ3UW13ZGhmZVZuMDFaS1Ewb05CN1JqQT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+aktwQUNmMWkxR0FMSWQ5Y0liQlFsTkJQMVhpZDhhYXFKOUxyTkFIZ1lpR2VIc0NscldVUkZJREprOGI0T3RmdHdXTGZKeXBXbXgwWm15M2hpTTJyVHBIbDBLMGVqSFNsOS9Ed0pabkNEQW1CS1lhZ0ZFR0xxWXYwaXI0Y2lYaForTkdXSDY1czhBRlVibjU2SytaS3lpMFkwMWc4TmVqaS92OTNlZFZ6ZTZnPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vdGVzdC5leGFtcGxlLmNvbS9zYW1sL21ldGFkYXRhIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+NDkyODgyNjE1YWNmMzFjODA5NmI2MjcyNDVkNzZhZTUzMDM2YzA5MDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjA1NC0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjA1NC0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wMi0xOVQwMTozNzowMVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwNTQtMDItMTlUMDk6Mzc6MDFaIiBTZXNzaW9uSW5kZXg9Il82MjczZDc3YjhjZGUwYzMzM2VjNzlkMjJhOWZhMDAwM2I5ZmUyZDc1Y2IiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c21hcnRpbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluQHlhY28uZXM8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iY24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNpeHRvMzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+TWFydGluMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5hZG1pbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==
\ No newline at end of file
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 02193ee2..c7c391c7 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -227,6 +227,9 @@ def testProcessResponseValid(self):
self.assertEqual(auth.get_attribute('mail'), attributes['mail'])
session_index = auth.get_session_index()
self.assertEqual('_6273d77b8cde0c333ec79d22a9fa0003b9fe2d75cb', session_index)
+ self.assertEqual("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", auth.get_nameid_format())
+ self.assertIsNone(auth.get_nameid_nq())
+ self.assertEqual("http://stuff.com/endpoints/metadata.php", auth.get_nameid_spnq())
def testRedirectTo(self):
"""
@@ -993,6 +996,70 @@ def testGetNameIdFormat(self):
self.assertTrue(auth.is_authenticated())
self.assertEqual("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", auth.get_nameid_format())
+ def testGetNameIdNameQualifier(self):
+ """
+ Tests the get_nameid_nq method of the OneLogin_Saml2_Auth
+ """
+ settings = self.loadSettingsJSON()
+ message = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64'))
+ request_data = self.get_request()
+ request_data['post_data'] = {
+ 'SAMLResponse': message
+ }
+ auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
+ self.assertIsNone(auth.get_nameid_nq())
+ auth.process_response()
+ self.assertTrue(auth.is_authenticated())
+ self.assertEqual("https://test.example.com/saml/metadata", auth.get_nameid_nq())
+
+ def testGetNameIdNameQualifier2(self):
+ """
+ Tests the get_nameid_nq method of the OneLogin_Saml2_Auth
+ """
+ settings = self.loadSettingsJSON()
+ message = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64'))
+ request_data = self.get_request()
+ request_data['post_data'] = {
+ 'SAMLResponse': message
+ }
+ auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
+ self.assertIsNone(auth.get_nameid_nq())
+ auth.process_response()
+ self.assertTrue(auth.is_authenticated())
+ self.assertIsNone(auth.get_nameid_nq())
+
+ def testGetNameIdSPNameQualifier(self):
+ """
+ Tests the get_nameid_spnq method of the OneLogin_Saml2_Auth
+ """
+ settings = self.loadSettingsJSON()
+ message = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64'))
+ request_data = self.get_request()
+ request_data['post_data'] = {
+ 'SAMLResponse': message
+ }
+ auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
+ self.assertIsNone(auth.get_nameid_spnq())
+ auth.process_response()
+ self.assertTrue(auth.is_authenticated())
+ self.assertIsNone(auth.get_nameid_spnq())
+
+ def testGetNameIdSPNameQualifier2(self):
+ """
+ Tests the get_nameid_spnq method of the OneLogin_Saml2_Auth
+ """
+ settings = self.loadSettingsJSON()
+ message = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64'))
+ request_data = self.get_request()
+ request_data['post_data'] = {
+ 'SAMLResponse': message
+ }
+ auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
+ self.assertIsNone(auth.get_nameid_spnq())
+ auth.process_response()
+ self.assertTrue(auth.is_authenticated())
+ self.assertEqual("http://stuff.com/endpoints/metadata.php", auth.get_nameid_spnq())
+
def testBuildRequestSignature(self):
"""
Tests the build_request_signature method of the OneLogin_Saml2_Auth
diff --git a/tests/src/OneLogin/saml2_tests/logout_request_test.py b/tests/src/OneLogin/saml2_tests/logout_request_test.py
index 3d724d82..d4c8ee6c 100644
--- a/tests/src/OneLogin/saml2_tests/logout_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_request_test.py
@@ -215,7 +215,6 @@ def testGetNameIdData(self):
expected_name_id_data = {
'Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress',
'NameQualifier': idp_data['entityId'],
- 'SPNameQualifier': 'http://stuff.com/endpoints/metadata.php',
'Value': 'ONELOGIN_9c86c4542ab9d6fce07f2f7fd335287b9b3cdf69'
}
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index d487c11b..3ce359d9 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -293,6 +293,74 @@ def testReturnNameIdFormat(self):
with self.assertRaisesRegex(Exception, 'An empty NameID value found'):
response_17.get_nameid_format()
+ def testReturnNameIdNameQualifier(self):
+ """
+ Tests the get_nameid_nq method of the OneLogin_Saml2_Response
+ """
+ json_settings = self.loadSettingsJSON()
+ json_settings['strict'] = False
+ settings = OneLogin_Saml2_Settings(json_settings)
+ xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
+ response = OneLogin_Saml2_Response(settings, xml)
+ self.assertIsNone(response.get_nameid_nq())
+
+ xml_2 = self.file_contents(join(self.data_path, 'responses', 'response_encrypted_nameid.xml.base64'))
+ response_2 = OneLogin_Saml2_Response(settings, xml_2)
+ self.assertIsNone(response_2.get_nameid_nq())
+
+ xml_3 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64'))
+ response_3 = OneLogin_Saml2_Response(settings, xml_3)
+ self.assertIsNone(response_3.get_nameid_nq())
+
+ xml_4 = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64'))
+ response_4 = OneLogin_Saml2_Response(settings, xml_4)
+ self.assertIsNone(response_4.get_nameid_nq())
+
+ xml_5 = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64'))
+ response_5 = OneLogin_Saml2_Response(settings, xml_5)
+ self.assertEqual('https://test.example.com/saml/metadata', response_5.get_nameid_nq())
+
+ json_settings['strict'] = True
+ settings = OneLogin_Saml2_Settings(json_settings)
+ xml_6 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_nameid.xml.base64'))
+ response_6 = OneLogin_Saml2_Response(settings, xml_6)
+ with self.assertRaisesRegex(Exception, 'NameID not found in the assertion of the Response'):
+ response_6.get_nameid_nq()
+
+ def testReturnNameIdNameSPQualifier(self):
+ """
+ Tests the get_nameid_spnq method of the OneLogin_Saml2_Response
+ """
+ json_settings = self.loadSettingsJSON()
+ json_settings['strict'] = False
+ settings = OneLogin_Saml2_Settings(json_settings)
+ xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
+ response = OneLogin_Saml2_Response(settings, xml)
+ self.assertIsNone(response.get_nameid_spnq())
+
+ xml_2 = self.file_contents(join(self.data_path, 'responses', 'response_encrypted_nameid.xml.base64'))
+ response_2 = OneLogin_Saml2_Response(settings, xml_2)
+ self.assertEqual("http://stuff.com/endpoints/metadata.php", response_2.get_nameid_spnq())
+
+ xml_3 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion.xml.base64'))
+ response_3 = OneLogin_Saml2_Response(settings, xml_3)
+ self.assertEqual("http://stuff.com/endpoints/metadata.php", response_3.get_nameid_spnq())
+
+ xml_4 = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64'))
+ response_4 = OneLogin_Saml2_Response(settings, xml_4)
+ self.assertEqual("http://stuff.com/endpoints/metadata.php", response_4.get_nameid_spnq())
+
+ xml_5 = self.file_contents(join(self.data_path, 'responses', 'valid_response_with_namequalifier.xml.base64'))
+ response_5 = OneLogin_Saml2_Response(settings, xml_5)
+ self.assertIsNone(response_5.get_nameid_spnq())
+
+ json_settings['strict'] = True
+ settings = OneLogin_Saml2_Settings(json_settings)
+ xml_6 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_nameid.xml.base64'))
+ response_6 = OneLogin_Saml2_Response(settings, xml_6)
+ with self.assertRaisesRegex(Exception, 'NameID not found in the assertion of the Response'):
+ response_6.get_nameid_spnq()
+
def testGetNameIdData(self):
"""
Tests the get_nameid_data method of the OneLogin_Saml2_Response
From ec21c28b9997ad3acf80c5d4ce3ef9e4afc6ca6e Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 27 Jun 2019 16:34:34 +0200
Subject: [PATCH 125/331] Support authnrequest_id logoutrequest_id on demos
---
demo-django/demo/views.py | 21 +++++++++++++++++++--
demo-flask/index.py | 17 +++++++++++++++--
2 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 22e5f6aa..337ef669 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -41,6 +41,10 @@ def index(request):
if 'sso' in req['get_data']:
return HttpResponseRedirect(auth.login())
+ # If AuthNRequest ID need to be stored in order to later validate it, do instead
+ # sso_built_url = auth.login()
+ # request.session['AuthNRequestID'] = auth.get_last_request_id()
+ # return HttpResponseRedirect(sso_built_url)
elif 'sso2' in req['get_data']:
return_to = OneLogin_Saml2_Utils.get_self_url(req) + reverse('attrs')
return HttpResponseRedirect(auth.login(return_to))
@@ -58,12 +62,22 @@ def index(request):
name_id_spnq = request.session['samlNameIdSPNameQualifier']
return HttpResponseRedirect(auth.logout(name_id=name_id, session_index=session_index, nq=name_id_nq, name_id_format=name_id_format, spnq=name_id_spnq))
+ # If LogoutRequest ID need to be stored in order to later validate it, do instead
+ # slo_built_url = auth.logout(name_id=name_id, session_index=session_index)
+ # request.session['LogoutRequestID'] = auth.get_last_request_id()
+ #return HttpResponseRedirect(slo_built_url)
elif 'acs' in req['get_data']:
- auth.process_response()
+ request_id = None
+ if 'AuthNRequestID' in request.session:
+ request_id = request.session['AuthNRequestID']
+
+ auth.process_response(request_id=request_id)
errors = auth.get_errors()
not_auth_warn = not auth.is_authenticated()
if not errors:
+ if 'AuthNRequestID' in request.session:
+ del request.session['AuthNRequestID']
request.session['samlUserdata'] = auth.get_attributes()
request.session['samlNameId'] = auth.get_nameid()
request.session['samlNameIdFormat'] = auth.get_nameid_format()
@@ -76,8 +90,11 @@ def index(request):
if auth.get_settings().is_debug_active():
error_reason = auth.get_last_error_reason()
elif 'sls' in req['get_data']:
+ request_id = None
+ if 'LogoutRequestID' in request.session:
+ request_id = request.session['LogoutRequestID']
dscb = lambda: request.session.flush()
- url = auth.process_slo(delete_session_cb=dscb)
+ url = auth.process_slo(request_id=request_id, delete_session_cb=dscb)
errors = auth.get_errors()
if len(errors) == 0:
if url is not None:
diff --git a/demo-flask/index.py b/demo-flask/index.py
index caf9a72a..b344da4c 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -46,6 +46,10 @@ def index():
if 'sso' in request.args:
return redirect(auth.login())
+ # If AuthNRequest ID need to be stored in order to later validate it, do instead
+ # sso_built_url = auth.login()
+ # request.session['AuthNRequestID'] = auth.get_last_request_id()
+ # return redirect(sso_built_url)
elif 'sso2' in request.args:
return_to = '%sattrs/' % request.host_url
return redirect(auth.login(return_to))
@@ -64,10 +68,16 @@ def index():
return redirect(auth.logout(name_id=name_id, session_index=session_index, nq=name_id_nq, name_id_format=name_id_format, spnq=name_id_spnq))
elif 'acs' in request.args:
- auth.process_response()
+ request_id = None
+ if 'AuthNRequestID' in session:
+ request_id = session['AuthNRequestID']
+
+ auth.process_response(request_id=request_id)
errors = auth.get_errors()
not_auth_warn = not auth.is_authenticated()
if len(errors) == 0:
+ if 'AuthNRequestID' in session:
+ del session['AuthNRequestID']
session['samlNameId'] = auth.get_nameid()
session['samlNameIdFormat'] = auth.get_nameid_format()
session['samlNameIdNameQualifier'] = auth.get_nameid_nq()
@@ -77,8 +87,11 @@ def index():
if 'RelayState' in request.form and self_url != request.form['RelayState']:
return redirect(auth.redirect_to(request.form['RelayState']))
elif 'sls' in request.args:
+ request_id = None
+ if 'LogoutRequestID' in session:
+ request_id = session['LogoutRequestID']
dscb = lambda: session.clear()
- url = auth.process_slo(delete_session_cb=dscb)
+ url = auth.process_slo(request_id=request_id, delete_session_cb=dscb)
errors = auth.get_errors()
if len(errors) == 0:
if url is not None:
From 45b920760ad9bbd5ce119164718f6be9a8beea46 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 2 Jul 2019 19:12:43 +0200
Subject: [PATCH 126/331] Release 1.7.0
---
changelog.md | 5 +++++
setup.py | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index 42466c4b..8c651302 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,9 @@
# python3-saml changelog
+### 1.7.0 (Jun 25, 2019)
+* Adjusted acs endpoint to extract NameQualifier and SPNameQualifier from SAMLResponse. Adjusted single logout service to provide NameQualifier and SPNameQualifier to logout method. Add getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from Auth and LogoutRequest constructor to support SPNameQualifier parameter. Align LogoutRequest constructor with SAML specs
+* Added get_in_response_to method to Response and LogoutResponse classes
+* Update defusexml dependency
+
### 1.6.0 (Apr 10, 2019)
* Add support for Subjects on AuthNRequests by the new name_id_value_req parameter
* [#127](https://github.com/onelogin/python3-saml/pull/127) Fix for SLO when XML specifies encoding
diff --git a/setup.py b/setup.py
index e93bccba..3a248144 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.6.0',
+ version='1.7.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From 839edb1227ba89b96a1e228f24008d1362672438 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 2 Jul 2019 19:18:16 +0200
Subject: [PATCH 127/331] Update date of release
---
changelog.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index 8c651302..c82d0cd6 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,5 @@
# python3-saml changelog
-### 1.7.0 (Jun 25, 2019)
+### 1.7.0 (Jul 02, 2019)
* Adjusted acs endpoint to extract NameQualifier and SPNameQualifier from SAMLResponse. Adjusted single logout service to provide NameQualifier and SPNameQualifier to logout method. Add getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from Auth and LogoutRequest constructor to support SPNameQualifier parameter. Align LogoutRequest constructor with SAML specs
* Added get_in_response_to method to Response and LogoutResponse classes
* Update defusexml dependency
From e63a2f8305ad0646eb89d1ff62853a4d97b6594c Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 2 Jul 2019 20:09:38 +0200
Subject: [PATCH 128/331] Fix flake8
---
demo-django/demo/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 337ef669..4c61e599 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -65,7 +65,7 @@ def index(request):
# If LogoutRequest ID need to be stored in order to later validate it, do instead
# slo_built_url = auth.logout(name_id=name_id, session_index=session_index)
# request.session['LogoutRequestID'] = auth.get_last_request_id()
- #return HttpResponseRedirect(slo_built_url)
+ # return HttpResponseRedirect(slo_built_url)
elif 'acs' in req['get_data']:
request_id = None
if 'AuthNRequestID' in request.session:
From 40883d925ce17e2b2dcd2d19cab3c710b2fe20b4 Mon Sep 17 00:00:00 2001
From: Craig Martin
Date: Tue, 9 Jul 2019 11:34:44 -0400
Subject: [PATCH 129/331] use python style comment in python code block
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 52f3b923..e652786e 100644
--- a/README.md
+++ b/README.md
@@ -540,7 +540,7 @@ req = {
"get_data": "",
"post_data": "",
- /* Advanced request options */
+ # Advanced request options
"https": "",
"lowercase_urlencoding": "",
"request_uri": "",
From 25dfdc0a694299fe2a37f6edfb2dc39cb6c57e10 Mon Sep 17 00:00:00 2001
From: Jeff Tchang
Date: Fri, 26 Jul 2019 12:16:14 -0700
Subject: [PATCH 130/331] Don't clean xsd and xsi namespaces
---
src/onelogin/saml2/constants.py | 1 +
src/onelogin/saml2/xml_utils.py | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/constants.py b/src/onelogin/saml2/constants.py
index 7b3f0085..e0778bd2 100644
--- a/src/onelogin/saml2/constants.py
+++ b/src/onelogin/saml2/constants.py
@@ -53,6 +53,7 @@ class OneLogin_Saml2_Constants(object):
NS_PREFIX_MD = 'md'
NS_PREFIX_XS = 'xs'
NS_PREFIX_XSI = 'xsi'
+ NS_PREFIX_XSD = 'xsd'
NS_PREFIX_XENC = 'xenc'
NS_PREFIX_DS = 'ds'
diff --git a/src/onelogin/saml2/xml_utils.py b/src/onelogin/saml2/xml_utils.py
index c7274298..d966e615 100644
--- a/src/onelogin/saml2/xml_utils.py
+++ b/src/onelogin/saml2/xml_utils.py
@@ -147,7 +147,9 @@ def cleanup_namespaces(tree_or_element, top_nsmap=None, keep_ns_prefixes=None):
:rtype: etree.Element
"""
all_prefixes_to_keep = [
- OneLogin_Saml2_Constants.NS_PREFIX_XS
+ OneLogin_Saml2_Constants.NS_PREFIX_XS,
+ OneLogin_Saml2_Constants.NS_PREFIX_XSI,
+ OneLogin_Saml2_Constants.NS_PREFIX_XSD
]
if keep_ns_prefixes:
From c77f75f645451032769decd4ae8cba5df30e72bb Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann"
Date: Thu, 1 Aug 2019 13:19:47 +0200
Subject: [PATCH 131/331] tests: make tests pass in 2020
For this we update 2020 to 2999
diff decoded is
a/tests/data/responses/unsigned_response.xml
b/tests/data/responses/unsigned_response.xml
@@ -9,15 +9,15 @@
someone@example.com
-
+
-
+
http://stuff.com/endpoints/metadata.php
-
+
urn:oasis:names:tc:SAML:2.0:ac:classes:Password
---
.../invalids/invalid_subjectconfirmation_nb.xml.base64 | 2 +-
tests/data/responses/unsigned_response.xml.base64 | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/data/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 b/tests/data/responses/invalids/invalid_subjectconfirmation_nb.xml.base64
index ff6d371c..5d1f8bc1 100644
--- a/tests/data/responses/invalids/invalid_subjectconfirmation_nb.xml.base64
+++ b/tests/data/responses/invalids/invalid_subjectconfirmation_nb.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdEJlZm9yZT0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdEJlZm9yZT0iMjk5OS0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjI5OTktMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjI5OTktMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
diff --git a/tests/data/responses/unsigned_response.xml.base64 b/tests/data/responses/unsigned_response.xml.base64
index 66892b53..18abcf23 100644
--- a/tests/data/responses/unsigned_response.xml.base64
+++ b/tests/data/responses/unsigned_response.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjk5OS0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjI5OTktMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjI5OTktMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
From 93f427085dffd2bd4d3b086a7c4183357bb7f646 Mon Sep 17 00:00:00 2001
From: Florent PIGOUT
Date: Wed, 14 Aug 2019 18:04:18 +0200
Subject: [PATCH 132/331] Drop python3.4 support
Python3.4 is not supported anymore by lxml. Since lots of distribution
handle python3.5 by default and because it breaks the CI, it looks
obvious to drop it.
---
.travis.yml | 1 -
changelog.md | 3 +++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 8023585e..3ddd4236 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,6 @@
language: python
python:
- '2.7'
- - '3.4'
- '3.5'
- '3.6'
diff --git a/changelog.md b/changelog.md
index c82d0cd6..9d7e4384 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,7 @@
# python3-saml changelog
+### 1.7.1 (unrelease)
+* Drop python3.4 support
+
### 1.7.0 (Jul 02, 2019)
* Adjusted acs endpoint to extract NameQualifier and SPNameQualifier from SAMLResponse. Adjusted single logout service to provide NameQualifier and SPNameQualifier to logout method. Add getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from Auth and LogoutRequest constructor to support SPNameQualifier parameter. Align LogoutRequest constructor with SAML specs
* Added get_in_response_to method to Response and LogoutResponse classes
From 86645a8e2538ad09df38bddead37c090181d1350 Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann"
Date: Wed, 21 Aug 2019 10:12:01 +0200
Subject: [PATCH 133/331] Drop broken python-3.4 tests
They failed with `This lxml version requires Python 2.7, 3.5 or later.`
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 8023585e..43d4b210 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,9 @@
language: python
python:
- '2.7'
- - '3.4'
- '3.5'
- '3.6'
+ - '3.7'
matrix:
include:
From b7ae95132f35a5b649c546bb79442422aff3073a Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 11 Sep 2019 16:35:40 +0200
Subject: [PATCH 134/331] Set true as the default value for strict setting
---
src/onelogin/saml2/settings.py | 4 ++--
tests/src/OneLogin/saml2_tests/settings_test.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 003aa0ad..5057a5ab 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -83,7 +83,7 @@ def __init__(self, settings=None, custom_base_path=None, sp_validation_only=Fals
"""
self.__sp_validation_only = sp_validation_only
self.__paths = {}
- self.__strict = False
+ self.__strict = True
self.__debug = False
self.__sp = {}
self.__idp = {}
@@ -214,7 +214,7 @@ def __load_settings_from_dict(self, settings):
self.__errors = []
self.__sp = settings['sp']
self.__idp = settings.get('idp', {})
- self.__strict = settings.get('strict', False)
+ self.__strict = settings.get('strict', True)
self.__debug = settings.get('debug', False)
self.__security = settings.get('security', {})
self.__contacts = settings.get('contactPerson', {})
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index 75031009..942fdd96 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -755,7 +755,7 @@ def testIsStrict(self):
del settings_info['strict']
settings = OneLogin_Saml2_Settings(settings_info)
- self.assertFalse(settings.is_strict())
+ self.assertTrue(settings.is_strict())
settings_info['strict'] = False
settings_2 = OneLogin_Saml2_Settings(settings_info)
From 1694935585554699d474fcf10f8b31a6aeb52cc7 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 11 Sep 2019 16:42:36 +0200
Subject: [PATCH 135/331] Release 1.8.0
---
README.md | 2 ++
changelog.md | 6 ++++--
setup.py | 2 +-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index e652786e..106e93c2 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ This version supports Python3. There is a separate version that only support Pyt
#### Warning ####
+Version 1.8.0 sets strict mode active by default
+
Update ``python3-saml`` to ``1.5.0``, this version includes security improvements for preventing XEE and Xpath Injections.
Update ``python3-saml`` to ``1.4.0``, this version includes a fix for the [CVE-2017-11427](https://www.cvedetails.com/cve/CVE-2017-11427/) vulnerability.
diff --git a/changelog.md b/changelog.md
index 9d7e4384..f0e28708 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,8 @@
# python3-saml changelog
-### 1.7.1 (unrelease)
-* Drop python3.4 support
+### 1.8.0 (Sep 11, 2019)
+* Set true as the default value for strict setting
+* [#152](https://github.com/onelogin/python3-saml/pull/152/files) Don't clean xsd and xsi namespaces
+* Drop python3.4 support due lxml. See lxml 4.4.0 (2019-07-27)
### 1.7.0 (Jul 02, 2019)
* Adjusted acs endpoint to extract NameQualifier and SPNameQualifier from SAMLResponse. Adjusted single logout service to provide NameQualifier and SPNameQualifier to logout method. Add getNameIdNameQualifier to Auth and SamlResponse. Extend logout method from Auth and LogoutRequest constructor to support SPNameQualifier parameter. Align LogoutRequest constructor with SAML specs
diff --git a/setup.py b/setup.py
index 3a248144..cb483f2d 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.7.0',
+ version='1.8.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From f900996dd53c2c96eeec25cc2bedf086847dbbbe Mon Sep 17 00:00:00 2001
From: Mika Lehtinen
Date: Fri, 18 Oct 2019 11:56:22 +0300
Subject: [PATCH 136/331] Fix parameter type annotations in merge_settings
---
src/onelogin/saml2/idp_metadata_parser.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 036028a2..88411f45 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -219,9 +219,9 @@ def merge_settings(settings, new_metadata_settings):
"""
Will update the settings with the provided new settings data extracted from the IdP metadata
:param settings: Current settings dict data
- :type settings: string
+ :type settings: dict
:param new_metadata_settings: Settings to be merged (extracted from IdP metadata after parsing)
- :type new_metadata_settings: string
+ :type new_metadata_settings: dict
:returns: merged settings
:rtype: dict
"""
From 99567790002a317630be417c84b53c892b81682d Mon Sep 17 00:00:00 2001
From: Florent PIGOUT
Date: Wed, 14 Aug 2019 17:11:36 +0200
Subject: [PATCH 137/331] Do not touch signed message
---
src/onelogin/saml2/utils.py | 8 ++++----
...ponse_without_assertion_reference_uri.xml.base64 | 1 +
.../response_without_reference_uri.xml.base64 | 2 +-
tests/src/OneLogin/saml2_tests/response_test.py | 13 +++++++++++--
4 files changed, 17 insertions(+), 7 deletions(-)
create mode 100644 tests/data/responses/response_without_assertion_reference_uri.xml.base64
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 4647c230..ebaa9c6d 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -956,10 +956,10 @@ def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, finger
)
# Check if Reference URI is empty
- reference_elem = OneLogin_Saml2_XML.query(signature_node, '//ds:Reference')
- if len(reference_elem) > 0:
- if reference_elem[0].get('URI') == '':
- reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID'))
+ # reference_elem = OneLogin_Saml2_XML.query(signature_node, '//ds:Reference')
+ # if len(reference_elem) > 0:
+ # if reference_elem[0].get('URI') == '':
+ # reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID'))
if validatecert:
manager = xmlsec.KeysManager()
diff --git a/tests/data/responses/response_without_assertion_reference_uri.xml.base64 b/tests/data/responses/response_without_assertion_reference_uri.xml.base64
new file mode 100644
index 00000000..8c4dab5d
--- /dev/null
+++ b/tests/data/responses/response_without_assertion_reference_uri.xml.base64
@@ -0,0 +1 @@
+PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0icGZ4ZDU5NDM0N2QtNDk1Zi1iOGQxLTBlZTItNDFjZmRhMTRkZDM1IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMS0wMlQyMjo0ODo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjkwMDEvdjEvdXNlcnMvYXV0aG9yaXplL3NhbWwiIENvbnNlbnQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjb25zZW50OnVuc3BlY2lmaWVkIiBJblJlc3BvbnNlVG89Il9lZDkxNWE0MC03NGZiLTAxMzItNWIxNi00OGUwZWIxNGExYzciPgogIDxJc3N1ZXIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHA6Ly9leGFtcGxlLmNvbTwvSXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgPC9zYW1scDpTdGF0dXM+CgogIDxBc3NlcnRpb24geG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfNzAwYWMzMjAtNzRmZi0wMTMyLTViMTQtNDhlMGViMTRhMWM3IiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBWZXJzaW9uPSIyLjAiPgogICAgPElzc3Vlcj5odHRwOi8vZXhhbXBsZS5jb208L0lzc3Vlcj4KICAgIDxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPgogIDxkczpTaWduZWRJbmZvPgogICAgPGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4KICAgIDxkczpSZWZlcmVuY2UgVVJJPSIiPgogICAgICA8ZHM6VHJhbnNmb3Jtcz4KICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CiAgICAgIDwvZHM6VHJhbnNmb3Jtcz4KICAgICAgPGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+CiAgICAgIDxkczpEaWdlc3RWYWx1ZT5qQ2dlWENQREZsd2pUZ3FnUHAwbVUyVHF3OWc9PC9kczpEaWdlc3RWYWx1ZT4KICAgIDwvZHM6UmVmZXJlbmNlPgogIDwvZHM6U2lnbmVkSW5mbz4KICA8ZHM6U2lnbmF0dXJlVmFsdWU+bG9SN21DRmlNSURIUHBLeVgzRUd2dzJYeTZycEtFZWZVMDhYS1lWRXJ6MXB3a1BUUFFlYU5iK2RGMHZLai9rNQoyUmJ2Z3ZFUFN2ZGI3RDJOMTY5QjJMTGVmbXpaWTBDY0RKcThkK3lNbnZSNER3YitSUFl6bWJoS29XQ1ZyY3VPCnNvbEUxQTg3WFZjenNpd2JYRWllM2p4RHdDSk5vWi9GRFJRZy80RHRQVmc9PC9kczpTaWduYXR1cmVWYWx1ZT4KPGRzOktleUluZm8+CiAgPGRzOlg1MDlEYXRhPgogICAgPGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDVnpDQ0FjQUNDUURJVkhhTlNCWUw2VEFOQmdrcWhraUc5dzBCQVFzRkFEQndNUXN3Q1FZRFZRUUdFd0pHVWpFT01Bd0dBMVVFQ0F3RlVHRnlhWE14RGpBTUJnTlZCQWNNQlZCaGNtbHpNUll3RkFZRFZRUUtEQTFPYjNaaGNHOXpkQ0JVUlZOVU1Ta3dKd1lKS29aSWh2Y05BUWtCRmhwbWJHOXlaVzUwTG5CcFoyOTFkRUJ1YjNaaGNHOXpkQzVtY2pBZUZ3MHhOREF5TVRNeE16VXpOREJhRncweE5UQXlNVE14TXpVek5EQmFNSEF4Q3pBSkJnTlZCQVlUQWtaU01RNHdEQVlEVlFRSURBVlFZWEpwY3pFT01Bd0dBMVVFQnd3RlVHRnlhWE14RmpBVUJnTlZCQW9NRFU1dmRtRndiM04wSUZSRlUxUXhLVEFuQmdrcWhraUc5dzBCQ1FFV0dtWnNiM0psYm5RdWNHbG5iM1YwUUc1dmRtRndiM04wTG1aeU1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRQ2hMRkhuM0xuTjRKUS83V0NkWXVweGtVZ2NOT1FuUEYreWxsKy9EUHB1eDlucGZZMDU5UElVYXRCOFg3a0NuNWk4dFJ3SXkvaWtISlI2TXI4K01QdmM2Vk9aRHhQTmRadk1vLzhsaHhyYk4zSmRydzN3aFptVS9LUFI5RjNCZEZkdStTTHpyTWwxVERVWmxQdFk5WHpVRlhjcU44SVhjeThUSnpDQmVOZXkzUUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRHQkFDdEo4ZmVHemUxTkhCNVZ3MThqTVVQdkhvN0gzR3dtajZaREFYUWxhaUFYTXVOQnhOWFZXVndpZmw2VituVzN3OVFhN0Zlby9uWi9PNFRVT0gxbnorYWRrbGNDRDRRcFphRUlibUFicmlQV0pLZ2I0TFdHaHFRcnV3WVI3SXRUUjFNTlg5Z0xiUDB6MHp2REVRbm50L1ZVV0ZFQkxTSnE0WjROcmU4TEZtUzI8L2RzOlg1MDlDZXJ0aWZpY2F0ZT4KICA8L2RzOlg1MDlEYXRhPgo8L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PFN1YmplY3Q+CiAgICAgIDxOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnNhbWxAdXNlci5jb208L05hbWVJRD4KICAgICAgPFN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4KICAgICAgICA8U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJfZWQ5MTVhNDAtNzRmYi0wMTMyLTViMTYtNDhlMGViMTRhMWM3IiBOb3RPbk9yQWZ0ZXI9IjIwMzgtMDEtMDJUMjI6NTE6NDhaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS92MS91c2Vycy9hdXRob3JpemUvc2FtbCIvPgogICAgICA8L1N1YmplY3RDb25maXJtYXRpb24+CiAgICA8L1N1YmplY3Q+CiAgICA8Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTUtMDEtMDJUMjI6NDg6NDNaIiBOb3RPbk9yQWZ0ZXI9IjIwMzgtMDEtMDJUMjM6NDg6NDhaIj4KICAgICAgPEF1ZGllbmNlUmVzdHJpY3Rpb24+CiAgICAgICAgPEF1ZGllbmNlPmh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS88L0F1ZGllbmNlPgogICAgICAgIDxBdWRpZW5jZT5mbGF0X3dvcmxkPC9BdWRpZW5jZT4KICAgICAgPC9BdWRpZW5jZVJlc3RyaWN0aW9uPgogICAgPC9Db25kaXRpb25zPgogICAgPEF0dHJpYnV0ZVN0YXRlbWVudD4KICAgICAgPEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiPgogICAgICAgIDxBdHRyaWJ1dGVWYWx1ZT5zYW1sQHVzZXIuY29tPC9BdHRyaWJ1dGVWYWx1ZT4KICAgICAgPC9BdHRyaWJ1dGU+CiAgICA8L0F0dHJpYnV0ZVN0YXRlbWVudD4KICAgIDxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBTZXNzaW9uSW5kZXg9Il83MDBhYzMyMC03NGZmLTAxMzItNWIxNC00OGUwZWIxNGExYzciPgogICAgICA8QXV0aG5Db250ZXh0PgogICAgICAgIDxBdXRobkNvbnRleHRDbGFzc1JlZj51cm46ZmVkZXJhdGlvbjphdXRoZW50aWNhdGlvbjp3aW5kb3dzPC9BdXRobkNvbnRleHRDbGFzc1JlZj4KICAgICAgPC9BdXRobkNvbnRleHQ+CiAgICA8L0F1dGhuU3RhdGVtZW50PgogIDwvQXNzZXJ0aW9uPgo8L3NhbWxwOlJlc3BvbnNlPgo=
diff --git a/tests/data/responses/response_without_reference_uri.xml.base64 b/tests/data/responses/response_without_reference_uri.xml.base64
index 7ceecf01..d830db01 100644
--- a/tests/data/responses/response_without_reference_uri.xml.base64
+++ b/tests/data/responses/response_without_reference_uri.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGQ1OTQzNDdkLTQ5NWYtYjhkMS0wZWUyLTQxY2ZkYTE0ZGQzNSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo5MDAxL3YxL3VzZXJzL2F1dGhvcml6ZS9zYW1sIiBDb25zZW50PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y29uc2VudDp1bnNwZWNpZmllZCIgSW5SZXNwb25zZVRvPSJfZWQ5MTVhNDAtNzRmYi0wMTMyLTViMTYtNDhlMGViMTRhMWM3Ij4NCiAgPElzc3VlciB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+aHR0cDovL2V4YW1wbGUuY29tPC9Jc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5qQ2dlWENQREZsd2pUZ3FnUHAwbVUyVHF3OWc9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPkRmdXByMTh3UityRGFndENQRWZRbFNHSHp3NE5kZlBIWjRIc3pGZTFKUENKWGpmYnlFTTFmZytqemdHYk1NdDZYemdDWGNLSk03RS9DUFNURGt2TWUzRFVKbEh1NERodURPQXovRHN5b0J3V3VWK1JmM1dpTmNGNFhDYzl3QlF6dm4vYXREN3pXNnh3TzdOL2hrQVpKcWZ2SmRkbnBNTUhLR1hxRy9aSFpBdz08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ3FEQ0NBaEdnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVEwRkFEQnhNUXN3Q1FZRFZRUUdFd0oxY3pFVE1CRUdBMVVFQ0F3S1YyRnphR2x1WjNSdmJqRWlNQ0FHQTFVRUNnd1pSbXhoZENCWGIzSnNaQ0JMYm05M2JHVmtaMlVzSUVsdVl6RWNNQm9HQTFVRUF3d1RiR1ZoY200dVpteGhkSGR2Y214a0xtTnZiVEVMTUFrR0ExVUVCd3dDUkVNd0hoY05NVFV3TnpBNE1EazFPVEF6V2hjTk1qVXdOekExTURrMU9UQXpXakJ4TVFzd0NRWURWUVFHRXdKMWN6RVRNQkVHQTFVRUNBd0tWMkZ6YUdsdVozUnZiakVpTUNBR0ExVUVDZ3daUm14aGRDQlhiM0pzWkNCTGJtOTNiR1ZrWjJVc0lFbHVZekVjTUJvR0ExVUVBd3dUYkdWaGNtNHVabXhoZEhkdmNteGtMbU52YlRFTE1Ba0dBMVVFQnd3Q1JFTXdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBTVBEd3NsNW82eDJRb3VOaTEvRTdJVXFSWWoyWW9jSlJGc3VFR1RldnlVKzJhRkNhQk5WL3R0NnNBYk05V1N1dEx1cWpFL2hmYm5sRWNaMDMrZ24wQ29MbDZZbXdiS0tlUnBrSXplVmhveUoxWVlNUUVBVmhMcmR5OFBvd3U4VUNaMFBiQXorbjlka2lSek01cENDTzc3K2d5Y0ZUQkZLSEFBOXFJcFVaWmtQQWdNQkFBR2pVREJPTUIwR0ExVWREZ1FXQkJRSFU1OGl1R3hGbFp1ckJVSndvbGFsSnIrRlJ6QWZCZ05WSFNNRUdEQVdnQlFIVTU4aXVHeEZsWnVyQlVKd29sYWxKcitGUnpBTUJnTlZIUk1FQlRBREFRSC9NQTBHQ1NxR1NJYjNEUUVCRFFVQUE0R0JBQzZpSGZNbWQraE1TUnpma29zaTNDK3d2cUhDTEVVc2czSEZwa1ptNWp4bVREbEY1cU8rQnQwbjB4bWZvcVdCekJNbE5DOFRzR3JhZmhKM3p1OEdORjBMZW8xMXJmYzFHTUdCdnI1SG9aM1dBQXltbkJFREFBb3N4TjZXWlJtajF4YWdhMTMrNnBXZkdCKysyblB3Y1pXUC84ZGtQY1JvZ2V2VjB4MHA1Njg2PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCg0KICA8QXNzZXJ0aW9uIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iXzcwMGFjMzIwLTc0ZmYtMDEzMi01YjE0LTQ4ZTBlYjE0YTFjNyIgSXNzdWVJbnN0YW50PSIyMDE1LTAxLTAyVDIyOjQ4OjQ4WiIgVmVyc2lvbj0iMi4wIj4NCiAgICA8SXNzdWVyPmh0dHA6Ly9leGFtcGxlLmNvbTwvSXNzdWVyPg0KICAgIDxTdWJqZWN0Pg0KICAgICAgPE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c2FtbEB1c2VyLmNvbTwvTmFtZUlEPg0KICAgICAgPFN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPFN1YmplY3RDb25maXJtYXRpb25EYXRhIEluUmVzcG9uc2VUbz0iX2VkOTE1YTQwLTc0ZmItMDEzMi01YjE2LTQ4ZTBlYjE0YTFjNyIgTm90T25PckFmdGVyPSIyMDM4LTAxLTAyVDIyOjUxOjQ4WiIgUmVjaXBpZW50PSJodHRwOi8vbG9jYWxob3N0OjkwMDEvdjEvdXNlcnMvYXV0aG9yaXplL3NhbWwiLz4NCiAgICAgIDwvU3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L1N1YmplY3Q+DQogICAgPENvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE1LTAxLTAyVDIyOjQ4OjQzWiIgTm90T25PckFmdGVyPSIyMDM4LTAxLTAyVDIzOjQ4OjQ4WiI+DQogICAgICA8QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPEF1ZGllbmNlPmh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS88L0F1ZGllbmNlPg0KICAgICAgICA8QXVkaWVuY2U+ZmxhdF93b3JsZDwvQXVkaWVuY2U+DQogICAgICA8L0F1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9Db25kaXRpb25zPg0KICAgIDxBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcyI+DQogICAgICAgIDxBdHRyaWJ1dGVWYWx1ZT5zYW1sQHVzZXIuY29tPC9BdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvQXR0cmlidXRlPg0KICAgIDwvQXR0cmlidXRlU3RhdGVtZW50Pg0KICAgIDxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBTZXNzaW9uSW5kZXg9Il83MDBhYzMyMC03NGZmLTAxMzItNWIxNC00OGUwZWIxNGExYzciPg0KICAgICAgPEF1dGhuQ29udGV4dD4NCiAgICAgICAgPEF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpmZWRlcmF0aW9uOmF1dGhlbnRpY2F0aW9uOndpbmRvd3M8L0F1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9BdXRobkNvbnRleHQ+DQogICAgPC9BdXRoblN0YXRlbWVudD4NCiAgPC9Bc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
\ No newline at end of file
+PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBJRD0icGZ4ZDU5NDM0N2QtNDk1Zi1iOGQxLTBlZTItNDFjZmRhMTRkZDM1IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMS0wMlQyMjo0ODo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vbG9jYWxob3N0OjkwMDEvdjEvdXNlcnMvYXV0aG9yaXplL3NhbWwiIENvbnNlbnQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjb25zZW50OnVuc3BlY2lmaWVkIiBJblJlc3BvbnNlVG89Il9lZDkxNWE0MC03NGZiLTAxMzItNWIxNi00OGUwZWIxNGExYzciPgogIDxJc3N1ZXIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHA6Ly9leGFtcGxlLmNvbTwvSXNzdWVyPgogIDxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPgogIDxkczpTaWduZWRJbmZvPgogICAgPGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4KICAgIDxkczpSZWZlcmVuY2UgVVJJPSIiPgogICAgICA8ZHM6VHJhbnNmb3Jtcz4KICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CiAgICAgIDwvZHM6VHJhbnNmb3Jtcz4KICAgICAgPGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+CiAgICAgIDxkczpEaWdlc3RWYWx1ZT5qQ2dlWENQREZsd2pUZ3FnUHAwbVUyVHF3OWc9PC9kczpEaWdlc3RWYWx1ZT4KICAgIDwvZHM6UmVmZXJlbmNlPgogIDwvZHM6U2lnbmVkSW5mbz4KICA8ZHM6U2lnbmF0dXJlVmFsdWU+bG9SN21DRmlNSURIUHBLeVgzRUd2dzJYeTZycEtFZWZVMDhYS1lWRXJ6MXB3a1BUUFFlYU5iK2RGMHZLai9rNQoyUmJ2Z3ZFUFN2ZGI3RDJOMTY5QjJMTGVmbXpaWTBDY0RKcThkK3lNbnZSNER3YitSUFl6bWJoS29XQ1ZyY3VPCnNvbEUxQTg3WFZjenNpd2JYRWllM2p4RHdDSk5vWi9GRFJRZy80RHRQVmc9PC9kczpTaWduYXR1cmVWYWx1ZT4KPGRzOktleUluZm8+CiAgPGRzOlg1MDlEYXRhPgogICAgPGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDVnpDQ0FjQUNDUURJVkhhTlNCWUw2VEFOQmdrcWhraUc5dzBCQVFzRkFEQndNUXN3Q1FZRFZRUUdFd0pHVWpFT01Bd0dBMVVFQ0F3RlVHRnlhWE14RGpBTUJnTlZCQWNNQlZCaGNtbHpNUll3RkFZRFZRUUtEQTFPYjNaaGNHOXpkQ0JVUlZOVU1Ta3dKd1lKS29aSWh2Y05BUWtCRmhwbWJHOXlaVzUwTG5CcFoyOTFkRUJ1YjNaaGNHOXpkQzVtY2pBZUZ3MHhOREF5TVRNeE16VXpOREJhRncweE5UQXlNVE14TXpVek5EQmFNSEF4Q3pBSkJnTlZCQVlUQWtaU01RNHdEQVlEVlFRSURBVlFZWEpwY3pFT01Bd0dBMVVFQnd3RlVHRnlhWE14RmpBVUJnTlZCQW9NRFU1dmRtRndiM04wSUZSRlUxUXhLVEFuQmdrcWhraUc5dzBCQ1FFV0dtWnNiM0psYm5RdWNHbG5iM1YwUUc1dmRtRndiM04wTG1aeU1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRQ2hMRkhuM0xuTjRKUS83V0NkWXVweGtVZ2NOT1FuUEYreWxsKy9EUHB1eDlucGZZMDU5UElVYXRCOFg3a0NuNWk4dFJ3SXkvaWtISlI2TXI4K01QdmM2Vk9aRHhQTmRadk1vLzhsaHhyYk4zSmRydzN3aFptVS9LUFI5RjNCZEZkdStTTHpyTWwxVERVWmxQdFk5WHpVRlhjcU44SVhjeThUSnpDQmVOZXkzUUlEQVFBQk1BMEdDU3FHU0liM0RRRUJDd1VBQTRHQkFDdEo4ZmVHemUxTkhCNVZ3MThqTVVQdkhvN0gzR3dtajZaREFYUWxhaUFYTXVOQnhOWFZXVndpZmw2VituVzN3OVFhN0Zlby9uWi9PNFRVT0gxbnorYWRrbGNDRDRRcFphRUlibUFicmlQV0pLZ2I0TFdHaHFRcnV3WVI3SXRUUjFNTlg5Z0xiUDB6MHp2REVRbm50L1ZVV0ZFQkxTSnE0WjROcmU4TEZtUzI8L2RzOlg1MDlDZXJ0aWZpY2F0ZT4KICA8L2RzOlg1MDlEYXRhPgo8L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz4KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4KICA8L3NhbWxwOlN0YXR1cz4KCiAgPEFzc2VydGlvbiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il83MDBhYzMyMC03NGZmLTAxMzItNWIxNC00OGUwZWIxNGExYzciIElzc3VlSW5zdGFudD0iMjAxNS0wMS0wMlQyMjo0ODo0OFoiIFZlcnNpb249IjIuMCI+CiAgICA8SXNzdWVyPmh0dHA6Ly9leGFtcGxlLmNvbTwvSXNzdWVyPgogICAgPFN1YmplY3Q+CiAgICAgIDxOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnNhbWxAdXNlci5jb208L05hbWVJRD4KICAgICAgPFN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4KICAgICAgICA8U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJfZWQ5MTVhNDAtNzRmYi0wMTMyLTViMTYtNDhlMGViMTRhMWM3IiBOb3RPbk9yQWZ0ZXI9IjIwMzgtMDEtMDJUMjI6NTE6NDhaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS92MS91c2Vycy9hdXRob3JpemUvc2FtbCIvPgogICAgICA8L1N1YmplY3RDb25maXJtYXRpb24+CiAgICA8L1N1YmplY3Q+CiAgICA8Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTUtMDEtMDJUMjI6NDg6NDNaIiBOb3RPbk9yQWZ0ZXI9IjIwMzgtMDEtMDJUMjM6NDg6NDhaIj4KICAgICAgPEF1ZGllbmNlUmVzdHJpY3Rpb24+CiAgICAgICAgPEF1ZGllbmNlPmh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS88L0F1ZGllbmNlPgogICAgICAgIDxBdWRpZW5jZT5mbGF0X3dvcmxkPC9BdWRpZW5jZT4KICAgICAgPC9BdWRpZW5jZVJlc3RyaWN0aW9uPgogICAgPC9Db25kaXRpb25zPgogICAgPEF0dHJpYnV0ZVN0YXRlbWVudD4KICAgICAgPEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiPgogICAgICAgIDxBdHRyaWJ1dGVWYWx1ZT5zYW1sQHVzZXIuY29tPC9BdHRyaWJ1dGVWYWx1ZT4KICAgICAgPC9BdHRyaWJ1dGU+CiAgICA8L0F0dHJpYnV0ZVN0YXRlbWVudD4KICAgIDxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBTZXNzaW9uSW5kZXg9Il83MDBhYzMyMC03NGZmLTAxMzItNWIxNC00OGUwZWIxNGExYzciPgogICAgICA8QXV0aG5Db250ZXh0PgogICAgICAgIDxBdXRobkNvbnRleHRDbGFzc1JlZj51cm46ZmVkZXJhdGlvbjphdXRoZW50aWNhdGlvbjp3aW5kb3dzPC9BdXRobkNvbnRleHRDbGFzc1JlZj4KICAgICAgPC9BdXRobkNvbnRleHQ+CiAgICA8L0F1dGhuU3RhdGVtZW50PgogIDwvQXNzZXJ0aW9uPgo8L3NhbWxwOlJlc3BvbnNlPgo=
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 3ce359d9..cba1620e 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1661,15 +1661,24 @@ def testIsValidSignFingerprint(self):
# Modified message
self.assertFalse(response_9.is_valid(self.get_request_data()))
- def testIsValidSignWithEmptyReferenceURI(self):
+ def testMessageSignedIsValidSignWithEmptyReferenceURI(self):
settings_info = self.loadSettingsJSON()
del settings_info['idp']['x509cert']
- settings_info['idp']['certFingerprint'] = "194d97e4d8c9c8cfa4b721e5ee497fd9660e5213"
+ settings_info['idp']['certFingerprint'] = "657302a5e11a4794a1e50a705988d66c9377575d"
settings = OneLogin_Saml2_Settings(settings_info)
xml = self.file_contents(join(self.data_path, 'responses', 'response_without_reference_uri.xml.base64'))
response = OneLogin_Saml2_Response(settings, xml)
self.assertTrue(response.is_valid(self.get_request_data()))
+ def testAssertionSignedIsValidSignWithEmptyReferenceURI(self):
+ settings_info = self.loadSettingsJSON()
+ del settings_info['idp']['x509cert']
+ settings_info['idp']['certFingerprint'] = "657302a5e11a4794a1e50a705988d66c9377575d"
+ settings = OneLogin_Saml2_Settings(settings_info)
+ xml = self.file_contents(join(self.data_path, 'responses', 'response_without_assertion_reference_uri.xml.base64'))
+ response = OneLogin_Saml2_Response(settings, xml)
+ self.assertTrue(response.is_valid(self.get_request_data()))
+
def testIsValidWithoutInResponseTo(self):
"""
If assertion contains InResponseTo but not the Response tag, we should
From 2cda97555aa7fe928d05ee61a8323773a13c5687 Mon Sep 17 00:00:00 2001
From: Florent PIGOUT
Date: Wed, 14 Aug 2019 17:12:52 +0200
Subject: [PATCH 138/331] Update changelog
---
changelog.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/changelog.md b/changelog.md
index f0e28708..f93b5878 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,7 @@
# python3-saml changelog
+### 1.8.1 (unreleased)
+* Do not touch message when validate his signature
+
### 1.8.0 (Sep 11, 2019)
* Set true as the default value for strict setting
* [#152](https://github.com/onelogin/python3-saml/pull/152/files) Don't clean xsd and xsi namespaces
From a6b07e92242fdf8b37d9384b5ac2e4d678d652ee Mon Sep 17 00:00:00 2001
From: Jordan Ephron
Date: Mon, 18 Nov 2019 13:47:50 -0800
Subject: [PATCH 139/331] Fix typo: Attribute -> Assertion
Fixes a typo in which the Assertion Consumer Service was erroniously referred to as the Attribute Consumer Service
See line 497 of https://www.oasis-open.org/committees/download.php/56783/sstc-saml-profiles-errata-2.0-wd-07-diff.pdf
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 106e93c2..0217a7c2 100644
--- a/README.md
+++ b/README.md
@@ -601,7 +601,7 @@ auth.login() # Method that builds and sends the AuthNRequest
The ``AuthNRequest`` will be sent signed or unsigned based on the security info of the ``advanced_settings.json`` file (i.e. ``authnRequestsSigned``).
-The IdP will then return the SAML Response to the user's client. The client is then forwarded to the **Attribute Consumer Service (ACS)** of the SP with this information.
+The IdP will then return the SAML Response to the user's client. The client is then forwarded to the **Assertion Consumer Service (ACS)** of the SP with this information.
We can set a ``return_to`` url parameter to the login function and that will be converted as a ``RelayState`` parameter:
@@ -650,7 +650,7 @@ saml_settings = OneLogin_Saml2_Settings(settings=None, custom_base_path=None, sp
```
to get the settings object and with the ``sp_validation_only=True`` parameter we will avoid the IdP settings validation.
-***Attribute Consumer Service (ACS)***
+***Assertion Consumer Service (ACS)***
This code handles the SAML response that the IdP forwards to the SP through the user's client.
From c13e998d36171d096518a76b4b6cef048b8ef717 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 19 Nov 2019 21:13:31 +0100
Subject: [PATCH 140/331] Fix failOnAuthnContextMismatch code
---
README.md | 2 +-
src/onelogin/saml2/authn_request.py | 4 +---
src/onelogin/saml2/response.py | 4 ++--
src/onelogin/saml2/settings.py | 1 +
tests/src/OneLogin/saml2_tests/response_test.py | 2 +-
5 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 106e93c2..e9e689d2 100644
--- a/README.md
+++ b/README.md
@@ -410,7 +410,7 @@ In addition to the required settings data (idp, sp), extra settings can be defin
"requestedAuthnContext": true,
// Allows the authn comparison parameter to be set, defaults to 'exact' if the setting is not present.
"requestedAuthnContextComparison": "exact",
- // Set to true to check that the AuthnContext received matches the one requested.
+ // Set to true to check that the AuthnContext(s) received match(es) the requested.
"failOnAuthnContextMismatch": false,
// In some environment you will need to set how long the published metadata of the Service Provider gonna be valid.
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index 57da1561..73547735 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -95,9 +95,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
requested_authn_context_str = ''
if security['requestedAuthnContext'] is not False:
- authn_comparison = 'exact'
- if 'requestedAuthnContextComparison' in security.keys():
- authn_comparison = security['requestedAuthnContextComparison']
+ authn_comparison = security['requestedAuthnContextComparison']
if security['requestedAuthnContext'] is True:
requested_authn_context_str = """
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 8aa309c5..83eafe7d 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -166,10 +166,10 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
requested_authn_contexts = security['requestedAuthnContext']
if security['failOnAuthnContextMismatch'] and requested_authn_contexts and requested_authn_contexts is not True:
authn_contexts = self.get_authn_contexts()
- unmatched_contexts = set(requested_authn_contexts).difference(authn_contexts)
+ unmatched_contexts = set(authn_contexts).difference(requested_authn_contexts)
if unmatched_contexts:
raise OneLogin_Saml2_ValidationError(
- 'The AuthnContext "%s" didn\'t include requested context "%s"' % (', '.join(authn_contexts), ', '.join(unmatched_contexts)),
+ 'The AuthnContext "%s" was not a requested context "%s"' % (', '.join(unmatched_contexts), ', '.join(requested_authn_contexts)),
OneLogin_Saml2_ValidationError.AUTHN_CONTEXT_MISMATCH
)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 5057a5ab..8acee0be 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -310,6 +310,7 @@ def __add_default_values(self):
self.__sp.setdefault('privateKey', '')
self.__security.setdefault('requestedAuthnContext', True)
+ self.__security.setdefault('requestedAuthnContextComparison', 'exact')
self.__security.setdefault('failOnAuthnContextMismatch', False)
def check_settings(self, settings):
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 3ce359d9..ad0ef3b8 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1056,7 +1056,7 @@ def testIsInValidAuthenticationContext(self):
# check that we catch when the contexts don't match
response = OneLogin_Saml2_Response(settings, message)
self.assertFalse(response.is_valid(request_data))
- self.assertIn('The AuthnContext "%s" didn\'t include requested context "%s"' % (password_context, two_factor_context), response.get_error())
+ self.assertIn('The AuthnContext "%s" was not a requested context "%s"' % (password_context, two_factor_context), response.get_error())
# now drop in the expected AuthnContextClassRef and see that it passes
original_message = compat.to_string(OneLogin_Saml2_Utils.b64decode(message))
From 5f162dc9947bb34cd7502177f35e4edbf9a477dd Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 20 Nov 2019 10:20:46 +0100
Subject: [PATCH 141/331] Allow any number of decimal places for seconds on
SAML datetimes
---
src/onelogin/saml2/utils.py | 17 ++++++++++++++---
tests/src/OneLogin/saml2_tests/utils_test.py | 8 ++++++++
2 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 4647c230..3045f5fd 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -65,6 +65,10 @@ class OneLogin_Saml2_Utils(object):
RESPONSE_SIGNATURE_XPATH = '/samlp:Response/ds:Signature'
ASSERTION_SIGNATURE_XPATH = '/samlp:Response/saml:Assertion/ds:Signature'
+ TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
+ TIME_FORMAT_2 = "%Y-%m-%dT%H:%M:%S.%fZ"
+ TIME_FORMAT_WITH_FRAGMENT = re.compile(r'^(\d{4,4}-\d{2,2}-\d{2,2}T\d{2,2}:\d{2,2}:\d{2,2})(\.\d*)?Z?$')
+
@staticmethod
def escape_url(url, lowercase_urlencoding=False):
"""
@@ -401,7 +405,7 @@ def parse_time_to_SAML(time):
:rtype: string
"""
data = datetime.utcfromtimestamp(float(time))
- return data.strftime('%Y-%m-%dT%H:%M:%SZ')
+ return data.strftime(OneLogin_Saml2_Utils.TIME_FORMAT)
@staticmethod
def parse_SAML_to_time(timestr):
@@ -416,9 +420,16 @@ def parse_SAML_to_time(timestr):
:rtype: int
"""
try:
- data = datetime.strptime(timestr, '%Y-%m-%dT%H:%M:%SZ')
+ data = datetime.strptime(timestr, OneLogin_Saml2_Utils.TIME_FORMAT)
except ValueError:
- data = datetime.strptime(timestr, '%Y-%m-%dT%H:%M:%S.%fZ')
+ try:
+ data = datetime.strptime(timestr, OneLogin_Saml2_Utils.TIME_FORMAT_2)
+ except ValueError:
+ elem = OneLogin_Saml2_Utils.TIME_FORMAT_WITH_FRAGMENT.match(timestr)
+ if not elem:
+ raise Exception("time data %s does not match format %s" % (timestr, r'yyyy-mm-ddThh:mm:ss(\.s+)?Z'))
+ data = datetime.strptime(elem.groups()[0] + "Z", OneLogin_Saml2_Utils.TIME_FORMAT)
+
return calendar.timegm(data.utctimetuple())
@staticmethod
diff --git a/tests/src/OneLogin/saml2_tests/utils_test.py b/tests/src/OneLogin/saml2_tests/utils_test.py
index 366a125b..597c4852 100644
--- a/tests/src/OneLogin/saml2_tests/utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/utils_test.py
@@ -450,6 +450,14 @@ def testParseSAML2Time(self):
saml_time2 = '2013-12-10T04:39:31.120Z'
self.assertEqual(time, OneLogin_Saml2_Utils.parse_SAML_to_time(saml_time2))
+ # Now test if toolkit supports microseconds
+ saml_time3 = '2013-12-10T04:39:31.120240Z'
+ self.assertEqual(time, OneLogin_Saml2_Utils.parse_SAML_to_time(saml_time3))
+
+ # Now test if toolkit supports nanoseconds
+ saml_time4 = '2013-12-10T04:39:31.120240360Z'
+ self.assertEqual(time, OneLogin_Saml2_Utils.parse_SAML_to_time(saml_time4))
+
def testParseTime2SAML(self):
"""
Tests the parse_time_to_SAML method of the OneLogin_Saml2_Utils
From 218cb497950a222b09160edfb888e46b4bef089d Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 20 Nov 2019 13:13:14 +0100
Subject: [PATCH 142/331] Update demos
---
demo-django/demo/views.py | 5 +++--
demo-django/requirements.txt | 2 +-
demo-flask/index.py | 6 ++++++
demo-flask/requirements.txt | 2 +-
demo-flask/templates/index.html | 3 +++
5 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 4c61e599..c02d226e 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -86,8 +86,7 @@ def index(request):
request.session['samlSessionIndex'] = auth.get_session_index()
if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))
- else:
- if auth.get_settings().is_debug_active():
+ elif auth.get_settings().is_debug_active():
error_reason = auth.get_last_error_reason()
elif 'sls' in req['get_data']:
request_id = None
@@ -101,6 +100,8 @@ def index(request):
return HttpResponseRedirect(url)
else:
success_slo = True
+ elif auth.get_settings().is_debug_active():
+ error_reason = auth.get_last_error_reason()
if 'samlUserdata' in request.session:
paint_logout = True
diff --git a/demo-django/requirements.txt b/demo-django/requirements.txt
index 1aa1df39..a4a895b0 100644
--- a/demo-django/requirements.txt
+++ b/demo-django/requirements.txt
@@ -1,2 +1,2 @@
-Django==1.11
+Django==1.11.26
python3-saml
diff --git a/demo-flask/index.py b/demo-flask/index.py
index b344da4c..c37dc9c7 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -39,6 +39,7 @@ def index():
req = prepare_flask_request(request)
auth = init_saml_auth(req)
errors = []
+ error_reason = None
not_auth_warn = False
success_slo = False
attributes = False
@@ -86,6 +87,8 @@ def index():
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in request.form and self_url != request.form['RelayState']:
return redirect(auth.redirect_to(request.form['RelayState']))
+ elif auth.get_settings().is_debug_active():
+ error_reason = auth.get_last_error_reason()
elif 'sls' in request.args:
request_id = None
if 'LogoutRequestID' in session:
@@ -98,6 +101,8 @@ def index():
return redirect(url)
else:
success_slo = True
+ elif auth.get_settings().is_debug_active():
+ error_reason = auth.get_last_error_reason()
if 'samlUserdata' in session:
paint_logout = True
@@ -107,6 +112,7 @@ def index():
return render_template(
'index.html',
errors=errors,
+ error_reason=error_reason,
not_auth_warn=not_auth_warn,
success_slo=success_slo,
attributes=attributes,
diff --git a/demo-flask/requirements.txt b/demo-flask/requirements.txt
index 335836f7..d9340937 100644
--- a/demo-flask/requirements.txt
+++ b/demo-flask/requirements.txt
@@ -1 +1 @@
-flask==0.10.1
+flask==1.0
diff --git a/demo-flask/templates/index.html b/demo-flask/templates/index.html
index ad42cf5c..fd1ff051 100644
--- a/demo-flask/templates/index.html
+++ b/demo-flask/templates/index.html
@@ -10,6 +10,9 @@
{{err}}
{% endfor %}
+ {% if error_reason %}
+ {{error_reason}}
+ {% endif %}
{% endif %}
From 2fdd7ef4bb9291e7e5f61164cb02ab89702f10a9 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 20 Nov 2019 13:16:14 +0100
Subject: [PATCH 143/331] Fix #160. Fix get_attribute docstring
---
src/onelogin/saml2/auth.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 2a9b7cde..1bf5f8a6 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -315,7 +315,7 @@ def get_attribute(self, name):
:param name: Name of the attribute
:type name: string
- :returns: Attribute value if exists or []
+ :returns: Attribute value if exists or None
:rtype: string
"""
assert isinstance(name, compat.str_type)
From 4d28828d8d67c4be2f44274e1fdaba3e31d1d0ce Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 20 Nov 2019 17:31:34 +0100
Subject: [PATCH 144/331] Update tornado version. Remove unnecesary doc.
Improve demo
---
README.md | 29 ++---------------------------
demo-tornado/requirements.txt | 9 +--------
demo-tornado/templates/index.html | 3 +++
demo-tornado/views.py | 24 ++++++++++++++++--------
4 files changed, 22 insertions(+), 43 deletions(-)
diff --git a/README.md b/README.md
index a2d2ec7b..f0e16ff0 100644
--- a/README.md
+++ b/README.md
@@ -178,6 +178,8 @@ This folder contains a Pyramid project that will be used as demo to show how to
This folder contains a Tornado project that will be used as demo to show how to add SAML support to the Tornado Framework. ``views.py`` (with its ``settings.py``) is the main Flask file that has all the code, this file uses the templates stored at the ``templates`` folder. In the ``saml`` folder we found the ``certs`` folder to store the X.509 public and private key, and the SAML toolkit settings (``settings.json`` and ``advanced_settings.json``).
+It requires python3.5 (it's using tornado 6.0.3)
+
#### setup.py ####
Setup script is the centre of all activity in building, distributing, and installing modules.
@@ -1253,33 +1255,6 @@ First we need to edit the ``saml/settings.json`` file, configure the SP part and
Once the SP is configured, the metadata of the SP is published at the ``/metadata`` url. Based on that info, configure the IdP.
-##### Test with keycloack #####
-
-You can test your SP with every compatible IdP, for example Keycloack by Red Hat (Check if you need also authorization and not only authentication )
-
-###### Install Docker ######
-
-Install docker as suggested by [docker guide](https://docs.docker.com/install/linux/docker-ce/ubuntu/)
-
-###### Keycloack starting ######
-
-First run:
-* docker run --name keycloackContainer -d -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_VENDOR=H2 jboss/keycloak
-
-After first run:
-* sudo docker start keycloackContainer
-
-Remember to stop keycloack after usage:
-* sudo docker stop keycloackContainer
-
-
-###### Keycloack useful urls ######
-
-* master: http://localhost:8080/auth/admin
-* users: http://localhost:8080/auth/realms/idp_dacd/account/
-* saml request: http://localhost:8080/auth/realms/idp_dacd/protocol/saml
-* metadata: http://localhost:8080/auth/realms/idp_dacd/protocol/saml/descriptor
-
#### How it works ####
1. First time you access to the main view (http://localhost:8000), you can select to login and return to the same view or login and be redirected to ``/?attrs`` (attrs view).
diff --git a/demo-tornado/requirements.txt b/demo-tornado/requirements.txt
index 66787013..17ee9ac2 100644
--- a/demo-tornado/requirements.txt
+++ b/demo-tornado/requirements.txt
@@ -1,8 +1 @@
-defusedxml==0.5.0
-isodate==0.6.0
-lxml==4.3.3
-pkgconfig==1.5.1
-python3-saml==1.6.0
-six==1.12.0
-tornado==6.0.2
-xmlsec==1.3.3
+tornado==6.0.3
diff --git a/demo-tornado/templates/index.html b/demo-tornado/templates/index.html
index 52bee968..f8dfac0a 100644
--- a/demo-tornado/templates/index.html
+++ b/demo-tornado/templates/index.html
@@ -9,6 +9,9 @@
{% for err in errors %}
{{err}}
{% end %}
+ {% if error_reason %}
+ {{error_reason}}
+ {% end %}
{% end %}
diff --git a/demo-tornado/views.py b/demo-tornado/views.py
index 5ac3e03a..ed195fb7 100644
--- a/demo-tornado/views.py
+++ b/demo-tornado/views.py
@@ -31,31 +31,36 @@ class IndexHandler(tornado.web.RequestHandler):
def post(self):
req = prepare_tornado_request(self.request)
auth = init_saml_auth(req)
+ error_reason = None
attributes = False
paint_logout = False
+ success_slo = False
auth.process_response()
errors = auth.get_errors()
not_auth_warn = not auth.is_authenticated()
if len(errors) == 0:
- session['samlUserdata'] = auth.get_attributes()
- session['samlNameId'] = auth.get_nameid()
- session['samlSessionIndex'] = auth.get_session_index()
- self_url = OneLogin_Saml2_Utils.get_self_url(req)
- if 'RelayState' in self.request.arguments and self_url != self.request.arguments['RelayState'][0].decode('utf-8'):
+ session['samlUserdata'] = auth.get_attributes()
+ session['samlNameId'] = auth.get_nameid()
+ session['samlSessionIndex'] = auth.get_session_index()
+ self_url = OneLogin_Saml2_Utils.get_self_url(req)
+ if 'RelayState' in self.request.arguments and self_url != self.request.arguments['RelayState'][0].decode('utf-8'):
return self.redirect(self.request.arguments['RelayState'][0].decode('utf-8'))
+ elif auth.get_settings().is_debug_active():
+ error_reason = auth.get_last_error_reason()
if 'samlUserdata' in session:
paint_logout = True
if len(session['samlUserdata']) > 0:
attributes = session['samlUserdata'].items()
- self.render('index.html',errors=errors,not_auth_warn=not_auth_warn,attributes=attributes,paint_logout=paint_logout)
+ self.render('index.html',errors=errors,error_reason=error_reason,not_auth_warn=not_auth_warn,success_slo=success_slo,attributes=attributes,paint_logout=paint_logout)
def get(self):
req = prepare_tornado_request(self.request)
auth = init_saml_auth(req)
+ error_reason = None
errors = []
not_auth_warn = False
success_slo = False
@@ -90,6 +95,8 @@ def get(self):
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in self.request.arguments and self_url != self.request.arguments['RelayState'][0].decode('utf-8'):
return self.redirect(auth.redirect_to(self.request.arguments['RelayState'][0].decode('utf-8')))
+ elif auth.get_settings().is_debug_active():
+ error_reason = auth.get_last_error_reason()
elif 'sls' in req['get_data']:
print('-sls-')
dscb = lambda: session.clear() ## clear out the session
@@ -100,14 +107,15 @@ def get(self):
return self.redirect(url)
else:
success_slo = True
-
+ elif auth.get_settings().is_debug_active():
+ error_reason = auth.get_last_error_reason()
if 'samlUserdata' in session:
print('-samlUserdata-')
paint_logout = True
if len(session['samlUserdata']) > 0:
attributes = session['samlUserdata'].items()
print("ATTRIBUTES", attributes)
- self.render('index.html',errors=errors,not_auth_warn=not_auth_warn,success_slo=success_slo,attributes=attributes,paint_logout=paint_logout)
+ self.render('index.html',errors=errors,error_reason=error_reason,not_auth_warn=not_auth_warn,success_slo=success_slo,attributes=attributes,paint_logout=paint_logout)
class AttrsHandler(tornado.web.RequestHandler):
def get(self):
From 4e5eb138183d8d2ae99b1eb3ef9ec9fe43e35cbd Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 20 Nov 2019 18:15:47 +0100
Subject: [PATCH 145/331] Fix flake8
---
demo-tornado/views.py | 29 ++++++++++++++++-------------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/demo-tornado/views.py b/demo-tornado/views.py
index ed195fb7..6fe72bcd 100644
--- a/demo-tornado/views.py
+++ b/demo-tornado/views.py
@@ -5,18 +5,18 @@
import tornado.httputil
from onelogin.saml2.auth import OneLogin_Saml2_Auth
-from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils
-##Global session info
+# Global session info
session = {}
+
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", IndexHandler),
(r"/attrs", AttrsHandler),
- (r"/metadata",MetadataHandler),
+ (r"/metadata", MetadataHandler),
]
settings = {
"template_path": Settings.TEMPLATE_PATH,
@@ -55,7 +55,7 @@ def post(self):
if len(session['samlUserdata']) > 0:
attributes = session['samlUserdata'].items()
- self.render('index.html',errors=errors,error_reason=error_reason,not_auth_warn=not_auth_warn,success_slo=success_slo,attributes=attributes,paint_logout=paint_logout)
+ self.render('index.html', errors=errors, error_reason=error_reason, not_auth_warn=not_auth_warn, success_slo=success_slo, attributes=attributes, paint_logout=paint_logout)
def get(self):
req = prepare_tornado_request(self.request)
@@ -99,7 +99,7 @@ def get(self):
error_reason = auth.get_last_error_reason()
elif 'sls' in req['get_data']:
print('-sls-')
- dscb = lambda: session.clear() ## clear out the session
+ dscb = lambda: session.clear() # clear out the session
url = auth.process_slo(delete_session_cb=dscb)
errors = auth.get_errors()
if len(errors) == 0:
@@ -115,7 +115,8 @@ def get(self):
if len(session['samlUserdata']) > 0:
attributes = session['samlUserdata'].items()
print("ATTRIBUTES", attributes)
- self.render('index.html',errors=errors,error_reason=error_reason,not_auth_warn=not_auth_warn,success_slo=success_slo,attributes=attributes,paint_logout=paint_logout)
+ self.render('index.html', errors=errors, error_reason=error_reason, not_auth_warn=not_auth_warn, success_slo=success_slo, attributes=attributes, paint_logout=paint_logout)
+
class AttrsHandler(tornado.web.RequestHandler):
def get(self):
@@ -127,27 +128,28 @@ def get(self):
if len(session['samlUserdata']) > 0:
attributes = session['samlUserdata'].items()
- self.render('attrs.html',paint_logout=paint_logout,attributes=attributes)
+ self.render('attrs.html', paint_logout=paint_logout, attributes=attributes)
+
class MetadataHandler(tornado.web.RequestHandler):
def get(self):
req = prepare_tornado_request(self.request)
auth = init_saml_auth(req)
saml_settings = auth.get_settings()
- #saml_settings = OneLogin_Saml2_Settings(settings=None, custom_base_path=settings.SAML_FOLDER, sp_validation_only=True)
metadata = saml_settings.get_sp_metadata()
errors = saml_settings.validate_metadata(metadata)
if len(errors) == 0:
- #resp = HttpResponse(content=metadata, content_type='text/xml')
- self.set_header('Content-Type','text/xml')
+ # resp = HttpResponse(content=metadata, content_type='text/xml')
+ self.set_header('Content-Type', 'text/xml')
self.write(metadata)
else:
- #resp = HttpResponseServerError(content=', '.join(errors))
+ # resp = HttpResponseServerError(content=', '.join(errors))
self.write(', '.join(errors))
- #return resp
+ # return resp
-def prepare_tornado_request(request):
+
+def prepare_tornado_request(request):
dataDict = {}
for key in request.arguments:
@@ -164,6 +166,7 @@ def prepare_tornado_request(request):
}
return result
+
def init_saml_auth(req):
auth = OneLogin_Saml2_Auth(req, custom_base_path=Settings.SAML_PATH)
return auth
From c660f6f639a783c66f05c003bc2a04ba6b162048 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 20 Nov 2019 18:17:19 +0100
Subject: [PATCH 146/331] Release 1.9.0
---
changelog.md | 7 +++++--
setup.py | 2 +-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/changelog.md b/changelog.md
index f93b5878..b5e5da52 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,9 @@
# python3-saml changelog
-### 1.8.1 (unreleased)
-* Do not touch message when validate his signature
+### 1.9.0 (Nov 20, 2019)
+* Allow any number of decimal places for seconds on SAML datetimes
+* Fix failOnAuthnContextMismatch code
+* Improve signature validation when no reference uri
+* Update demo versions. Improve them and add Tornado demo.
### 1.8.0 (Sep 11, 2019)
* Set true as the default value for strict setting
diff --git a/setup.py b/setup.py
index cb483f2d..5a928564 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.8.0',
+ version='1.9.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From 57da6e5c0e3a5c90092cd9213a49acd5a6a41b49 Mon Sep 17 00:00:00 2001
From: nirn
Date: Thu, 19 Dec 2019 11:10:39 +0200
Subject: [PATCH 147/331] Add samlUserdata to demo-flask session
Unlike demo-django, samlUserdata isn't set in demo-flask's session and /attrs/ always lacks attributes.
---
demo-flask/index.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/demo-flask/index.py b/demo-flask/index.py
index c37dc9c7..b523cb92 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -79,6 +79,7 @@ def index():
if len(errors) == 0:
if 'AuthNRequestID' in session:
del session['AuthNRequestID']
+ session['samlUserdata'] = auth.get_attributes()
session['samlNameId'] = auth.get_nameid()
session['samlNameIdFormat'] = auth.get_nameid_format()
session['samlNameIdNameQualifier'] = auth.get_nameid_nq()
@@ -88,7 +89,7 @@ def index():
if 'RelayState' in request.form and self_url != request.form['RelayState']:
return redirect(auth.redirect_to(request.form['RelayState']))
elif auth.get_settings().is_debug_active():
- error_reason = auth.get_last_error_reason()
+ error_reason = auth.get_last_error_reason()
elif 'sls' in request.args:
request_id = None
if 'LogoutRequestID' in session:
From 0c4f8463b74e77a4032476b74101cebc7f55046d Mon Sep 17 00:00:00 2001
From: Rahul Raina
Date: Tue, 7 Jan 2020 17:53:28 +0800
Subject: [PATCH 148/331] Adding support to read idp.crt from certs folder
---
src/onelogin/saml2/settings.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 8acee0be..1d8e9518 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -558,7 +558,12 @@ def get_idp_cert(self):
:returns: IdP public cert
:rtype: string
"""
- return self.__idp.get('x509cert')
+ cert = self.__idp.get('x509cert')
+ cert_file_name = self.__paths['cert'] + 'idp.crt'
+ if not cert and exists(cert_file_name):
+ with open(cert_file_name) as f:
+ cert = f.read()
+ return cert or None
def get_idp_data(self):
"""
From 222620128c48615e37ca3f3dd76866308958043d Mon Sep 17 00:00:00 2001
From: Rahul Raina
Date: Tue, 7 Jan 2020 18:23:57 +0800
Subject: [PATCH 149/331] Adding tests and new settings file
---
tests/settings/settings9.json | 46 +++++++++++++++++++
.../src/OneLogin/saml2_tests/settings_test.py | 22 +++++++++
2 files changed, 68 insertions(+)
create mode 100644 tests/settings/settings9.json
diff --git a/tests/settings/settings9.json b/tests/settings/settings9.json
new file mode 100644
index 00000000..351c5c95
--- /dev/null
+++ b/tests/settings/settings9.json
@@ -0,0 +1,46 @@
+{
+ "strict": false,
+ "debug": false,
+ "custom_base_path": "../../../tests/data/customPath/",
+ "sp": {
+ "entityId": "http://stuff.com/endpoints/metadata.php",
+ "assertionConsumerService": {
+ "url": "http://stuff.com/endpoints/endpoints/acs.php"
+ },
+ "singleLogoutService": {
+ "url": "http://stuff.com/endpoints/endpoints/sls.php"
+ },
+ "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ },
+ "idp": {
+ "entityId": "http://idp.example.com/",
+ "singleSignOnService": {
+ "url": "http://idp.example.com/SSOService.php"
+ },
+ "singleLogoutService": {
+ "url": "http://idp.example.com/SingleLogoutService.php"
+ }
+ },
+ "security": {
+ "authnRequestsSigned": false,
+ "wantAssertionsSigned": false,
+ "signMetadata": false
+ },
+ "contactPerson": {
+ "technical": {
+ "givenName": "technical_name",
+ "emailAddress": "technical@example.com"
+ },
+ "support": {
+ "givenName": "support_name",
+ "emailAddress": "support@example.com"
+ }
+ },
+ "organization": {
+ "en-US": {
+ "name": "sp_test",
+ "displayname": "SP test",
+ "url": "http://sp.example.com"
+ }
+ }
+}
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index 942fdd96..32b037fe 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -222,6 +222,28 @@ def testGetSPKey(self):
settings_3 = OneLogin_Saml2_Settings(settings_data, custom_base_path=custom_base_path)
self.assertIsNone(settings_3.get_sp_key())
+ def testGetIDPCert(self):
+ """
+ Tests the get_idp_cert method of the OneLogin_Saml2_Settings
+ """
+
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON('settings9.json'))
+ cert = "-----BEGIN CERTIFICATE-----\nMIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMC\nTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYD\nVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG\n9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4\nMTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xi\nZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2Zl\naWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5v\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LO\nNoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHIS\nKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d\n1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8\nBUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7n\nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2Qar\nQ4/67OZfHd7R+POBXhophSMv1ZOo\n-----END CERTIFICATE-----\n"
+ self.assertEqual(cert, settings.get_idp_cert())
+
+ settings_data = self.loadSettingsJSON()
+
+ settings = OneLogin_Saml2_Settings(settings_data)
+ settings_data['idp']['x509cert'] = cert
+ self.assertEqual(cert, settings.get_sp_cert())
+
+ del settings_data['idp']['x509cert']
+ del settings_data['custom_base_path']
+ custom_base_path = dirname(__file__)
+
+ settings_3 = OneLogin_Saml2_Settings(settings_data, custom_base_path=custom_base_path)
+ self.assertIsNone(settings_3.get_idp_cert())
+
def testFormatIdPCert(self):
"""
Tests the format_idp_cert method of the OneLogin_Saml2_Settings
From 7668721e9677f087e256798a97f8c33a46872c69 Mon Sep 17 00:00:00 2001
From: Rahul Raina
Date: Wed, 8 Jan 2020 14:32:13 +0800
Subject: [PATCH 150/331] Adding helper methods to load idp_cert
---
src/onelogin/saml2/auth.py | 5 +-
src/onelogin/saml2/logout_request.py | 2 +-
src/onelogin/saml2/response.py | 2 +-
tests/settings/settings10.json | 46 +++++++++++++++++++
.../src/OneLogin/saml2_tests/response_test.py | 6 +--
5 files changed, 53 insertions(+), 8 deletions(-)
create mode 100644 tests/settings/settings10.json
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 1bf5f8a6..72d37847 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -605,8 +605,7 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
return True
idp_data = self.get_settings().get_idp_data()
-
- exists_x509cert = 'x509cert' in idp_data and idp_data['x509cert']
+ exists_x509cert = self.get_settings().get_idp_cert() is not None
exists_multix509sign = 'x509certMulti' in idp_data and \
'signing' in idp_data['x509certMulti'] and \
idp_data['x509certMulti']['signing']
@@ -646,7 +645,7 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
)
else:
- cert = idp_data['x509cert']
+ cert = self.get_settings().get_idp_cert()
if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query,
OneLogin_Saml2_Utils.b64decode(signature),
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index 252726b7..ef4d05e2 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -72,7 +72,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
if exists_multix509enc:
cert = idp_data['x509certMulti']['encryption'][0]
else:
- cert = idp_data['x509cert']
+ cert = self.__settings.get_idp_cert()
if name_id is not None:
if not name_id_format and sp_data['NameIDFormat'] != OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED:
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 83eafe7d..a5004fea 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -293,7 +293,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND
)
else:
- cert = idp_data.get('x509cert', None)
+ cert = self.__settings.get_idp_cert()
fingerprint = idp_data.get('certFingerprint', None)
if fingerprint:
fingerprint = OneLogin_Saml2_Utils.format_finger_print(fingerprint)
diff --git a/tests/settings/settings10.json b/tests/settings/settings10.json
new file mode 100644
index 00000000..f118b4d0
--- /dev/null
+++ b/tests/settings/settings10.json
@@ -0,0 +1,46 @@
+{
+ "strict": false,
+ "debug": false,
+ "sp": {
+ "entityId": "http://stuff.com/endpoints/metadata.php",
+ "assertionConsumerService": {
+ "url": "http://stuff.com/endpoints/endpoints/acs.php"
+ },
+ "singleLogoutService": {
+ "url": "http://stuff.com/endpoints/endpoints/sls.php"
+ },
+ "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ },
+ "idp": {
+ "entityId": "http://idp.example.com/",
+ "singleSignOnService": {
+ "url": "http://idp.example.com/SSOService.php"
+ },
+ "singleLogoutService": {
+ "url": "http://idp.example.com/SingleLogoutService.php"
+ },
+ "x509cert": "MIICbDCCAdWgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBTMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRgwFgYDVQQDDA9pZHAuZXhhbXBsZS5jb20wHhcNMTQwOTIzMTIyNDA4WhcNNDIwMjA4MTIyNDA4WjBTMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRgwFgYDVQQDDA9pZHAuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOWA+YHU7cvPOrBOfxCscsYTJB+kH3MaA9BFrSHFS+KcR6cw7oPSktIJxUgvDpQbtfNcOkE/tuOPBDoech7AXfvH6d7Bw7xtW8PPJ2mB5Hn/HGW2roYhxmfh3tR5SdwN6i4ERVF8eLkvwCHsNQyK2Ref0DAJvpBNZMHCpS24916/AgMBAAGjUDBOMB0GA1UdDgQWBBQ77/qVeiigfhYDITplCNtJKZTM8DAfBgNVHSMEGDAWgBQ77/qVeiigfhYDITplCNtJKZTM8DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAJO2j/1uO80E5C2PM6Fk9mzerrbkxl7AZ/mvlbOn+sNZE+VZ1AntYuG8ekbJpJtG1YfRfc7EA9mEtqvv4dhv7zBy4nK49OR+KpIBjItWB5kYvrqMLKBa32sMbgqqUqeF1ENXKjpvLSuPdfGJZA3dNa/+Dyb8GGqWe707zLyc5F8m"
+ },
+ "security": {
+ "authnRequestsSigned": false,
+ "wantAssertionsSigned": false,
+ "signMetadata": false
+ },
+ "contactPerson": {
+ "technical": {
+ "givenName": "technical_name",
+ "emailAddress": "technical@example.com"
+ },
+ "support": {
+ "givenName": "support_name",
+ "emailAddress": "support@example.com"
+ }
+ },
+ "organization": {
+ "en-US": {
+ "name": "sp_test",
+ "displayname": "SP test",
+ "url": "http://sp.example.com"
+ }
+ }
+}
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index e6641b73..7d9934a1 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1473,7 +1473,7 @@ def testIsValid2(self):
response_2 = OneLogin_Saml2_Response(settings_2, xml_2)
self.assertTrue(response_2.is_valid(self.get_request_data()))
- settings_info_3 = self.loadSettingsJSON('settings2.json')
+ settings_info_3 = self.loadSettingsJSON('settings10.json')
idp_cert = OneLogin_Saml2_Utils.format_cert(settings_info_3['idp']['x509cert'])
settings_info_3['idp']['certFingerprint'] = OneLogin_Saml2_Utils.calculate_x509_fingerprint(idp_cert)
settings_info_3['idp']['x509cert'] = ''
@@ -1662,7 +1662,7 @@ def testIsValidSignFingerprint(self):
self.assertFalse(response_9.is_valid(self.get_request_data()))
def testMessageSignedIsValidSignWithEmptyReferenceURI(self):
- settings_info = self.loadSettingsJSON()
+ settings_info = self.loadSettingsJSON("settings10.json")
del settings_info['idp']['x509cert']
settings_info['idp']['certFingerprint'] = "657302a5e11a4794a1e50a705988d66c9377575d"
settings = OneLogin_Saml2_Settings(settings_info)
@@ -1671,7 +1671,7 @@ def testMessageSignedIsValidSignWithEmptyReferenceURI(self):
self.assertTrue(response.is_valid(self.get_request_data()))
def testAssertionSignedIsValidSignWithEmptyReferenceURI(self):
- settings_info = self.loadSettingsJSON()
+ settings_info = self.loadSettingsJSON('settings10.json')
del settings_info['idp']['x509cert']
settings_info['idp']['certFingerprint'] = "657302a5e11a4794a1e50a705988d66c9377575d"
settings = OneLogin_Saml2_Settings(settings_info)
From 70c9a50286b014f8bda2124227740ef47a6575fa Mon Sep 17 00:00:00 2001
From: Rahul Raina
Date: Wed, 8 Jan 2020 14:37:34 +0800
Subject: [PATCH 151/331] Fixing spacing
---
src/onelogin/saml2/auth.py | 1 +
src/onelogin/saml2/response.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 72d37847..e432f769 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -605,6 +605,7 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
return True
idp_data = self.get_settings().get_idp_data()
+
exists_x509cert = self.get_settings().get_idp_cert() is not None
exists_multix509sign = 'x509certMulti' in idp_data and \
'signing' in idp_data['x509certMulti'] and \
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index a5004fea..1e7736a8 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -293,7 +293,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND
)
else:
- cert = self.__settings.get_idp_cert()
+ cert = self.__settings.get_idp_cert()
fingerprint = idp_data.get('certFingerprint', None)
if fingerprint:
fingerprint = OneLogin_Saml2_Utils.format_finger_print(fingerprint)
From 08970a9dd1882af3cd5a07720e639700c134d991 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 15 Jan 2020 17:22:40 +0100
Subject: [PATCH 152/331] Fix flake8
---
demo-tornado/views.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/demo-tornado/views.py b/demo-tornado/views.py
index 6fe72bcd..7d143087 100644
--- a/demo-tornado/views.py
+++ b/demo-tornado/views.py
@@ -171,6 +171,7 @@ def init_saml_auth(req):
auth = OneLogin_Saml2_Auth(req, custom_base_path=Settings.SAML_PATH)
return auth
+
if __name__ == "__main__":
app = Application()
http_server = tornado.httpserver.HTTPServer(app)
From 6f8ca9c1f628db3a818312e678d5c2c108efbe9c Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 15 Jan 2020 18:30:29 +0100
Subject: [PATCH 153/331] Fix tests
---
src/onelogin/saml2/settings.py | 2 +-
tests/data/customPath/certs/idp.crt | 16 ++++++++++++++++
tests/src/OneLogin/saml2_tests/settings_test.py | 4 ++--
3 files changed, 19 insertions(+), 3 deletions(-)
create mode 100644 tests/data/customPath/certs/idp.crt
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 1d8e9518..404024ab 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -559,7 +559,7 @@ def get_idp_cert(self):
:rtype: string
"""
cert = self.__idp.get('x509cert')
- cert_file_name = self.__paths['cert'] + 'idp.crt'
+ cert_file_name = self.get_cert_path() + 'idp.crt'
if not cert and exists(cert_file_name):
with open(cert_file_name) as f:
cert = f.read()
diff --git a/tests/data/customPath/certs/idp.crt b/tests/data/customPath/certs/idp.crt
new file mode 100644
index 00000000..483db3ce
--- /dev/null
+++ b/tests/data/customPath/certs/idp.crt
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMC
+Tk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYD
+VQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG
+9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4
+MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xi
+ZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2Zl
+aWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5v
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LO
+NoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHIS
+KOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d
+1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8
+BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7n
+bK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2Qar
+Q4/67OZfHd7R+POBXhophSMv1ZOo
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index 32b037fe..837af55f 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -228,14 +228,14 @@ def testGetIDPCert(self):
"""
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON('settings9.json'))
- cert = "-----BEGIN CERTIFICATE-----\nMIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMC\nTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYD\nVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG\n9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4\nMTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xi\nZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2Zl\naWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5v\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LO\nNoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHIS\nKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d\n1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8\nBUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7n\nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2Qar\nQ4/67OZfHd7R+POBXhophSMv1ZOo\n-----END CERTIFICATE-----\n"
+ cert = "-----BEGIN CERTIFICATE-----\nMIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMC\nTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYD\nVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG\n9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4\nMTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xi\nZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2Zl\naWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5v\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LO\nNoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHIS\nKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d\n1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8\nBUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7n\nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2Qar\nQ4/67OZfHd7R+POBXhophSMv1ZOo\n-----END CERTIFICATE-----"
self.assertEqual(cert, settings.get_idp_cert())
settings_data = self.loadSettingsJSON()
settings = OneLogin_Saml2_Settings(settings_data)
settings_data['idp']['x509cert'] = cert
- self.assertEqual(cert, settings.get_sp_cert())
+ self.assertEqual(cert, settings.get_idp_cert())
del settings_data['idp']['x509cert']
del settings_data['custom_base_path']
From 589425d1fbd2161ce2a566dfcb92835a221a3541 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 21 Feb 2020 10:47:09 +0100
Subject: [PATCH 154/331] Add sha256 instead sha1 algorithm for sign/digest as
recommended value on documentation and settings
---
README.md | 4 ++--
demo-django/saml/advanced_settings.json | 4 ++--
demo-flask/saml/advanced_settings.json | 4 ++--
demo-tornado/saml/advanced_settings.json | 4 ++--
demo_pyramid/demo_pyramid/saml/advanced_settings.json | 4 ++--
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index f0e16ff0..97f43add 100644
--- a/README.md
+++ b/README.md
@@ -431,14 +431,14 @@ In addition to the required settings data (idp, sp), extra settings can be defin
// 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
// 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
// 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
- "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
+ "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
// Algorithm that the toolkit will use on digest process. Options:
// 'http://www.w3.org/2000/09/xmldsig#sha1'
// 'http://www.w3.org/2001/04/xmlenc#sha256'
// 'http://www.w3.org/2001/04/xmldsig-more#sha384'
// 'http://www.w3.org/2001/04/xmlenc#sha512'
- 'digestAlgorithm': "http://www.w3.org/2000/09/xmldsig#sha1"
+ 'digestAlgorithm': "http://www.w3.org/2001/04/xmlenc#sha256"
},
// Contact information template, it is recommended to suply a
diff --git a/demo-django/saml/advanced_settings.json b/demo-django/saml/advanced_settings.json
index 3115e17e..1307b0ae 100644
--- a/demo-django/saml/advanced_settings.json
+++ b/demo-django/saml/advanced_settings.json
@@ -10,8 +10,8 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
- "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
- "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
+ "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
"contactPerson": {
"technical": {
diff --git a/demo-flask/saml/advanced_settings.json b/demo-flask/saml/advanced_settings.json
index 3115e17e..1307b0ae 100644
--- a/demo-flask/saml/advanced_settings.json
+++ b/demo-flask/saml/advanced_settings.json
@@ -10,8 +10,8 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
- "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
- "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
+ "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
"contactPerson": {
"technical": {
diff --git a/demo-tornado/saml/advanced_settings.json b/demo-tornado/saml/advanced_settings.json
index 3115e17e..1307b0ae 100644
--- a/demo-tornado/saml/advanced_settings.json
+++ b/demo-tornado/saml/advanced_settings.json
@@ -10,8 +10,8 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
- "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
- "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
+ "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
"contactPerson": {
"technical": {
diff --git a/demo_pyramid/demo_pyramid/saml/advanced_settings.json b/demo_pyramid/demo_pyramid/saml/advanced_settings.json
index 3115e17e..1307b0ae 100644
--- a/demo_pyramid/demo_pyramid/saml/advanced_settings.json
+++ b/demo_pyramid/demo_pyramid/saml/advanced_settings.json
@@ -10,8 +10,8 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
- "signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
- "digestAlgorithm": "http://www.w3.org/2000/09/xmldsig#sha1"
+ "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
"contactPerson": {
"technical": {
From 802c6121b6ae97cbf063cfe04d579753dff6b23a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 5 Jun 2020 17:32:18 +0000
Subject: [PATCH 155/331] Bump django from 1.11.26 to 1.11.29 in /demo-django
Bumps [django](https://github.com/django/django) from 1.11.26 to 1.11.29.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/1.11.26...1.11.29)
Signed-off-by: dependabot[bot]
---
demo-django/requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo-django/requirements.txt b/demo-django/requirements.txt
index a4a895b0..46ceaced 100644
--- a/demo-django/requirements.txt
+++ b/demo-django/requirements.txt
@@ -1,2 +1,2 @@
-Django==1.11.26
+Django==1.11.29
python3-saml
From b192e2a3585bdf24a461c76aa8fe6da2952340c1 Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Fri, 21 Aug 2020 16:22:32 +0300
Subject: [PATCH 156/331] Add
OneLogin_Saml2_Authn_Request._generate_request_id() override point
Refs https://github.com/onelogin/python3-saml/issues/208#issuecomment-678099281
---
src/onelogin/saml2/authn_request.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index 73547735..ef9ed603 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -47,8 +47,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
idp_data = self.__settings.get_idp_data()
security = self.__settings.get_security_data()
- uid = OneLogin_Saml2_Utils.generate_unique_id()
- self.__id = uid
+ self.__id = self._generate_request_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
destination = idp_data['singleSignOnService']['url']
@@ -113,7 +112,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
request = OneLogin_Saml2_Templates.AUTHN_REQUEST % \
{
- 'id': uid,
+ 'id': self.__id,
'provider_name': provider_name_str,
'force_authn_str': force_authn_str,
'is_passive_str': is_passive_str,
@@ -129,6 +128,14 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
self.__authn_request = request
+ def _generate_request_id(self):
+ """
+ Generate an unique request ID.
+
+ You can override this in a subclass.
+ """
+ return OneLogin_Saml2_Utils.generate_unique_id()
+
def get_request(self, deflate=True):
"""
Returns unsigned AuthnRequest.
From 53db80de9e4d853742565e500f640bfcb01ad295 Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Fri, 21 Aug 2020 16:27:15 +0300
Subject: [PATCH 157/331] Make request/response/metadata classes overridable by
subclassing
---
src/onelogin/saml2/auth.py | 17 +++++++++++------
src/onelogin/saml2/settings.py | 10 ++++++----
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index e432f769..cd7c1607 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -34,6 +34,11 @@ class OneLogin_Saml2_Auth(object):
SAML Response, a Logout Request or a Logout Response).
"""
+ authn_request_class = OneLogin_Saml2_Authn_Request
+ logout_request_class = OneLogin_Saml2_Logout_Request
+ logout_response_class = OneLogin_Saml2_Logout_Response
+ response_class = OneLogin_Saml2_Response
+
def __init__(self, request_data, old_settings=None, custom_base_path=None):
"""
Initializes the SP SAML instance.
@@ -102,7 +107,7 @@ def process_response(self, request_id=None):
if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data['post_data']:
# AuthnResponse -- HTTP_POST Binding
- response = OneLogin_Saml2_Response(self.__settings, self.__request_data['post_data']['SAMLResponse'])
+ response = self.response_class(self.__settings, self.__request_data['post_data']['SAMLResponse'])
self.__last_response = response.get_xml_document()
if response.is_valid(self.__request_data, request_id):
@@ -147,7 +152,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
get_data = 'get_data' in self.__request_data and self.__request_data['get_data']
if get_data and 'SAMLResponse' in get_data:
- logout_response = OneLogin_Saml2_Logout_Response(self.__settings, get_data['SAMLResponse'])
+ logout_response = self.logout_response_class(self.__settings, get_data['SAMLResponse'])
self.__last_response = logout_response.get_xml()
if not self.validate_response_signature(get_data):
self.__errors.append('invalid_logout_response_signature')
@@ -163,7 +168,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
elif get_data and 'SAMLRequest' in get_data:
- logout_request = OneLogin_Saml2_Logout_Request(self.__settings, get_data['SAMLRequest'])
+ logout_request = self.logout_request_class(self.__settings, get_data['SAMLRequest'])
self.__last_request = logout_request.get_xml()
if not self.validate_request_signature(get_data):
self.__errors.append("invalid_logout_request_signature")
@@ -177,7 +182,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
in_response_to = logout_request.id
self.__last_message_id = logout_request.id
- response_builder = OneLogin_Saml2_Logout_Response(self.__settings)
+ response_builder = self.logout_response_class(self.__settings)
response_builder.build(in_response_to)
self.__last_response = response_builder.get_xml()
logout_response = response_builder.get_response()
@@ -371,7 +376,7 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
:returns: Redirection URL
:rtype: string
"""
- authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
+ authn_request = self.authn_request_class(self.__settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
self.__last_request = authn_request.get_xml()
self.__last_request_id = authn_request.get_id()
@@ -425,7 +430,7 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name
if name_id_format is None and self.__nameid_format is not None:
name_id_format = self.__nameid_format
- logout_request = OneLogin_Saml2_Logout_Request(
+ logout_request = self.logout_request_class(
self.__settings,
name_id=name_id,
session_index=session_index,
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 404024ab..6df22432 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -66,6 +66,8 @@ class OneLogin_Saml2_Settings(object):
"""
+ metadata_class = OneLogin_Saml2_Metadata
+
def __init__(self, settings=None, custom_base_path=None, sp_validation_only=False):
"""
Initializes the settings:
@@ -616,7 +618,7 @@ def get_sp_metadata(self):
:returns: SP metadata (xml)
:rtype: string
"""
- metadata = OneLogin_Saml2_Metadata.builder(
+ metadata = self.metadata_class.builder(
self.__sp, self.__security['authnRequestsSigned'],
self.__security['wantAssertionsSigned'],
self.__security['metadataValidUntil'],
@@ -627,10 +629,10 @@ def get_sp_metadata(self):
add_encryption = self.__security['wantNameIdEncrypted'] or self.__security['wantAssertionsEncrypted']
cert_new = self.get_sp_cert_new()
- metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(metadata, cert_new, add_encryption)
+ metadata = self.metadata_class.add_x509_key_descriptors(metadata, cert_new, add_encryption)
cert = self.get_sp_cert()
- metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(metadata, cert, add_encryption)
+ metadata = self.metadata_class.add_x509_key_descriptors(metadata, cert, add_encryption)
# Sign metadata
if 'signMetadata' in self.__security and self.__security['signMetadata'] is not False:
@@ -684,7 +686,7 @@ def get_sp_metadata(self):
signature_algorithm = self.__security['signatureAlgorithm']
digest_algorithm = self.__security['digestAlgorithm']
- metadata = OneLogin_Saml2_Metadata.sign_metadata(metadata, key_metadata, cert_metadata, signature_algorithm, digest_algorithm)
+ metadata = self.metadata_class.sign_metadata(metadata, key_metadata, cert_metadata, signature_algorithm, digest_algorithm)
return metadata
From ca04d1d92864726c7b8594f2dd357e4b839dbeb1 Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Sat, 22 Aug 2020 13:34:54 +0300
Subject: [PATCH 158/331] Make some staticmethods classmethods instead
---
src/onelogin/saml2/idp_metadata_parser.py | 15 ++++++++-------
src/onelogin/saml2/logout_request.py | 14 +++++++-------
src/onelogin/saml2/metadata.py | 16 ++++++++--------
3 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 88411f45..1172bd06 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -25,8 +25,8 @@ class OneLogin_Saml2_IdPMetadataParser(object):
A class that contain methods related to obtaining and parsing metadata from IdP
"""
- @staticmethod
- def get_metadata(url, validate_cert=True):
+ @classmethod
+ def get_metadata(cls, url, validate_cert=True):
"""
Gets the metadata XML from the provided URL
:param url: Url where the XML of the Identity Provider Metadata is published.
@@ -63,8 +63,8 @@ def get_metadata(url, validate_cert=True):
return xml
- @staticmethod
- def parse_remote(url, validate_cert=True, entity_id=None, **kwargs):
+ @classmethod
+ def parse_remote(cls, url, validate_cert=True, entity_id=None, **kwargs):
"""
Gets the metadata XML from the provided URL and parse it, returning a dict with extracted data
:param url: Url where the XML of the Identity Provider Metadata is published.
@@ -80,11 +80,12 @@ def parse_remote(url, validate_cert=True, entity_id=None, **kwargs):
:returns: settings dict with extracted data
:rtype: dict
"""
- idp_metadata = OneLogin_Saml2_IdPMetadataParser.get_metadata(url, validate_cert)
- return OneLogin_Saml2_IdPMetadataParser.parse(idp_metadata, entity_id=entity_id, **kwargs)
+ idp_metadata = cls.get_metadata(url, validate_cert)
+ return cls.parse(idp_metadata, entity_id=entity_id, **kwargs)
- @staticmethod
+ @classmethod
def parse(
+ cls,
idp_metadata,
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index ef4d05e2..cd59e368 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -203,8 +203,8 @@ def get_nameid_data(request, key=None):
return name_id_data
- @staticmethod
- def get_nameid(request, key=None):
+ @classmethod
+ def get_nameid(cls, request, key=None):
"""
Gets the NameID of the Logout Request Message
:param request: Logout Request Message
@@ -214,11 +214,11 @@ def get_nameid(request, key=None):
:return: Name ID Value
:rtype: string
"""
- name_id = OneLogin_Saml2_Logout_Request.get_nameid_data(request, key)
+ name_id = cls.get_nameid_data(request, key)
return name_id['Value']
- @staticmethod
- def get_nameid_format(request, key=None):
+ @classmethod
+ def get_nameid_format(cls, request, key=None):
"""
Gets the NameID Format of the Logout Request Message
:param request: Logout Request Message
@@ -229,7 +229,7 @@ def get_nameid_format(request, key=None):
:rtype: string
"""
name_id_format = None
- name_id_data = OneLogin_Saml2_Logout_Request.get_nameid_data(request, key)
+ name_id_data = cls.get_nameid_data(request, key)
if name_id_data and 'Format' in name_id_data.keys():
name_id_format = name_id_data['Format']
return name_id_format
@@ -326,7 +326,7 @@ def is_valid(self, request_data, raise_exceptions=False):
)
# Check issuer
- issuer = OneLogin_Saml2_Logout_Request.get_issuer(root)
+ issuer = self.get_issuer(root)
if issuer is not None and issuer != idp_entity_id:
raise OneLogin_Saml2_ValidationError(
'Invalid issuer in the Logout Request (expected %(idpEntityId)s, got %(issuer)s)' %
diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py
index 0aab105c..9528b0e8 100644
--- a/src/onelogin/saml2/metadata.py
+++ b/src/onelogin/saml2/metadata.py
@@ -34,8 +34,8 @@ class OneLogin_Saml2_Metadata(object):
TIME_VALID = 172800 # 2 days
TIME_CACHED = 604800 # 1 week
- @staticmethod
- def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=None, contacts=None, organization=None):
+ @classmethod
+ def builder(cls, sp, authnsign=False, wsign=False, valid_until=None, cache_duration=None, contacts=None, organization=None):
"""
Builds the metadata of the SP
@@ -61,7 +61,7 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
:type organization: dict
"""
if valid_until is None:
- valid_until = int(time()) + OneLogin_Saml2_Metadata.TIME_VALID
+ valid_until = int(time()) + cls.TIME_VALID
if not isinstance(valid_until, basestring):
if isinstance(valid_until, datetime):
valid_until_time = valid_until.timetuple()
@@ -72,7 +72,7 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
valid_until_str = valid_until
if cache_duration is None:
- cache_duration = OneLogin_Saml2_Metadata.TIME_CACHED
+ cache_duration = cls.TIME_CACHED
if not isinstance(cache_duration, compat.str_type):
cache_duration_str = 'PT%sS' % cache_duration # Period of Time x Seconds
else:
@@ -228,8 +228,8 @@ def __add_x509_key_descriptors(root, cert, signing):
x509_certificate.text = OneLogin_Saml2_Utils.format_cert(cert, False)
key_descriptor.set('use', ('encryption', 'signing')[signing])
- @staticmethod
- def add_x509_key_descriptors(metadata, cert=None, add_encryption=True):
+ @classmethod
+ def add_x509_key_descriptors(cls, metadata, cert=None, add_encryption=True):
"""
Adds the x509 descriptors (sign/encryption) to the metadata
The same cert will be used for sign/encrypt
@@ -260,6 +260,6 @@ def add_x509_key_descriptors(metadata, cert=None, add_encryption=True):
raise Exception('Malformed metadata.')
if add_encryption:
- OneLogin_Saml2_Metadata.__add_x509_key_descriptors(sp_sso_descriptor, cert, False)
- OneLogin_Saml2_Metadata.__add_x509_key_descriptors(sp_sso_descriptor, cert, True)
+ cls.__add_x509_key_descriptors(sp_sso_descriptor, cert, False)
+ cls.__add_x509_key_descriptors(sp_sso_descriptor, cert, True)
return OneLogin_Saml2_XML.to_string(root)
From 468c747338f7fe2a7549382bc2fd294282658686 Mon Sep 17 00:00:00 2001
From: Florian Best
Date: Wed, 4 Nov 2020 15:38:06 +0100
Subject: [PATCH 159/331] tornado-demo: Fix autoreloading
---
demo-tornado/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo-tornado/views.py b/demo-tornado/views.py
index 7d143087..70cfea68 100644
--- a/demo-tornado/views.py
+++ b/demo-tornado/views.py
@@ -21,7 +21,7 @@ def __init__(self):
settings = {
"template_path": Settings.TEMPLATE_PATH,
"saml_path": Settings.SAML_PATH,
- "autorealod": True,
+ "autoreload": True,
"debug": True
}
tornado.web.Application.__init__(self, handlers, **settings)
From 51da7ec96414f4387c430f36e1584e1f1fabe936 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 24 Nov 2020 18:08:37 +0100
Subject: [PATCH 160/331] Fix #225 Fix get_session_expiration signature
---
src/onelogin/saml2/auth.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index e432f769..76a79272 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -279,7 +279,7 @@ def get_session_expiration(self):
"""
Returns the SessionNotOnOrAfter from the AuthnStatement.
:returns: The SessionNotOnOrAfter of the assertion
- :rtype: DateTime|None
+ :rtype: unix/posix timestamp|None
"""
return self.__session_expiration
From 082dfc75ceadaf317f9b8c66bdcec3a35f4105a2 Mon Sep 17 00:00:00 2001
From: BeritJanssen
Date: Thu, 3 Dec 2020 12:45:13 +0100
Subject: [PATCH 161/331] adding returnUrl parameter to singleLogoutService of
IdP
---
README.md | 3 +++
src/onelogin/saml2/auth.py | 12 +++++++++++-
src/onelogin/saml2/logout_response.py | 3 ++-
3 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 97f43add..c172f276 100644
--- a/README.md
+++ b/README.md
@@ -304,6 +304,9 @@ This is the ``settings.json`` file:
"singleLogoutService": {
// URL Location of the IdP where SLO Request will be sent.
"url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
+ // URL Location where the from the SP will returned (after IdP-initiated logout)
+ // OPTIONAL: only specify if different from url parameter
+ "returnUrl": "/slo_return/"
// SAML protocol binding to be used when returning the
// message. OneLogin Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 76a79272..37aada61 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -190,7 +190,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
if security['logoutResponseSigned']:
self.add_response_signature(parameters, security['signatureAlgorithm'])
- return self.redirect_to(self.get_slo_url(), parameters)
+ return self.redirect_to(self.get_slo_return_url(), parameters)
else:
self.__errors.append('invalid_binding')
raise OneLogin_Saml2_Error(
@@ -468,6 +468,16 @@ def get_slo_url(self):
if 'url' in idp_data['singleLogoutService']:
return idp_data['singleLogoutService']['url']
+ def get_return_slo_url(self):
+ """
+ Gets the SLO return URL for IdP-initiated logout.
+
+ :returns: an URL, the SLO return endpoint of the IdP
+ :rtype: string
+ """
+ slo_data = self.__settings.get_idp_data()['singeLogoutService']
+ return slo_data.get('returnUrl', self.get_slo_url())
+
def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
"""
Builds the Signature of the SAML Request.
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 6dd0a6d8..8400818d 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -162,12 +162,13 @@ def build(self, in_response_to):
uid = OneLogin_Saml2_Utils.generate_unique_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
+ destination = idp_data['singeLogoutService'].get('returnUrl', idp_data['singeLogoutService']['url'])
logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % \
{
'id': uid,
'issue_instant': issue_instant,
- 'destination': idp_data['singleLogoutService']['url'],
+ 'destination': destination,
'in_response_to': in_response_to,
'entity_id': sp_data['entityId'],
'status': "urn:oasis:names:tc:SAML:2.0:status:Success"
From b7402c0029d63e6a7fecb9955bc33d5b10b33fac Mon Sep 17 00:00:00 2001
From: BeritJanssen
Date: Thu, 3 Dec 2020 13:06:45 +0100
Subject: [PATCH 162/331] correct typo
---
README.md | 2 +-
src/onelogin/saml2/auth.py | 2 +-
src/onelogin/saml2/logout_response.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index c172f276..6d3602d4 100644
--- a/README.md
+++ b/README.md
@@ -306,7 +306,7 @@ This is the ``settings.json`` file:
"url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
// URL Location where the from the SP will returned (after IdP-initiated logout)
// OPTIONAL: only specify if different from url parameter
- "returnUrl": "/slo_return/"
+ "returnUrl": "https://app.onelogin.com/trust/saml2/http-redirect/slo_return/"
// SAML protocol binding to be used when returning the
// message. OneLogin Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 37aada61..cd8d1624 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -475,7 +475,7 @@ def get_return_slo_url(self):
:returns: an URL, the SLO return endpoint of the IdP
:rtype: string
"""
- slo_data = self.__settings.get_idp_data()['singeLogoutService']
+ slo_data = self.__settings.get_idp_data()['singleLogoutService']
return slo_data.get('returnUrl', self.get_slo_url())
def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 8400818d..bb5c3fbb 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -162,7 +162,7 @@ def build(self, in_response_to):
uid = OneLogin_Saml2_Utils.generate_unique_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
- destination = idp_data['singeLogoutService'].get('returnUrl', idp_data['singeLogoutService']['url'])
+ destination = idp_data['singleLogoutService'].get('returnUrl', idp_data['singleLogoutService']['url'])
logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % \
{
From da8a673b6c19892348d1e86e0a5b3fbd68360414 Mon Sep 17 00:00:00 2001
From: BeritJanssen
Date: Thu, 3 Dec 2020 13:13:12 +0100
Subject: [PATCH 163/331] correct function name
---
src/onelogin/saml2/auth.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index cd8d1624..ac79fb7b 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -468,7 +468,7 @@ def get_slo_url(self):
if 'url' in idp_data['singleLogoutService']:
return idp_data['singleLogoutService']['url']
- def get_return_slo_url(self):
+ def get_slo_return_url(self):
"""
Gets the SLO return URL for IdP-initiated logout.
From 90835cdbbe787c0fae0a378a66da1800952085c4 Mon Sep 17 00:00:00 2001
From: BeritJanssen
Date: Thu, 3 Dec 2020 14:17:16 +0100
Subject: [PATCH 164/331] test implemented
---
tests/settings/settings1.json | 3 ++-
tests/src/OneLogin/saml2_tests/auth_test.py | 15 +++++++++++++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/tests/settings/settings1.json b/tests/settings/settings1.json
index 69d7d25e..276a8964 100644
--- a/tests/settings/settings1.json
+++ b/tests/settings/settings1.json
@@ -18,7 +18,8 @@
"url": "http://idp.example.com/SSOService.php"
},
"singleLogoutService": {
- "url": "http://idp.example.com/SingleLogoutService.php"
+ "url": "http://idp.example.com/SingleLogoutService.php",
+ "returnUrl": "http://idp.example.com/SingleLogoutReturn.php"
},
"x509cert": "MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo"
},
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index c7c391c7..b78ecdfb 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -87,6 +87,21 @@ def testGetSLOurl(self):
slo_url = settings_info['idp']['singleLogoutService']['url']
self.assertEqual(auth.get_slo_url(), slo_url)
+ def testGetSLOReturnUrl(self):
+ """
+ Tests the get_slo_return_url method of the OneLogin_Saml2_Auth class
+ """
+ settings_info = self.loadSettingsJSON()
+ auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
+ slo_url = settings_info['idp']['singleLogoutService']['returnUrl']
+ self.assertEqual(auth.get_slo_return_url(), slo_url)
+ # test that the function falls back to the url setting if returnUrl is not set
+ settings_info['idp']['singleLogoutService'].pop('returnUrl')
+ auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
+ slo_url = settings_info['idp']['singleLogoutService']['url']
+ self.assertEqual(auth.get_slo_return_url(), slo_url)
+
+
def testGetSessionIndex(self):
"""
Tests the get_session_index method of the OneLogin_Saml2_Auth class
From b266fd5a2f475a2b0abcf63a935a06cea855bbb6 Mon Sep 17 00:00:00 2001
From: BeritJanssen
Date: Thu, 3 Dec 2020 14:44:01 +0100
Subject: [PATCH 165/331] fixed tests
---
tests/settings/settings1.json | 3 +--
tests/src/OneLogin/saml2_tests/auth_test.py | 1 +
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/settings/settings1.json b/tests/settings/settings1.json
index 276a8964..69d7d25e 100644
--- a/tests/settings/settings1.json
+++ b/tests/settings/settings1.json
@@ -18,8 +18,7 @@
"url": "http://idp.example.com/SSOService.php"
},
"singleLogoutService": {
- "url": "http://idp.example.com/SingleLogoutService.php",
- "returnUrl": "http://idp.example.com/SingleLogoutReturn.php"
+ "url": "http://idp.example.com/SingleLogoutService.php"
},
"x509cert": "MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo"
},
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index b78ecdfb..8ca6c814 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -92,6 +92,7 @@ def testGetSLOReturnUrl(self):
Tests the get_slo_return_url method of the OneLogin_Saml2_Auth class
"""
settings_info = self.loadSettingsJSON()
+ settings_info['idp']['singleLogoutService']['returnUrl'] = "http://idp.example.com/SingleLogoutReturn.php"
auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
slo_url = settings_info['idp']['singleLogoutService']['returnUrl']
self.assertEqual(auth.get_slo_return_url(), slo_url)
From ae5cae2524a04c47f78c3994c2a463e1a225f1e9 Mon Sep 17 00:00:00 2001
From: BeritJanssen
Date: Thu, 3 Dec 2020 14:54:26 +0100
Subject: [PATCH 166/331] rename returnUrl to responseUrl
---
README.md | 2 +-
src/onelogin/saml2/auth.py | 6 +++---
src/onelogin/saml2/logout_response.py | 2 +-
tests/src/OneLogin/saml2_tests/auth_test.py | 16 ++++++++--------
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index 6d3602d4..8e3ccbec 100644
--- a/README.md
+++ b/README.md
@@ -306,7 +306,7 @@ This is the ``settings.json`` file:
"url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
// URL Location where the from the SP will returned (after IdP-initiated logout)
// OPTIONAL: only specify if different from url parameter
- "returnUrl": "https://app.onelogin.com/trust/saml2/http-redirect/slo_return/"
+ "responseUrl": "https://app.onelogin.com/trust/saml2/http-redirect/slo_return/"
// SAML protocol binding to be used when returning the
// message. OneLogin Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index ac79fb7b..041f9445 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -190,7 +190,7 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
if security['logoutResponseSigned']:
self.add_response_signature(parameters, security['signatureAlgorithm'])
- return self.redirect_to(self.get_slo_return_url(), parameters)
+ return self.redirect_to(self.get_slo_response_url(), parameters)
else:
self.__errors.append('invalid_binding')
raise OneLogin_Saml2_Error(
@@ -468,7 +468,7 @@ def get_slo_url(self):
if 'url' in idp_data['singleLogoutService']:
return idp_data['singleLogoutService']['url']
- def get_slo_return_url(self):
+ def get_slo_response_url(self):
"""
Gets the SLO return URL for IdP-initiated logout.
@@ -476,7 +476,7 @@ def get_slo_return_url(self):
:rtype: string
"""
slo_data = self.__settings.get_idp_data()['singleLogoutService']
- return slo_data.get('returnUrl', self.get_slo_url())
+ return slo_data.get('responseUrl', self.get_slo_url())
def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
"""
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index bb5c3fbb..7ce7f97d 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -162,7 +162,7 @@ def build(self, in_response_to):
uid = OneLogin_Saml2_Utils.generate_unique_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
- destination = idp_data['singleLogoutService'].get('returnUrl', idp_data['singleLogoutService']['url'])
+ destination = idp_data['singleLogoutService'].get('responseUrl', idp_data['singleLogoutService']['url'])
logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % \
{
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 8ca6c814..dcb35f72 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -87,20 +87,20 @@ def testGetSLOurl(self):
slo_url = settings_info['idp']['singleLogoutService']['url']
self.assertEqual(auth.get_slo_url(), slo_url)
- def testGetSLOReturnUrl(self):
+ def testGetSLOresponseUrl(self):
"""
- Tests the get_slo_return_url method of the OneLogin_Saml2_Auth class
+ Tests the get_slo_response_url method of the OneLogin_Saml2_Auth class
"""
settings_info = self.loadSettingsJSON()
- settings_info['idp']['singleLogoutService']['returnUrl'] = "http://idp.example.com/SingleLogoutReturn.php"
+ settings_info['idp']['singleLogoutService']['responseUrl'] = "http://idp.example.com/SingleLogoutReturn.php"
auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
- slo_url = settings_info['idp']['singleLogoutService']['returnUrl']
- self.assertEqual(auth.get_slo_return_url(), slo_url)
- # test that the function falls back to the url setting if returnUrl is not set
- settings_info['idp']['singleLogoutService'].pop('returnUrl')
+ slo_url = settings_info['idp']['singleLogoutService']['responseUrl']
+ self.assertEqual(auth.get_slo_response_url(), slo_url)
+ # test that the function falls back to the url setting if responseUrl is not set
+ settings_info['idp']['singleLogoutService'].pop('responseUrl')
auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
slo_url = settings_info['idp']['singleLogoutService']['url']
- self.assertEqual(auth.get_slo_return_url(), slo_url)
+ self.assertEqual(auth.get_slo_response_url(), slo_url)
def testGetSessionIndex(self):
From d6385fb25f3978b13cb0a563b9294e6c303d70e3 Mon Sep 17 00:00:00 2001
From: "Volm, David"
Date: Thu, 3 Dec 2020 10:19:12 -0600
Subject: [PATCH 167/331] Optionally set request.scheme based on
X-Forwarded-Proto header in demo_pyramid
---
demo_pyramid/demo_pyramid/views.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/demo_pyramid/demo_pyramid/views.py b/demo_pyramid/demo_pyramid/views.py
index 86814a99..b393f14d 100644
--- a/demo_pyramid/demo_pyramid/views.py
+++ b/demo_pyramid/demo_pyramid/views.py
@@ -16,6 +16,10 @@ def init_saml_auth(req):
def prepare_pyramid_request(request):
# If server is behind proxys or balancers use the HTTP_X_FORWARDED fields
+
+ if 'X-Forwarded-Proto' in request.headers:
+ request.scheme = request.headers['X-Forwarded-Proto']
+
return {
'https': 'on' if request.scheme == 'https' else 'off',
'http_host': request.host,
From f11d48f5a8df7224a951fd3f67818d844a0411ae Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Tue, 8 Dec 2020 13:59:30 -0600
Subject: [PATCH 168/331] Resolved issue 226 with testing
---
src/onelogin/saml2/response.py | 12 +++-
.../src/OneLogin/saml2_tests/response_test.py | 60 ++++++++++++++++++-
2 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 1e7736a8..a40ab7e6 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -10,6 +10,7 @@
"""
from copy import deepcopy
+from urllib.parse import urlsplit, urlunsplit
from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.utils import OneLogin_Saml2_Utils, OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError, return_false_on_exception
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML
@@ -191,7 +192,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Checks destination
destination = self.document.get('Destination', None)
if destination:
- if not destination.startswith(current_url):
+ if not self.__standardize_url(destination).startswith(self.__standardize_url(current_url)):
# TODO: Review if following lines are required, since we can control the
# request_data
# current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
@@ -865,6 +866,15 @@ def __decrypt_assertion(self, xml):
decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug=debug, inplace=True)
xml.replace(encrypted_assertion_nodes[0], decrypted)
return xml
+
+ def __standardize_url(self, url):
+ try:
+ parsed = list(urlsplit(url))
+ parsed[1] = parsed[1].lower()
+ standardized_url = urlunsplit(parsed)
+ return standardized_url
+ except Exception:
+ return url
def get_error(self):
"""
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 7d9934a1..41d72fab 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -18,7 +18,6 @@
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils
-
class OneLogin_Saml2_Response_Test(unittest.TestCase):
data_path = join(dirname(dirname(dirname(dirname(__file__)))), 'data')
settings_path = join(dirname(dirname(dirname(dirname(__file__)))), 'settings')
@@ -50,6 +49,24 @@ def get_request_data(self):
'script_name': 'index.html'
}
+ def get_request_data_domain_capitalized(self):
+ return {
+ 'http_host': 'StuFF.Com',
+ 'script_name': 'endpoints/endpoints/acs.php'
+ }
+
+ def get_request_data_path_capitalized(self):
+ return {
+ 'http_host': 'stuff.com',
+ 'script_name': 'Endpoints/endPoints/acs.php'
+ }
+
+ def get_request_data_both_capitalized(self):
+ return {
+ 'http_host': 'StuFF.Com',
+ 'script_name': 'Endpoints/endPoints/aCs.php'
+ }
+
def testConstruct(self):
"""
Tests the OneLogin_Saml2_Response Constructor.
@@ -977,7 +994,7 @@ def testIsInValidDuplicatedAttrs(self):
response = OneLogin_Saml2_Response(settings, xml)
with self.assertRaisesRegex(Exception, 'Found an Attribute element with duplicated Name'):
response.get_attributes()
-
+
def testIsInValidDestination(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response class
@@ -1014,6 +1031,45 @@ def testIsInValidDestination(self):
self.assertFalse(response_5.is_valid(self.get_request_data()))
self.assertIn('A valid SubjectConfirmation was not found on this Response', response_5.get_error())
+ settings.set_strict(True)
+ response_2 = OneLogin_Saml2_Response(settings, message)
+ self.assertFalse(response_2.is_valid(self.get_request_data()))
+ self.assertIn('The response was received at', response_2.get_error())
+
+ def testIsInValidCapitalizationOfDestinationElements(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_Response class
+ Case Invalid Response due to differences in capitalization of path
+ """
+
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+ message = self.file_contents(join(self.data_path, 'responses', 'unsigned_response.xml.base64'))
+
+ #Test path capitalized
+ settings.set_strict(True)
+ response = OneLogin_Saml2_Response(settings, message)
+ self.assertFalse(response.is_valid(self.get_request_data_path_capitalized()))
+ self.assertIn('The response was received at', response.get_error())
+
+ #Test both domain and path capitalized
+ response_2 = OneLogin_Saml2_Response(settings, message)
+ self.assertFalse(response_2.is_valid(self.get_request_data_both_capitalized()))
+ self.assertIn('The response was received at', response_2.get_error())
+
+ def testIsValidCapitalizationOfDestinationHost(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_Response class
+ Case Valid Response, even if host is differently capitalized (per RFC)
+ """
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+ message = self.file_contents(join(self.data_path, 'responses', 'unsigned_response.xml.base64'))
+
+ #Test path capitalized
+ settings.set_strict(True)
+ response = OneLogin_Saml2_Response(settings, message)
+ self.assertFalse(response.is_valid(self.get_request_data_domain_capitalized()))
+ self.assertNotIn('The response was received at', response.get_error())
+
def testIsInValidAudience(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response class
From 1cb34cdb0a21f9ce6a1e54c3604188b09a677f1c Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Tue, 8 Dec 2020 14:01:48 -0600
Subject: [PATCH 169/331] Fixed comment
---
tests/src/OneLogin/saml2_tests/response_test.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 41d72fab..e3e6b390 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1036,7 +1036,7 @@ def testIsInValidDestination(self):
self.assertFalse(response_2.is_valid(self.get_request_data()))
self.assertIn('The response was received at', response_2.get_error())
- def testIsInValidCapitalizationOfDestinationElements(self):
+ def testIsInValidDestinationCapitalizationOfElements(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response class
Case Invalid Response due to differences in capitalization of path
@@ -1056,7 +1056,7 @@ def testIsInValidCapitalizationOfDestinationElements(self):
self.assertFalse(response_2.is_valid(self.get_request_data_both_capitalized()))
self.assertIn('The response was received at', response_2.get_error())
- def testIsValidCapitalizationOfDestinationHost(self):
+ def testIsValidDestinationCapitalizationOfHost(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response class
Case Valid Response, even if host is differently capitalized (per RFC)
@@ -1064,11 +1064,13 @@ def testIsValidCapitalizationOfDestinationHost(self):
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
message = self.file_contents(join(self.data_path, 'responses', 'unsigned_response.xml.base64'))
- #Test path capitalized
+ #Test domain capitalized
settings.set_strict(True)
response = OneLogin_Saml2_Response(settings, message)
self.assertFalse(response.is_valid(self.get_request_data_domain_capitalized()))
self.assertNotIn('The response was received at', response.get_error())
+ #Assert we got past the destination check, which appears later
+ self.assertIn('A valid SubjectConfirmation was not found', response.get_error())
def testIsInValidAudience(self):
"""
From 534974ac6c45a71f9a8dc67c547d4ef34ff5869e Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Tue, 8 Dec 2020 14:56:28 -0600
Subject: [PATCH 170/331] suggested changes
---
src/onelogin/saml2/response.py | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index a40ab7e6..248d2f77 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -192,7 +192,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Checks destination
destination = self.document.get('Destination', None)
if destination:
- if not self.__standardize_url(destination).startswith(self.__standardize_url(current_url)):
+ if not self.__normalize_url(destination).startswith(self.__normalize_url(current_url)):
# TODO: Review if following lines are required, since we can control the
# request_data
# current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
@@ -867,12 +867,24 @@ def __decrypt_assertion(self, xml):
xml.replace(encrypted_assertion_nodes[0], decrypted)
return xml
- def __standardize_url(self, url):
+ def __normalize_url(self, url):
+ """
+ Returns normalized URL for comparison.
+ This method converts the hostname to lowercase, as it should be case-insensitive (per RFC 4343)
+ If standardization fails, the original URL is returned
+ Python documentation indicates that URL split also normalizes query strings if empty query fields are present
+
+ :param url: URL
+ :type url: String
+
+ :returns: A normalized URL, or the given URL string if parsing fails
+ :rtype: list
+ """
try:
parsed = list(urlsplit(url))
parsed[1] = parsed[1].lower()
- standardized_url = urlunsplit(parsed)
- return standardized_url
+ normalized_url = urlunsplit(parsed)
+ return normalized_url
except Exception:
return url
From 021ee793075b378809be6f4c9270106857c38b78 Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Tue, 8 Dec 2020 15:10:58 -0600
Subject: [PATCH 171/331] Clarification in comment
---
src/onelogin/saml2/response.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 248d2f77..fc233272 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -870,7 +870,7 @@ def __decrypt_assertion(self, xml):
def __normalize_url(self, url):
"""
Returns normalized URL for comparison.
- This method converts the hostname to lowercase, as it should be case-insensitive (per RFC 4343)
+ This method converts the netloc to lowercase, as it should be case-insensitive (per RFC 4343, RFC 7617)
If standardization fails, the original URL is returned
Python documentation indicates that URL split also normalizes query strings if empty query fields are present
@@ -882,6 +882,8 @@ def __normalize_url(self, url):
"""
try:
parsed = list(urlsplit(url))
+ #scheme and hostname converted to lowercase
+ parsed[0] = parsed[0].lower()
parsed[1] = parsed[1].lower()
normalized_url = urlunsplit(parsed)
return normalized_url
From 9c40592324ca45f6549349e2f767e2da24b66bc7 Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Tue, 8 Dec 2020 15:22:20 -0600
Subject: [PATCH 172/331] Changes suggested
---
src/onelogin/saml2/response.py | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index fc233272..2b4fdc25 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -881,11 +881,8 @@ def __normalize_url(self, url):
:rtype: list
"""
try:
- parsed = list(urlsplit(url))
- #scheme and hostname converted to lowercase
- parsed[0] = parsed[0].lower()
- parsed[1] = parsed[1].lower()
- normalized_url = urlunsplit(parsed)
+ scheme, netloc, *rest = urlsplit(url)
+ normalized_url = urlunsplit((scheme.lower(), netloc.lower(), *rest))
return normalized_url
except Exception:
return url
From 707fd2a4afa73fa7d2cd454e17bba99003831b77 Mon Sep 17 00:00:00 2001
From: "Volm, David"
Date: Fri, 11 Dec 2020 17:31:47 -0600
Subject: [PATCH 173/331] Optionally set request.server_port based on
X-Forwarded-Port header in demo_pyramid
---
demo_pyramid/demo_pyramid/views.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/demo_pyramid/demo_pyramid/views.py b/demo_pyramid/demo_pyramid/views.py
index b393f14d..a213e367 100644
--- a/demo_pyramid/demo_pyramid/views.py
+++ b/demo_pyramid/demo_pyramid/views.py
@@ -19,6 +19,8 @@ def prepare_pyramid_request(request):
if 'X-Forwarded-Proto' in request.headers:
request.scheme = request.headers['X-Forwarded-Proto']
+ if 'X-Forwarded-Port' in request.headers:
+ request.server_port = int(request.headers['X-Forwarded-Port'])
return {
'https': 'on' if request.scheme == 'https' else 'off',
From 92fae88f373ccc387f423689e113eff9391d51bd Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Mon, 4 Jan 2021 16:32:14 -0600
Subject: [PATCH 174/331] Suggested edits made
---
src/onelogin/saml2/logout_request.py | 2 +-
src/onelogin/saml2/logout_response.py | 2 +-
src/onelogin/saml2/response.py | 22 +---------------------
src/onelogin/saml2/utils.py | 21 +++++++++++++++++++++
4 files changed, 24 insertions(+), 23 deletions(-)
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index ef4d05e2..7d83e835 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -314,7 +314,7 @@ def is_valid(self, request_data, raise_exceptions=False):
if root.get('Destination', None):
destination = root.get('Destination')
if destination != '':
- if current_url not in destination:
+ if OneLogin_Saml2_Utils.normalize_url(current_url) not in OneLogin_Saml2_Utils.normalize_url(destination):
raise OneLogin_Saml2_ValidationError(
'The LogoutRequest was received at '
'%(currentURL)s instead of %(destination)s' %
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 6dd0a6d8..bc4a3bfb 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -118,7 +118,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Check destination
destination = self.document.get('Destination', None)
- if destination and current_url not in destination:
+ if destination and OneLogin_Saml2_Utils.normalize_url(current_url) not in OneLogin_Saml2_Utils.normalize_url(destination):
raise OneLogin_Saml2_ValidationError(
'The LogoutResponse was received at %s instead of %s' % (current_url, destination),
OneLogin_Saml2_ValidationError.WRONG_DESTINATION
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 2b4fdc25..37402459 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -192,7 +192,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Checks destination
destination = self.document.get('Destination', None)
if destination:
- if not self.__normalize_url(destination).startswith(self.__normalize_url(current_url)):
+ if not OneLogin_Saml2_Utils.normalize_url(destination).startswith(OneLogin_Saml2_Utils.normalize_url(current_url)):
# TODO: Review if following lines are required, since we can control the
# request_data
# current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
@@ -867,26 +867,6 @@ def __decrypt_assertion(self, xml):
xml.replace(encrypted_assertion_nodes[0], decrypted)
return xml
- def __normalize_url(self, url):
- """
- Returns normalized URL for comparison.
- This method converts the netloc to lowercase, as it should be case-insensitive (per RFC 4343, RFC 7617)
- If standardization fails, the original URL is returned
- Python documentation indicates that URL split also normalizes query strings if empty query fields are present
-
- :param url: URL
- :type url: String
-
- :returns: A normalized URL, or the given URL string if parsing fails
- :rtype: list
- """
- try:
- scheme, netloc, *rest = urlsplit(url)
- normalized_url = urlunsplit((scheme.lower(), netloc.lower(), *rest))
- return normalized_url
- except Exception:
- return url
-
def get_error(self):
"""
After executing a validation process, if it fails this method returns the cause
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index b0856ebc..7a0d2f13 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -1062,3 +1062,24 @@ def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_
if debug:
print(e)
return False
+
+ @staticmethod
+ def normalize_url(self, url):
+ """
+ Returns normalized URL for comparison.
+ This method converts the netloc to lowercase, as it should be case-insensitive (per RFC 4343, RFC 7617)
+ If standardization fails, the original URL is returned
+ Python documentation indicates that URL split also normalizes query strings if empty query fields are present
+
+ :param url: URL
+ :type url: String
+
+ :returns: A normalized URL, or the given URL string if parsing fails
+ :rtype: String
+ """
+ try:
+ scheme, netloc, *rest = urlsplit(url)
+ normalized_url = urlunsplit((scheme.lower(), netloc.lower(), *rest))
+ return normalized_url
+ except Exception:
+ return url
\ No newline at end of file
From 8d9ea1a232ef24c6ce49fe98d3dfae5ef395b4d2 Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Mon, 4 Jan 2021 17:21:33 -0600
Subject: [PATCH 175/331] Fixed tests
---
src/onelogin/saml2/logout_response.py | 2 +-
src/onelogin/saml2/response.py | 2 +-
src/onelogin/saml2/utils.py | 6 +++---
tests/src/OneLogin/saml2_tests/response_test.py | 2 +-
tests/src/OneLogin/saml2_tests/utils_test.py | 12 ++++++++++++
5 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index bc4a3bfb..86de96c6 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -118,7 +118,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Check destination
destination = self.document.get('Destination', None)
- if destination and OneLogin_Saml2_Utils.normalize_url(current_url) not in OneLogin_Saml2_Utils.normalize_url(destination):
+ if destination and OneLogin_Saml2_Utils.normalize_url(url=current_url) not in OneLogin_Saml2_Utils.normalize_url(url=destination):
raise OneLogin_Saml2_ValidationError(
'The LogoutResponse was received at %s instead of %s' % (current_url, destination),
OneLogin_Saml2_ValidationError.WRONG_DESTINATION
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 37402459..54d140c3 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -192,7 +192,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Checks destination
destination = self.document.get('Destination', None)
if destination:
- if not OneLogin_Saml2_Utils.normalize_url(destination).startswith(OneLogin_Saml2_Utils.normalize_url(current_url)):
+ if not OneLogin_Saml2_Utils.normalize_url(url=destination).startswith(OneLogin_Saml2_Utils.normalize_url(url=current_url)):
# TODO: Review if following lines are required, since we can control the
# request_data
# current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 7a0d2f13..3c22e406 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -20,7 +20,7 @@
from functools import wraps
from uuid import uuid4
from xml.dom.minidom import Element
-
+from urllib.parse import urlsplit, urlunsplit
import zlib
import xmlsec
@@ -1063,8 +1063,8 @@ def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_
print(e)
return False
- @staticmethod
- def normalize_url(self, url):
+ @staticmethod
+ def normalize_url(url):
"""
Returns normalized URL for comparison.
This method converts the netloc to lowercase, as it should be case-insensitive (per RFC 4343, RFC 7617)
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index e3e6b390..efe6fd0f 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1063,12 +1063,12 @@ def testIsValidDestinationCapitalizationOfHost(self):
"""
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
message = self.file_contents(join(self.data_path, 'responses', 'unsigned_response.xml.base64'))
-
#Test domain capitalized
settings.set_strict(True)
response = OneLogin_Saml2_Response(settings, message)
self.assertFalse(response.is_valid(self.get_request_data_domain_capitalized()))
self.assertNotIn('The response was received at', response.get_error())
+
#Assert we got past the destination check, which appears later
self.assertIn('A valid SubjectConfirmation was not found', response.get_error())
diff --git a/tests/src/OneLogin/saml2_tests/utils_test.py b/tests/src/OneLogin/saml2_tests/utils_test.py
index 597c4852..c332a5e8 100644
--- a/tests/src/OneLogin/saml2_tests/utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/utils_test.py
@@ -917,3 +917,15 @@ def testValidateSign(self):
# Signature Wrapping attack
wrapping_attack1 = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'signature_wrapping_attack.xml.base64')))
self.assertFalse(OneLogin_Saml2_Utils.validate_sign(wrapping_attack1, cert))
+
+ def testNormalizeUrl(self):
+ base_url = 'https://blah.com/path'
+ capital_scheme = 'hTTps://blah.com/path'
+ capital_domain = 'https://blAH.Com/path'
+ capital_path = 'https://blah.com/PAth'
+ capital_all = 'HTTPS://BLAH.COM/PATH'
+
+ self.assertIn(base_url, OneLogin_Saml2_Utils.normalize_url(capital_scheme))
+ self.assertIn(base_url, OneLogin_Saml2_Utils.normalize_url(capital_domain))
+ self.assertNotIn(base_url, OneLogin_Saml2_Utils.normalize_url(capital_path))
+ self.assertNotIn(base_url, OneLogin_Saml2_Utils.normalize_url(capital_all))
From a8d03a512825f098d347d9a44b2006391c84508c Mon Sep 17 00:00:00 2001
From: Tessa Bloomer
Date: Mon, 4 Jan 2021 17:32:22 -0600
Subject: [PATCH 176/331] more testing
---
.../saml2_tests/logout_request_test.py | 64 +++++++++++++++++++
.../saml2_tests/logout_response_test.py | 58 +++++++++++++++++
2 files changed, 122 insertions(+)
diff --git a/tests/src/OneLogin/saml2_tests/logout_request_test.py b/tests/src/OneLogin/saml2_tests/logout_request_test.py
index d4c8ee6c..e1510c4d 100644
--- a/tests/src/OneLogin/saml2_tests/logout_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_request_test.py
@@ -413,6 +413,70 @@ def testIsValid(self):
logout_request5 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
self.assertTrue(logout_request5.is_valid(request_data))
+ def testIsValidWithCapitalization(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest
+ """
+ request_data = {
+ 'http_host': 'exaMPLe.com',
+ 'script_name': 'index.html'
+ }
+ request = self.file_contents(join(self.data_path, 'logout_requests', 'logout_request.xml'))
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+
+ logout_request = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertTrue(logout_request.is_valid(request_data))
+
+ settings.set_strict(True)
+ logout_request2 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertFalse(logout_request2.is_valid(request_data))
+
+ settings.set_strict(False)
+ dom = parseString(request)
+ logout_request3 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml()))
+ self.assertTrue(logout_request3.is_valid(request_data))
+
+ settings.set_strict(True)
+ logout_request4 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml()))
+ self.assertFalse(logout_request4.is_valid(request_data))
+
+ current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
+ request = request.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url.lower())
+ logout_request5 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertTrue(logout_request5.is_valid(request_data))
+
+ def testIsInValidWithCapitalization(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest
+ """
+ request_data = {
+ 'http_host': 'example.com',
+ 'script_name': 'INdex.html'
+ }
+ request = self.file_contents(join(self.data_path, 'logout_requests', 'logout_request.xml'))
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+
+ logout_request = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertTrue(logout_request.is_valid(request_data))
+
+ settings.set_strict(True)
+ logout_request2 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertFalse(logout_request2.is_valid(request_data))
+
+ settings.set_strict(False)
+ dom = parseString(request)
+ logout_request3 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml()))
+ self.assertTrue(logout_request3.is_valid(request_data))
+
+ settings.set_strict(True)
+ logout_request4 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml()))
+ self.assertFalse(logout_request4.is_valid(request_data))
+
+ current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
+ request = request.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url.lower())
+ logout_request5 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request))
+ self.assertFalse(logout_request5.is_valid(request_data))
+
def testIsValidWithXMLEncoding(self):
"""
Tests the is_valid method of the OneLogin_Saml2_LogoutRequest
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index 054e9762..3ab17653 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -276,6 +276,64 @@ def testIsValid(self):
response_3 = OneLogin_Saml2_Logout_Response(settings, message_3)
self.assertTrue(response_3.is_valid(request_data))
+ def testIsValidWithCapitalization(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_LogoutResponse
+ """
+ request_data = {
+ 'http_host': 'exaMPLe.com',
+ 'script_name': 'index.html',
+ 'get_data': {}
+ }
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+ message = self.file_contents(join(self.data_path, 'logout_responses', 'logout_response_deflated.xml.base64'))
+
+ response = OneLogin_Saml2_Logout_Response(settings, message)
+ self.assertTrue(response.is_valid(request_data))
+
+ settings.set_strict(True)
+ response_2 = OneLogin_Saml2_Logout_Response(settings, message)
+ with self.assertRaisesRegex(Exception, 'The LogoutResponse was received at'):
+ response_2.is_valid(request_data, raise_exceptions=True)
+
+ plain_message = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(message))
+
+ current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data).lower()
+ plain_message = plain_message.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url)
+ message_3 = OneLogin_Saml2_Utils.deflate_and_base64_encode(plain_message)
+
+ response_3 = OneLogin_Saml2_Logout_Response(settings, message_3)
+ self.assertTrue(response_3.is_valid(request_data))
+
+ def testIsInValidWithCapitalization(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_LogoutResponse
+ """
+ request_data = {
+ 'http_host': 'example.com',
+ 'script_name': 'INdex.html',
+ 'get_data': {}
+ }
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+ message = self.file_contents(join(self.data_path, 'logout_responses', 'logout_response_deflated.xml.base64'))
+
+ response = OneLogin_Saml2_Logout_Response(settings, message)
+ self.assertTrue(response.is_valid(request_data))
+
+ settings.set_strict(True)
+ response_2 = OneLogin_Saml2_Logout_Response(settings, message)
+ with self.assertRaisesRegex(Exception, 'The LogoutResponse was received at'):
+ response_2.is_valid(request_data, raise_exceptions=True)
+
+ plain_message = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(message))
+ current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data).lower()
+ plain_message = plain_message.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url)
+ message_3 = OneLogin_Saml2_Utils.deflate_and_base64_encode(plain_message)
+
+ response_3 = OneLogin_Saml2_Logout_Response(settings, message_3)
+ self.assertFalse(response_3.is_valid(request_data))
+
+
def testIsValidWithXMLEncoding(self):
"""
Tests the is_valid method of the OneLogin_Saml2_LogoutResponse
From 9fdb11d1cd0190cfc783306cf9aed5133121a1ce Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 8 Jan 2021 19:48:37 +0100
Subject: [PATCH 177/331] Adding get_idp_sso_url, get_idp_slo_url and
get_idp_slo_response_url methods to the Settings class and use it in the
toolkit
---
src/onelogin/saml2/auth.py | 10 ++----
src/onelogin/saml2/logout_request.py | 2 +-
src/onelogin/saml2/logout_response.py | 3 +-
src/onelogin/saml2/settings.py | 31 ++++++++++++++++
.../src/OneLogin/saml2_tests/settings_test.py | 35 +++++++++++++++++++
5 files changed, 71 insertions(+), 10 deletions(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 041f9445..67da5183 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -454,8 +454,7 @@ def get_sso_url(self):
:returns: An URL, the SSO endpoint of the IdP
:rtype: string
"""
- idp_data = self.__settings.get_idp_data()
- return idp_data['singleSignOnService']['url']
+ return self.__settings.get_idp_sso_url()
def get_slo_url(self):
"""
@@ -464,9 +463,7 @@ def get_slo_url(self):
:returns: An URL, the SLO endpoint of the IdP
:rtype: string
"""
- idp_data = self.__settings.get_idp_data()
- if 'url' in idp_data['singleLogoutService']:
- return idp_data['singleLogoutService']['url']
+ return self.__settings.get_idp_slo_url()
def get_slo_response_url(self):
"""
@@ -475,8 +472,7 @@ def get_slo_response_url(self):
:returns: an URL, the SLO return endpoint of the IdP
:rtype: string
"""
- slo_data = self.__settings.get_idp_data()['singleLogoutService']
- return slo_data.get('responseUrl', self.get_slo_url())
+ return self.__settings.get_idp_slo_response_url()
def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
"""
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index ef4d05e2..78b01de6 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -110,7 +110,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
{
'id': uid,
'issue_instant': issue_instant,
- 'single_logout_url': idp_data['singleLogoutService']['url'],
+ 'single_logout_url': self.__settings.get_idp_slo_response_url(),
'entity_id': sp_data['entityId'],
'name_id': name_id_obj,
'session_index': session_index_str,
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 7ce7f97d..de827f6a 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -162,13 +162,12 @@ def build(self, in_response_to):
uid = OneLogin_Saml2_Utils.generate_unique_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
- destination = idp_data['singleLogoutService'].get('responseUrl', idp_data['singleLogoutService']['url'])
logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % \
{
'id': uid,
'issue_instant': issue_instant,
- 'destination': destination,
+ 'destination': self.__settings.get_idp_slo_response_url(),
'in_response_to': in_response_to,
'entity_id': sp_data['entityId'],
'status': "urn:oasis:names:tc:SAML:2.0:status:Success"
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 404024ab..5df25991 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -506,6 +506,37 @@ def check_sp_certs(self):
cert = self.get_sp_cert()
return key is not None and cert is not None
+ def get_idp_sso_url(self):
+ """
+ Gets the IdP SSO URL.
+
+ :returns: An URL, the SSO endpoint of the IdP
+ :rtype: string
+ """
+ idp_data = self.get_idp_data()
+ return idp_data['singleSignOnService']['url']
+
+ def get_idp_slo_url(self):
+ """
+ Gets the IdP SLO URL.
+
+ :returns: An URL, the SLO endpoint of the IdP
+ :rtype: string
+ """
+ idp_data = self.get_idp_data()
+ if 'url' in idp_data['singleLogoutService']:
+ return idp_data['singleLogoutService']['url']
+
+ def get_idp_slo_response_url(self):
+ """
+ Gets the IdP SLO return URL for IdP-initiated logout.
+
+ :returns: an URL, the SLO return endpoint of the IdP
+ :rtype: string
+ """
+ slo_data = self.get_idp_data()['singleLogoutService']
+ return slo_data.get('responseUrl', self.get_idp_slo_url())
+
def get_sp_key(self):
"""
Returns the x509 private key of the SP.
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index 837af55f..fb7ba7fe 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -165,6 +165,41 @@ def testGetSchemasPath(self):
base = settings.get_base_path()
self.assertEqual(join(base, 'lib', 'schemas') + sep, settings.get_schemas_path())
+ def testGetIdPSSOurl(self):
+ """
+ Tests the get_idp_sso_url method of the OneLogin_Saml2_Settings class
+ """
+ settings_info = self.loadSettingsJSON()
+ settings = OneLogin_Saml2_Settings(settings_info)
+
+ sso_url = settings_info['idp']['singleSignOnService']['url']
+ self.assertEqual(settings.get_idp_sso_url(), sso_url)
+
+ def testGetIdPSLOurl(self):
+ """
+ Tests the get_idp_slo_url method of the OneLogin_Saml2_Settings class
+ """
+ settings_info = self.loadSettingsJSON()
+ settings = OneLogin_Saml2_Settings(settings_info)
+
+ slo_url = settings_info['idp']['singleLogoutService']['url']
+ self.assertEqual(settings.get_idp_slo_url(), slo_url)
+
+ def testGetIdPSLOresponseUrl(self):
+ """
+ Tests the get_idp_slo_response_url method of the OneLogin_Saml2_Settings class
+ """
+ settings_info = self.loadSettingsJSON()
+ settings_info['idp']['singleLogoutService']['responseUrl'] = "http://idp.example.com/SingleLogoutReturn.php"
+ settings = OneLogin_Saml2_Settings(settings_info)
+ slo_url = settings_info['idp']['singleLogoutService']['responseUrl']
+ self.assertEqual(settings.get_idp_slo_response_url(), slo_url)
+ # test that the function falls back to the url setting if responseUrl is not set
+ settings_info['idp']['singleLogoutService'].pop('responseUrl')
+ settings = OneLogin_Saml2_Settings(settings_info)
+ slo_url = settings_info['idp']['singleLogoutService']['url']
+ self.assertEqual(settings.get_idp_slo_response_url(), slo_url)
+
def testGetSPCert(self):
"""
Tests the get_sp_cert method of the OneLogin_Saml2_Settings
From fa1ddab1573eb710583046633bce47ace33006aa Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sat, 9 Jan 2021 00:07:24 +0100
Subject: [PATCH 178/331] Update Readme.md
---
README.md | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 8e3ccbec..e98b60c4 100644
--- a/README.md
+++ b/README.md
@@ -241,11 +241,13 @@ This is the ``settings.json`` file:
// HTTP-POST binding only.
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
- // Specifies info about where and how the message MUST be
- // returned to the requester, in this case our SP.
+ // Specifies info about where and how the message MUST be sent.
"singleLogoutService": {
- // URL Location where the from the IdP will be returned
+ // URL Location where the from the IdP will be sent (IdP-initiated logout)
"url": "https:///?sls",
+ // URL Location where the from the IdP will sent (SP-initiated logout, reply)
+ // OPTIONAL: only specify if different from url parameter
+ //"responseUrl": "https:///?sls",
// SAML protocol binding to be used when returning the
// message. OneLogin Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
@@ -302,11 +304,11 @@ This is the ``settings.json`` file:
},
// SLO endpoint info of the IdP.
"singleLogoutService": {
- // URL Location of the IdP where SLO Request will be sent.
+ // URL Location where the from the IdP will be sent (IdP-initiated logout)
"url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
- // URL Location where the from the SP will returned (after IdP-initiated logout)
+ // URL Location where the from the IdP will sent (SP-initiated logout, reply)
// OPTIONAL: only specify if different from url parameter
- "responseUrl": "https://app.onelogin.com/trust/saml2/http-redirect/slo_return/"
+ "responseUrl": "https://app.onelogin.com/trust/saml2/http-redirect/slo_return/",
// SAML protocol binding to be used when returning the
// message. OneLogin Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
From ee8b5d7c2426506be9873a82772c231743bc052d Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sat, 9 Jan 2021 00:30:17 +0100
Subject: [PATCH 179/331] Refactor
---
src/onelogin/saml2/settings.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 5df25991..e51a7124 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -534,8 +534,9 @@ def get_idp_slo_response_url(self):
:returns: an URL, the SLO return endpoint of the IdP
:rtype: string
"""
- slo_data = self.get_idp_data()['singleLogoutService']
- return slo_data.get('responseUrl', self.get_idp_slo_url())
+ idp_data = self.get_idp_data()
+ if 'url' in idp_data['singleLogoutService']:
+ return idp_data['singleLogoutService'].get('responseUrl', self.get_idp_slo_url())
def get_sp_key(self):
"""
From 3aa3c575ffc4e55a3e06a1535dbe3094e7a31ae0 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sat, 9 Jan 2021 00:56:03 +0100
Subject: [PATCH 180/331] See #223 Fix doc signature of get_attribute method
---
src/onelogin/saml2/auth.py | 4 ++--
tests/src/OneLogin/saml2_tests/auth_test.py | 1 -
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 67da5183..c173b1b5 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -315,8 +315,8 @@ def get_attribute(self, name):
:param name: Name of the attribute
:type name: string
- :returns: Attribute value if exists or None
- :rtype: string
+ :returns: Attribute value(s) if exists or None
+ :rtype: list
"""
assert isinstance(name, compat.str_type)
return self.__attributes.get(name)
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index dcb35f72..81d009e9 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -102,7 +102,6 @@ def testGetSLOresponseUrl(self):
slo_url = settings_info['idp']['singleLogoutService']['url']
self.assertEqual(auth.get_slo_response_url(), slo_url)
-
def testGetSessionIndex(self):
"""
Tests the get_session_index method of the OneLogin_Saml2_Auth class
From a1e1f8b4d0fa4cd17467446f5cbe72dc1fc80bd8 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sat, 9 Jan 2021 02:07:49 +0100
Subject: [PATCH 181/331] Close #217. Support single-label-domains as valid.
New security parameter allowSingleLabelDomains
---
README.md | 4 +++
demo-django/saml/advanced_settings.json | 1 +
demo-flask/saml/advanced_settings.json | 1 +
demo-tornado/saml/advanced_settings.json | 1 +
.../demo_pyramid/saml/advanced_settings.json | 1 +
src/onelogin/saml2/logout_response.py | 1 -
src/onelogin/saml2/settings.py | 33 +++++++++++++++----
.../src/OneLogin/saml2_tests/settings_test.py | 19 +++++++----
8 files changed, 47 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index e98b60c4..c5528a2f 100644
--- a/README.md
+++ b/README.md
@@ -430,6 +430,10 @@ In addition to the required settings data (idp, sp), extra settings can be defin
// Provide the desire Duration, for example PT518400S (6 days)
"metadataCacheDuration": null,
+ // If enabled, URLs with single-label-domains will
+ // be allowed and not rejected by the settings validator (Enable it under Docker/Kubernetes/testing env, not recommended on production)
+ "allowSingleLabelDomains": false,
+
// Algorithm that the toolkit will use on signing process. Options:
// 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
// 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'
diff --git a/demo-django/saml/advanced_settings.json b/demo-django/saml/advanced_settings.json
index 1307b0ae..fef16fe9 100644
--- a/demo-django/saml/advanced_settings.json
+++ b/demo-django/saml/advanced_settings.json
@@ -10,6 +10,7 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
+ "allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
diff --git a/demo-flask/saml/advanced_settings.json b/demo-flask/saml/advanced_settings.json
index 1307b0ae..fef16fe9 100644
--- a/demo-flask/saml/advanced_settings.json
+++ b/demo-flask/saml/advanced_settings.json
@@ -10,6 +10,7 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
+ "allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
diff --git a/demo-tornado/saml/advanced_settings.json b/demo-tornado/saml/advanced_settings.json
index 1307b0ae..fef16fe9 100644
--- a/demo-tornado/saml/advanced_settings.json
+++ b/demo-tornado/saml/advanced_settings.json
@@ -10,6 +10,7 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
+ "allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
diff --git a/demo_pyramid/demo_pyramid/saml/advanced_settings.json b/demo_pyramid/demo_pyramid/saml/advanced_settings.json
index 1307b0ae..fef16fe9 100644
--- a/demo_pyramid/demo_pyramid/saml/advanced_settings.json
+++ b/demo_pyramid/demo_pyramid/saml/advanced_settings.json
@@ -10,6 +10,7 @@
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
+ "allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
},
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index de827f6a..7548d57f 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -158,7 +158,6 @@ def build(self, in_response_to):
:type in_response_to: string
"""
sp_data = self.__settings.get_sp_data()
- idp_data = self.__settings.get_idp_data()
uid = OneLogin_Saml2_Utils.generate_unique_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index e51a7124..128802f5 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -39,10 +39,19 @@
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
+url_regex_single_label_domain = re.compile(
+ r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
+ r'(?:(?:[A-Z0-9_](?:[A-Z0-9-_]{0,61}[A-Z0-9_])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
+ r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,61}[A-Z0-9_]))|' # single-label-domain
+ r'localhost|' # localhost...
+ r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
+ r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
+ r'(?::\d+)?' # optional port
+ r'(?:/?|[/?]\S+)$', re.IGNORECASE)
url_schemes = ['http', 'https', 'ftp', 'ftps']
-def validate_url(url):
+def validate_url(url, allow_single_label_domain=False):
"""
Auxiliary method to validate an urllib
:param url: An url to be validated
@@ -54,8 +63,12 @@ def validate_url(url):
scheme = url.split('://')[0].lower()
if scheme not in url_schemes:
return False
- if not bool(url_regex.search(url)):
- return False
+ if allow_single_label_domain:
+ if not bool(url_regex_single_label_domain.search(url)):
+ return False
+ else:
+ if not bool(url_regex.search(url)):
+ return False
return True
@@ -353,17 +366,18 @@ def check_idp_settings(self, settings):
if not settings.get('idp'):
errors.append('idp_not_found')
else:
+ allow_single_domain_urls = self._get_allow_single_label_domain(settings)
idp = settings['idp']
if not idp.get('entityId'):
errors.append('idp_entityId_not_found')
if not idp.get('singleSignOnService', {}).get('url'):
errors.append('idp_sso_not_found')
- elif not validate_url(idp['singleSignOnService']['url']):
+ elif not validate_url(idp['singleSignOnService']['url'], allow_single_domain_urls):
errors.append('idp_sso_url_invalid')
slo_url = idp.get('singleLogoutService', {}).get('url')
- if slo_url and not validate_url(slo_url):
+ if slo_url and not validate_url(slo_url, allow_single_domain_urls):
errors.append('idp_slo_url_invalid')
if 'security' in settings:
@@ -407,6 +421,7 @@ def check_sp_settings(self, settings):
if not settings.get('sp'):
errors.append('sp_not_found')
else:
+ allow_single_domain_urls = self._get_allow_single_label_domain(settings)
# check_sp_certs uses self.__sp so I add it
old_sp = self.__sp
self.__sp = settings['sp']
@@ -419,7 +434,7 @@ def check_sp_settings(self, settings):
if not sp.get('assertionConsumerService', {}).get('url'):
errors.append('sp_acs_not_found')
- elif not validate_url(sp['assertionConsumerService']['url']):
+ elif not validate_url(sp['assertionConsumerService']['url'], allow_single_domain_urls):
errors.append('sp_acs_url_invalid')
if sp.get('attributeConsumingService'):
@@ -448,7 +463,7 @@ def check_sp_settings(self, settings):
errors.append('sp_attributeConsumingService_serviceDescription_type_invalid')
slo_url = sp.get('singleLogoutService', {}).get('url')
- if slo_url and not validate_url(slo_url):
+ if slo_url and not validate_url(slo_url, allow_single_domain_urls):
errors.append('sp_sls_url_invalid')
if 'signMetadata' in security and isinstance(security['signMetadata'], dict):
@@ -833,3 +848,7 @@ def is_debug_active(self):
:rtype: boolean
"""
return self.__debug
+
+ def _get_allow_single_label_domain(self, settings):
+ security = settings.get('security', {})
+ return 'allowSingleLabelDomains' in security.keys() and security['allowSingleLabelDomains']
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index fb7ba7fe..25e87e38 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -71,11 +71,18 @@ def testLoadSettingsFromDict(self):
except Exception as e:
self.assertIn('Invalid dict settings: idp_sso_url_invalid', str(e))
+ settings_info['idp']['singleSignOnService']['url'] = 'http://single-label-domain'
+ settings_info['security'] = {}
+ settings_info['security']['allowSingleLabelDomains'] = True
+ settings_4 = OneLogin_Saml2_Settings(settings_info)
+ self.assertEqual(len(settings_4.get_errors()), 0)
+
+ del settings_info['security']
del settings_info['sp']
del settings_info['idp']
try:
- settings_4 = OneLogin_Saml2_Settings(settings_info)
- self.assertNotEqual(len(settings_4.get_errors()), 0)
+ settings_5 = OneLogin_Saml2_Settings(settings_info)
+ self.assertNotEqual(len(settings_5.get_errors()), 0)
except Exception as e:
self.assertIn('Invalid dict settings', str(e))
self.assertIn('idp_not_found', str(e))
@@ -85,8 +92,8 @@ def testLoadSettingsFromDict(self):
settings_info['security']['authnRequestsSigned'] = True
settings_info['custom_base_path'] = dirname(__file__)
try:
- settings_5 = OneLogin_Saml2_Settings(settings_info)
- self.assertNotEqual(len(settings_5.get_errors()), 0)
+ settings_6 = OneLogin_Saml2_Settings(settings_info)
+ self.assertNotEqual(len(settings_6.get_errors()), 0)
except Exception as e:
self.assertIn('Invalid dict settings: sp_cert_not_found_and_required', str(e))
@@ -94,8 +101,8 @@ def testLoadSettingsFromDict(self):
settings_info['security']['nameIdEncrypted'] = True
del settings_info['idp']['x509cert']
try:
- settings_6 = OneLogin_Saml2_Settings(settings_info)
- self.assertNotEqual(len(settings_6.get_errors()), 0)
+ settings_7 = OneLogin_Saml2_Settings(settings_info)
+ self.assertNotEqual(len(settings_7.get_errors()), 0)
except Exception as e:
self.assertIn('Invalid dict settings: idp_cert_not_found_and_required', str(e))
From 8c828e3f615617a6e34f3e400f55d21cc6b5d3a9 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sat, 9 Jan 2021 02:12:59 +0100
Subject: [PATCH 182/331] Add doc to validate_url method
---
src/onelogin/saml2/settings.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 128802f5..924fb0c2 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -56,6 +56,8 @@ def validate_url(url, allow_single_label_domain=False):
Auxiliary method to validate an urllib
:param url: An url to be validated
:type url: string
+ :param allow_single_label_domain: In order to allow or not single label domain
+ :type url: bool
:returns: True if the url is valid
:rtype: bool
"""
From f1e4af244efd6c16921653ffa5f76a71f12f7985 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Sat, 9 Jan 2021 03:36:15 +0100
Subject: [PATCH 183/331] Remove external lib method get_ext_lib_path. Add
set_cert_path in order to allow set the cert path in a different folder than
the toolkit
---
src/onelogin/saml2/settings.py | 18 +++----
.../src/OneLogin/saml2_tests/settings_test.py | 48 +++++++++++++++----
2 files changed, 45 insertions(+), 21 deletions(-)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 924fb0c2..04a66047 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -153,8 +153,7 @@ def __load_paths(self, base_path=None):
self.__paths = {
'base': base_path,
'cert': base_path + 'certs' + sep,
- 'lib': base_path + 'lib' + sep,
- 'extlib': base_path + 'extlib' + sep,
+ 'lib': dirname(__file__) + sep
}
def __update_paths(self, settings):
@@ -187,6 +186,12 @@ def get_cert_path(self):
"""
return self.__paths['cert']
+ def set_cert_path(self, path):
+ """
+ Set a new cert path
+ """
+ self.__paths['cert'] = path
+
def get_lib_path(self):
"""
Returns lib path
@@ -196,15 +201,6 @@ def get_lib_path(self):
"""
return self.__paths['lib']
- def get_ext_lib_path(self):
- """
- Returns external lib path
-
- :return: The external library folder path
- :rtype: string
- """
- return self.__paths['extlib']
-
def get_schemas_path(self):
"""
Returns schema path
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index 25e87e38..41e6de6f 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -148,29 +148,57 @@ def testGetCertPath(self):
settings = OneLogin_Saml2_Settings(custom_base_path=self.settings_path)
self.assertEqual(self.settings_path + sep + 'certs' + sep, settings.get_cert_path())
- def testGetLibPath(self):
+ def testSetCertPath(self):
"""
- Tests getLibPath method of the OneLogin_Saml2_Settings
+ Tests setCertPath method of the OneLogin_Saml2_Settings
"""
settings = OneLogin_Saml2_Settings(custom_base_path=self.settings_path)
- base = settings.get_base_path()
- self.assertEqual(join(base, 'lib') + sep, settings.get_lib_path())
+ self.assertEqual(self.settings_path + sep + 'certs' + sep, settings.get_cert_path())
- def testGetExtLibPath(self):
+ settings.set_cert_path('/tmp')
+ self.assertEqual('/tmp', settings.get_cert_path())
+
+ def testGetLibPath(self):
"""
- Tests getExtLibPath method of the OneLogin_Saml2_Settings
+ Tests getLibPath method of the OneLogin_Saml2_Settings
"""
+ settingsInfo = self.loadSettingsJSON()
+ settings = OneLogin_Saml2_Settings(settingsInfo)
+ path = settings.get_base_path()
+ self.assertEqual(settings.get_lib_path(), join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/'))
+ self.assertEqual(path, join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/../../../tests/data/customPath/'))
+
+ del settingsInfo['custom_base_path']
+ settings = OneLogin_Saml2_Settings(settingsInfo)
+ path = settings.get_base_path()
+ self.assertEqual(settings.get_lib_path(), join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/'))
+ self.assertEqual(path, join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/'))
+
settings = OneLogin_Saml2_Settings(custom_base_path=self.settings_path)
- base = settings.get_base_path()
- self.assertEqual(join(base, 'extlib') + sep, settings.get_ext_lib_path())
+ path = settings.get_base_path()
+ self.assertEqual(settings.get_lib_path(), join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/'))
+ self.assertEqual(path, join(dirname(dirname(dirname(dirname(__file__)))), 'settings/'))
def testGetSchemasPath(self):
"""
Tests getSchemasPath method of the OneLogin_Saml2_Settings
"""
+ settingsInfo = self.loadSettingsJSON()
+ settings = OneLogin_Saml2_Settings(settingsInfo)
+ path = settings.get_base_path()
+ self.assertEqual(settings.get_schemas_path(), join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/schemas/'))
+ self.assertEqual(path, join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/../../../tests/data/customPath/'))
+
+ del settingsInfo['custom_base_path']
+ settings = OneLogin_Saml2_Settings(settingsInfo)
+ path = settings.get_base_path()
+ self.assertEqual(settings.get_schemas_path(), join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/schemas/'))
+ self.assertEqual(path, join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/'))
+
settings = OneLogin_Saml2_Settings(custom_base_path=self.settings_path)
- base = settings.get_base_path()
- self.assertEqual(join(base, 'lib', 'schemas') + sep, settings.get_schemas_path())
+ path = settings.get_base_path()
+ self.assertEqual(settings.get_schemas_path(), join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/schemas/'))
+ self.assertEqual(path, join(dirname(dirname(dirname(dirname(__file__)))), 'settings/'))
def testGetIdPSSOurl(self):
"""
From 52f0daa7e88bb4bf5bcc6b6423852767dbba98f6 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 11 Jan 2021 17:39:07 +0100
Subject: [PATCH 184/331] Close #218. Add support for
get_friendlyname_attributes and get_friendlyname_attribute
---
src/onelogin/saml2/auth.py | 24 +++++++++++++
src/onelogin/saml2/response.py | 36 +++++++++++++++++++
.../response1_with_friendlyname.xml.base64 | 1 +
tests/src/OneLogin/saml2_tests/auth_test.py | 2 ++
.../src/OneLogin/saml2_tests/response_test.py | 26 +++++++++++++-
5 files changed, 88 insertions(+), 1 deletion(-)
create mode 100644 tests/data/responses/response1_with_friendlyname.xml.base64
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index c173b1b5..67559abf 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -53,6 +53,7 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
else:
self.__settings = OneLogin_Saml2_Settings(old_settings, custom_base_path)
self.__attributes = dict()
+ self.__friendlyname_attributes = dict()
self.__nameid = None
self.__nameid_format = None
self.__nameid_nq = None
@@ -107,6 +108,7 @@ def process_response(self, request_id=None):
if response.is_valid(self.__request_data, request_id):
self.__attributes = response.get_attributes()
+ self.__friendlyname_attributes = response.get_friendlyname_attributes()
self.__nameid = response.get_nameid()
self.__nameid_format = response.get_nameid_format()
self.__nameid_nq = response.get_nameid_nq()
@@ -231,6 +233,15 @@ def get_attributes(self):
"""
return self.__attributes
+ def get_friendlyname_attributes(self):
+ """
+ Returns the set of SAML attributes indexed by FiendlyName.
+
+ :returns: SAML attributes
+ :rtype: dict
+ """
+ return self.__friendlyname_attributes
+
def get_nameid(self):
"""
Returns the nameID.
@@ -321,6 +332,19 @@ def get_attribute(self, name):
assert isinstance(name, compat.str_type)
return self.__attributes.get(name)
+ def get_friendlyname_attribute(self, friendlyname):
+ """
+ Returns the requested SAML attribute searched by FriendlyName.
+
+ :param friendlyname: FriendlyName of the attribute
+ :type friendlyname: string
+
+ :returns: Attribute value(s) if exists or None
+ :rtype: list
+ """
+ assert isinstance(friendlyname, compat.str_type)
+ return self.__friendlyname_attributes.get(friendlyname)
+
def get_last_request_id(self):
"""
:returns: The ID of the last Request SAML message generated.
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 1e7736a8..792c88af 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -603,6 +603,42 @@ def get_attributes(self):
attributes[attr_name] = values
return attributes
+ def get_friendlyname_attributes(self):
+ """
+ Gets the Attributes from the AttributeStatement element indexed by FiendlyName.
+ EncryptedAttributes are not supported
+ """
+ attributes = {}
+ attribute_nodes = self.__query_assertion('/saml:AttributeStatement/saml:Attribute')
+ for attribute_node in attribute_nodes:
+ attr_friendlyname = attribute_node.get('FriendlyName')
+ if attr_friendlyname:
+ if attr_friendlyname in attributes.keys():
+ raise OneLogin_Saml2_ValidationError(
+ 'Found an Attribute element with duplicated FriendlyName',
+ OneLogin_Saml2_ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND
+ )
+
+ values = []
+ for attr in attribute_node.iterchildren('{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP['saml']):
+ attr_text = OneLogin_Saml2_XML.element_text(attr)
+ if attr_text:
+ attr_text = attr_text.strip()
+ if attr_text:
+ values.append(attr_text)
+
+ # Parse any nested NameID children
+ for nameid in attr.iterchildren('{%s}NameID' % OneLogin_Saml2_Constants.NSMAP['saml']):
+ values.append({
+ 'NameID': {
+ 'Format': nameid.get('Format'),
+ 'NameQualifier': nameid.get('NameQualifier'),
+ 'value': nameid.text
+ }
+ })
+ attributes[attr_friendlyname] = values
+ return attributes
+
def validate_num_assertions(self):
"""
Verifies that the document only contains a single Assertion (encrypted or not)
diff --git a/tests/data/responses/response1_with_friendlyname.xml.base64 b/tests/data/responses/response1_with_friendlyname.xml.base64
new file mode 100644
index 00000000..a17c3941
--- /dev/null
+++ b/tests/data/responses/response1_with_friendlyname.xml.base64
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeDhmZmIzOTgzLWNiZjYtOTJhMS1mMmM0LTYxOWFlMWJlMWM4NiIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4NCiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4OGZmYjM5ODMtY2JmNi05MmExLWYyYzQtNjE5YWUxYmUxYzg2Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5oZ3VRYkNIYW5pYmJEQzdxM1p6eHpIY1Bvbkk9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPkdhbmNEOXZSb2g5TWJUMDAyRHk3OXQxbTZJNllmaFVLUGZibGttcDJ1ZG9sWHVqdjZlMU1XdnNWbXhOenRzSUdseEFhMHFLRGlTTXpDTkRac2szanN5c1VsMW5BS25BZzE4NWpmWGpzemhzZG1SK005MWR4azZrZmNMVW9zT29sb3ZhZFdMUFdxbjdQM2o4LzV4enA5THBSQTNndkI0MTgyUlNpcldDQlhQUT08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT4NCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zdXBwb3J0QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6MzdaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgRnJpZW5kbHlOYW1lPSJ1c2VybmFtZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+dmFsdWU8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
\ No newline at end of file
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 81d009e9..2ccd3994 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -240,6 +240,8 @@ def testProcessResponseValid(self):
attributes = auth.get_attributes()
self.assertNotEqual(len(attributes), 0)
self.assertEqual(auth.get_attribute('mail'), attributes['mail'])
+ friendlyname_attributes = auth.get_friendlyname_attributes()
+ self.assertEqual(len(friendlyname_attributes), 0)
session_index = auth.get_session_index()
self.assertEqual('_6273d77b8cde0c333ec79d22a9fa0003b9fe2d75cb', session_index)
self.assertEqual("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", auth.get_nameid_format())
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 7d9934a1..c6eb7eac 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -656,7 +656,7 @@ def testGetSessionIndex(self):
def testGetAttributes(self):
"""
- Tests the getAttributes method of the OneLogin_Saml2_Response
+ Tests the get_attributes method of the OneLogin_Saml2_Response
"""
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
@@ -678,6 +678,30 @@ def testGetAttributes(self):
response_3 = OneLogin_Saml2_Response(settings, xml_3)
self.assertEqual({}, response_3.get_attributes())
+ def testGetFriendlyAttributes(self):
+ """
+ Tests the get_friendlyname_attributes method of the OneLogin_Saml2_Response
+ """
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+ xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
+ response = OneLogin_Saml2_Response(settings, xml)
+ self.assertEqual({}, response.get_friendlyname_attributes())
+
+ expected_attributes = {
+ 'username': ['demo']
+ }
+ xml_2 = self.file_contents(join(self.data_path, 'responses', 'response1_with_friendlyname.xml.base64'))
+ response_2 = OneLogin_Saml2_Response(settings, xml_2)
+ self.assertEqual(expected_attributes, response_2.get_friendlyname_attributes())
+
+ xml_3 = self.file_contents(join(self.data_path, 'responses', 'response2.xml.base64'))
+ response_3 = OneLogin_Saml2_Response(settings, xml_3)
+ self.assertEqual({}, response_3.get_friendlyname_attributes())
+
+ xml_4 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'encrypted_attrs.xml.base64'))
+ response_4 = OneLogin_Saml2_Response(settings, xml_4)
+ self.assertEqual({}, response_4.get_friendlyname_attributes())
+
def testGetNestedNameIDAttributes(self):
"""
Tests the getAttributes method of the OneLogin_Saml2_Response with nested
From 06e15d3cbc9db1f7d0a8fb265b7dca2583cc4b92 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 11 Jan 2021 21:06:53 +0100
Subject: [PATCH 185/331] Adapt code to work with py2.7 as well. Follow same
pattern on response, logoutrequest and logoutresponse destination check
---
src/onelogin/saml2/logout_request.py | 25 ++++++++++++-------------
src/onelogin/saml2/logout_response.py | 11 ++++++-----
src/onelogin/saml2/response.py | 1 -
src/onelogin/saml2/utils.py | 8 ++++----
4 files changed, 22 insertions(+), 23 deletions(-)
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index 6ee8b553..865841f1 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -311,19 +311,18 @@ def is_valid(self, request_data, raise_exceptions=False):
)
# Check destination
- if root.get('Destination', None):
- destination = root.get('Destination')
- if destination != '':
- if OneLogin_Saml2_Utils.normalize_url(current_url) not in OneLogin_Saml2_Utils.normalize_url(destination):
- raise OneLogin_Saml2_ValidationError(
- 'The LogoutRequest was received at '
- '%(currentURL)s instead of %(destination)s' %
- {
- 'currentURL': current_url,
- 'destination': destination,
- },
- OneLogin_Saml2_ValidationError.WRONG_DESTINATION
- )
+ destination = root.get('Destination', None)
+ if destination:
+ if not OneLogin_Saml2_Utils.normalize_url(url=destination).startswith(OneLogin_Saml2_Utils.normalize_url(url=current_url)):
+ raise OneLogin_Saml2_ValidationError(
+ 'The LogoutRequest was received at '
+ '%(currentURL)s instead of %(destination)s' %
+ {
+ 'currentURL': current_url,
+ 'destination': destination,
+ },
+ OneLogin_Saml2_ValidationError.WRONG_DESTINATION
+ )
# Check issuer
issuer = OneLogin_Saml2_Logout_Request.get_issuer(root)
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 7c89f9e3..b31f64da 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -118,11 +118,12 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Check destination
destination = self.document.get('Destination', None)
- if destination and OneLogin_Saml2_Utils.normalize_url(url=current_url) not in OneLogin_Saml2_Utils.normalize_url(url=destination):
- raise OneLogin_Saml2_ValidationError(
- 'The LogoutResponse was received at %s instead of %s' % (current_url, destination),
- OneLogin_Saml2_ValidationError.WRONG_DESTINATION
- )
+ if destination:
+ if not OneLogin_Saml2_Utils.normalize_url(url=destination).startswith(OneLogin_Saml2_Utils.normalize_url(url=current_url)):
+ raise OneLogin_Saml2_ValidationError(
+ 'The LogoutResponse was received at %s instead of %s' % (current_url, destination),
+ OneLogin_Saml2_ValidationError.WRONG_DESTINATION
+ )
if security['wantMessagesSigned']:
if 'Signature' not in get_data:
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 8030de26..6de385e9 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -10,7 +10,6 @@
"""
from copy import deepcopy
-from urllib.parse import urlsplit, urlunsplit
from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.utils import OneLogin_Saml2_Utils, OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError, return_false_on_exception
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 3c22e406..bc618f03 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -20,7 +20,6 @@
from functools import wraps
from uuid import uuid4
from xml.dom.minidom import Element
-from urllib.parse import urlsplit, urlunsplit
import zlib
import xmlsec
@@ -31,8 +30,9 @@
try:
- from urllib.parse import quote_plus # py3
+ from urllib.parse import quote_plus, urlsplit, urlunsplit # py3
except ImportError:
+ from urlparse import urlsplit, urlunsplit
from urllib import quote_plus # py2
@@ -1078,8 +1078,8 @@ def normalize_url(url):
:rtype: String
"""
try:
- scheme, netloc, *rest = urlsplit(url)
- normalized_url = urlunsplit((scheme.lower(), netloc.lower(), *rest))
+ scheme, netloc, path, query, fragment = urlsplit(url)
+ normalized_url = urlunsplit((scheme.lower(), netloc.lower(), path, query, fragment))
return normalized_url
except Exception:
return url
\ No newline at end of file
From a281bc5482a8b1e10f36ad1a8bb9836b5a9d50ca Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 11 Jan 2021 21:53:03 +0100
Subject: [PATCH 186/331] pep8 fixes
---
.../OneLogin/saml2_tests/logout_response_test.py | 3 +--
tests/src/OneLogin/saml2_tests/response_test.py | 16 ++++++++--------
tests/src/OneLogin/saml2_tests/settings_test.py | 2 +-
3 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index 3ab17653..90872f10 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -297,7 +297,7 @@ def testIsValidWithCapitalization(self):
response_2.is_valid(request_data, raise_exceptions=True)
plain_message = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(message))
-
+
current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data).lower()
plain_message = plain_message.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url)
message_3 = OneLogin_Saml2_Utils.deflate_and_base64_encode(plain_message)
@@ -333,7 +333,6 @@ def testIsInValidWithCapitalization(self):
response_3 = OneLogin_Saml2_Logout_Response(settings, message_3)
self.assertFalse(response_3.is_valid(request_data))
-
def testIsValidWithXMLEncoding(self):
"""
Tests the is_valid method of the OneLogin_Saml2_LogoutResponse
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index cdecf91f..3ace583e 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -18,6 +18,7 @@
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils
+
class OneLogin_Saml2_Response_Test(unittest.TestCase):
data_path = join(dirname(dirname(dirname(dirname(__file__)))), 'data')
settings_path = join(dirname(dirname(dirname(dirname(__file__)))), 'settings')
@@ -54,7 +55,7 @@ def get_request_data_domain_capitalized(self):
'http_host': 'StuFF.Com',
'script_name': 'endpoints/endpoints/acs.php'
}
-
+
def get_request_data_path_capitalized(self):
return {
'http_host': 'stuff.com',
@@ -1018,7 +1019,7 @@ def testIsInValidDuplicatedAttrs(self):
response = OneLogin_Saml2_Response(settings, xml)
with self.assertRaisesRegex(Exception, 'Found an Attribute element with duplicated Name'):
response.get_attributes()
-
+
def testIsInValidDestination(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response class
@@ -1065,17 +1066,16 @@ def testIsInValidDestinationCapitalizationOfElements(self):
Tests the is_valid method of the OneLogin_Saml2_Response class
Case Invalid Response due to differences in capitalization of path
"""
-
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
message = self.file_contents(join(self.data_path, 'responses', 'unsigned_response.xml.base64'))
-
- #Test path capitalized
+
+ # Test path capitalized
settings.set_strict(True)
response = OneLogin_Saml2_Response(settings, message)
self.assertFalse(response.is_valid(self.get_request_data_path_capitalized()))
self.assertIn('The response was received at', response.get_error())
- #Test both domain and path capitalized
+ # Test both domain and path capitalized
response_2 = OneLogin_Saml2_Response(settings, message)
self.assertFalse(response_2.is_valid(self.get_request_data_both_capitalized()))
self.assertIn('The response was received at', response_2.get_error())
@@ -1087,13 +1087,13 @@ def testIsValidDestinationCapitalizationOfHost(self):
"""
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
message = self.file_contents(join(self.data_path, 'responses', 'unsigned_response.xml.base64'))
- #Test domain capitalized
+ # Test domain capitalized
settings.set_strict(True)
response = OneLogin_Saml2_Response(settings, message)
self.assertFalse(response.is_valid(self.get_request_data_domain_capitalized()))
self.assertNotIn('The response was received at', response.get_error())
- #Assert we got past the destination check, which appears later
+ # Assert we got past the destination check, which appears later
self.assertIn('A valid SubjectConfirmation was not found', response.get_error())
def testIsInValidAudience(self):
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index 41e6de6f..ddf8c531 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -198,7 +198,7 @@ def testGetSchemasPath(self):
settings = OneLogin_Saml2_Settings(custom_base_path=self.settings_path)
path = settings.get_base_path()
self.assertEqual(settings.get_schemas_path(), join(dirname(dirname(dirname(dirname(dirname(__file__))))), 'src/onelogin/saml2/schemas/'))
- self.assertEqual(path, join(dirname(dirname(dirname(dirname(__file__)))), 'settings/'))
+ self.assertEqual(path, join(dirname(dirname(dirname(dirname(__file__)))), 'settings/'))
def testGetIdPSSOurl(self):
"""
From 8d130bf62ad27752e10d9cf3af59f9144585f1a2 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 12 Jan 2021 12:56:12 +0100
Subject: [PATCH 187/331] More pep8 fixes
---
src/onelogin/saml2/response.py | 2 +-
src/onelogin/saml2/utils.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 6de385e9..677ef6b7 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -901,7 +901,7 @@ def __decrypt_assertion(self, xml):
decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug=debug, inplace=True)
xml.replace(encrypted_assertion_nodes[0], decrypted)
return xml
-
+
def get_error(self):
"""
After executing a validation process, if it fails this method returns the cause
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index bc618f03..4ca5962c 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -1066,7 +1066,7 @@ def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_
@staticmethod
def normalize_url(url):
"""
- Returns normalized URL for comparison.
+ Returns normalized URL for comparison.
This method converts the netloc to lowercase, as it should be case-insensitive (per RFC 4343, RFC 7617)
If standardization fails, the original URL is returned
Python documentation indicates that URL split also normalizes query strings if empty query fields are present
@@ -1082,4 +1082,4 @@ def normalize_url(url):
normalized_url = urlunsplit((scheme.lower(), netloc.lower(), path, query, fragment))
return normalized_url
except Exception:
- return url
\ No newline at end of file
+ return url
From 95f06d340b0727a65a081575e2f8750353b2a4e5 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Tue, 12 Jan 2021 13:15:25 +0100
Subject: [PATCH 188/331] Add _generate_request_id to logout_request and
logout_response and replace staticmethod by classmethod
---
src/onelogin/saml2/logout_request.py | 27 ++++++++++++++++-----------
src/onelogin/saml2/logout_response.py | 11 +++++++++--
2 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index 51944a62..b7d77ca6 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -59,8 +59,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
idp_data = self.__settings.get_idp_data()
security = self.__settings.get_security_data()
- uid = OneLogin_Saml2_Utils.generate_unique_id()
- self.id = uid
+ self.id = self._generate_request_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
@@ -108,7 +107,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
logout_request = OneLogin_Saml2_Templates.LOGOUT_REQUEST % \
{
- 'id': uid,
+ 'id': self.id,
'issue_instant': issue_instant,
'single_logout_url': self.__settings.get_idp_slo_response_url(),
'entity_id': sp_data['entityId'],
@@ -144,8 +143,8 @@ def get_xml(self):
"""
return self.__logout_request
- @staticmethod
- def get_id(request):
+ @classmethod
+ def get_id(cls, request):
"""
Returns the ID of the Logout Request
:param request: Logout Request Message
@@ -157,8 +156,8 @@ def get_id(request):
elem = OneLogin_Saml2_XML.to_etree(request)
return elem.get('ID', None)
- @staticmethod
- def get_nameid_data(request, key=None):
+ @classmethod
+ def get_nameid_data(cls, request, key=None):
"""
Gets the NameID Data of the the Logout Request
:param request: Logout Request Message
@@ -234,8 +233,8 @@ def get_nameid_format(cls, request, key=None):
name_id_format = name_id_data['Format']
return name_id_format
- @staticmethod
- def get_issuer(request):
+ @classmethod
+ def get_issuer(cls, request):
"""
Gets the Issuer of the Logout Request Message
:param request: Logout Request Message
@@ -251,8 +250,8 @@ def get_issuer(request):
issuer = OneLogin_Saml2_XML.element_text(issuer_nodes[0])
return issuer
- @staticmethod
- def get_session_indexes(request):
+ @classmethod
+ def get_session_indexes(cls, request):
"""
Gets the SessionIndexes from the Logout Request
:param request: Logout Request Message
@@ -359,3 +358,9 @@ def get_error(self):
After executing a validation process, if it fails this method returns the cause
"""
return self.__error
+
+ def _generate_request_id(self):
+ """
+ Generate an unique logout request ID.
+ """
+ return OneLogin_Saml2_Utils.generate_unique_id()
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index b31f64da..73decb10 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -160,12 +160,13 @@ def build(self, in_response_to):
"""
sp_data = self.__settings.get_sp_data()
- uid = OneLogin_Saml2_Utils.generate_unique_id()
+ self.id = self._generate_request_id()
+
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % \
{
- 'id': uid,
+ 'id': self.id,
'issue_instant': issue_instant,
'destination': self.__settings.get_idp_slo_response_url(),
'in_response_to': in_response_to,
@@ -211,3 +212,9 @@ def get_xml(self):
:rtype: string
"""
return self.__logout_response
+
+ def _generate_request_id(self):
+ """
+ Generate an unique logout response ID.
+ """
+ return OneLogin_Saml2_Utils.generate_unique_id()
\ No newline at end of file
From c25df8164563df28ec94dd6f5d7a9a6c65c06e83 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 13 Jan 2021 12:51:13 +0100
Subject: [PATCH 189/331] Added custom lxml parser based on the one defined at
xmldefused. Update copyright
---
LICENSE | 2 +-
setup.py | 5 +-
src/onelogin/__init__.py | 2 +-
src/onelogin/saml2/__init__.py | 2 +-
src/onelogin/saml2/auth.py | 14 +-
src/onelogin/saml2/authn_request.py | 4 +-
src/onelogin/saml2/compat.py | 2 +-
src/onelogin/saml2/constants.py | 2 +-
src/onelogin/saml2/errors.py | 2 +-
src/onelogin/saml2/idp_metadata_parser.py | 2 +-
src/onelogin/saml2/logout_request.py | 2 +-
src/onelogin/saml2/logout_response.py | 4 +-
src/onelogin/saml2/metadata.py | 2 +-
src/onelogin/saml2/response.py | 2 +-
src/onelogin/saml2/settings.py | 2 +-
src/onelogin/saml2/utils.py | 2 +-
src/onelogin/saml2/xml_templates.py | 2 +-
src/onelogin/saml2/xml_utils.py | 9 +-
src/onelogin/saml2/xmlparser.py | 147 ++++++++++++++++++
tests/src/OneLogin/saml2_tests/auth_test.py | 2 +-
.../saml2_tests/authn_request_test.py | 2 +-
tests/src/OneLogin/saml2_tests/error_test.py | 2 +-
.../saml2_tests/idp_metadata_parser_test.py | 2 +-
.../saml2_tests/logout_request_test.py | 2 +-
.../saml2_tests/logout_response_test.py | 2 +-
.../src/OneLogin/saml2_tests/metadata_test.py | 2 +-
.../src/OneLogin/saml2_tests/response_test.py | 2 +-
.../src/OneLogin/saml2_tests/settings_test.py | 2 +-
.../saml2_tests/signed_response_test.py | 2 +-
tests/src/OneLogin/saml2_tests/utils_test.py | 4 +-
.../OneLogin/saml2_tests/xml_utils_test.py | 2 +-
31 files changed, 191 insertions(+), 44 deletions(-)
create mode 100644 src/onelogin/saml2/xmlparser.py
diff --git a/LICENSE b/LICENSE
index 1c8f814e..734c18ba 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/setup.py b/setup.py
index 5a928564..3053b82c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
from setuptools import setup
@@ -37,8 +37,9 @@
test_suite='tests',
install_requires=[
'isodate>=0.5.0',
+ 'lxml>=3.3.5',
'xmlsec>=0.6.0',
- 'defusedxml>=0.5.0'
+ 'defusedxml==0.6.0'
],
dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'],
extras_require={
diff --git a/src/onelogin/__init__.py b/src/onelogin/__init__.py
index ba664a65..52ea1212 100644
--- a/src/onelogin/__init__.py
+++ b/src/onelogin/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Add SAML support to your Python softwares using this library.
diff --git a/src/onelogin/saml2/__init__.py b/src/onelogin/saml2/__init__.py
index ba664a65..52ea1212 100644
--- a/src/onelogin/saml2/__init__.py
+++ b/src/onelogin/saml2/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Add SAML support to your Python softwares using this library.
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index f8bddda4..a67e2071 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Auth class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Main class of OneLogin's Python Toolkit.
@@ -12,16 +12,16 @@
"""
import xmlsec
-from defusedxml.lxml import tostring
from onelogin.saml2 import compat
-from onelogin.saml2.settings import OneLogin_Saml2_Settings
-from onelogin.saml2.response import OneLogin_Saml2_Response
-from onelogin.saml2.logout_response import OneLogin_Saml2_Logout_Response
+from onelogin.saml2.authn_request import OneLogin_Saml2_Authn_Request
from onelogin.saml2.constants import OneLogin_Saml2_Constants
-from onelogin.saml2.utils import OneLogin_Saml2_Utils, OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError
from onelogin.saml2.logout_request import OneLogin_Saml2_Logout_Request
-from onelogin.saml2.authn_request import OneLogin_Saml2_Authn_Request
+from onelogin.saml2.logout_response import OneLogin_Saml2_Logout_Response
+from onelogin.saml2.response import OneLogin_Saml2_Response
+from onelogin.saml2.settings import OneLogin_Saml2_Settings
+from onelogin.saml2.utils import OneLogin_Saml2_Utils, OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError
+from onelogin.saml2.xmlparser import tostring
class OneLogin_Saml2_Auth(object):
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index ef9ed603..48ad9d2a 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Authn_Request class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
AuthNRequest class of OneLogin's Python Toolkit.
@@ -131,8 +131,6 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
def _generate_request_id(self):
"""
Generate an unique request ID.
-
- You can override this in a subclass.
"""
return OneLogin_Saml2_Utils.generate_unique_id()
diff --git a/src/onelogin/saml2/compat.py b/src/onelogin/saml2/compat.py
index b69e61e4..90bcfac7 100644
--- a/src/onelogin/saml2/compat.py
+++ b/src/onelogin/saml2/compat.py
@@ -2,7 +2,7 @@
""" py3 compatibility class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
"""
diff --git a/src/onelogin/saml2/constants.py b/src/onelogin/saml2/constants.py
index e0778bd2..e85a7fb9 100644
--- a/src/onelogin/saml2/constants.py
+++ b/src/onelogin/saml2/constants.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Constants class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Constants class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/errors.py b/src/onelogin/saml2/errors.py
index 6faba2e2..6a50f9f1 100644
--- a/src/onelogin/saml2/errors.py
+++ b/src/onelogin/saml2/errors.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Error class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Error class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 1172bd06..2d7eec2c 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
""" OneLogin_Saml2_IdPMetadataParser class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Metadata class of OneLogin's Python Toolkit.
"""
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index b7d77ca6..caf0701f 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Logout_Request class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Logout Request class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index 73decb10..bd942200 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Logout_Response class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Logout Response class of OneLogin's Python Toolkit.
@@ -217,4 +217,4 @@ def _generate_request_id(self):
"""
Generate an unique logout response ID.
"""
- return OneLogin_Saml2_Utils.generate_unique_id()
\ No newline at end of file
+ return OneLogin_Saml2_Utils.generate_unique_id()
diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py
index 9528b0e8..89e0af8f 100644
--- a/src/onelogin/saml2/metadata.py
+++ b/src/onelogin/saml2/metadata.py
@@ -2,7 +2,7 @@
""" OneLoginSaml2Metadata class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Metadata class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 677ef6b7..04264919 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Response class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
SAML Response class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 09e593e7..ab3dbe37 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Settings class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Setting class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 4ca5962c..1290033e 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Utils class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Auxiliary class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/xml_templates.py b/src/onelogin/saml2/xml_templates.py
index ec4f6260..306b1afe 100644
--- a/src/onelogin/saml2/xml_templates.py
+++ b/src/onelogin/saml2/xml_templates.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_Auth class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Main class of OneLogin's Python Toolkit.
diff --git a/src/onelogin/saml2/xml_utils.py b/src/onelogin/saml2/xml_utils.py
index d966e615..8cbd434b 100644
--- a/src/onelogin/saml2/xml_utils.py
+++ b/src/onelogin/saml2/xml_utils.py
@@ -2,7 +2,7 @@
""" OneLogin_Saml2_XML class
-Copyright (c) 2010-2018 OneLogin, Inc.
+Copyright (c) 2010-2021 OneLogin, Inc.
MIT License
Auxiliary class of OneLogin's Python Toolkit.
@@ -11,9 +11,9 @@
from os.path import join, dirname
from lxml import etree
-from defusedxml.lxml import tostring, fromstring
from onelogin.saml2 import compat
from onelogin.saml2.constants import OneLogin_Saml2_Constants
+from onelogin.saml2.xmlparser import tostring, fromstring
for prefix, url in OneLogin_Saml2_Constants.NSMAP.items():
@@ -63,9 +63,9 @@ def to_etree(xml):
if isinstance(xml, OneLogin_Saml2_XML._element_class):
return xml
if isinstance(xml, OneLogin_Saml2_XML._bytes_class):
- return OneLogin_Saml2_XML._parse_etree(xml, forbid_dtd=True)
+ return OneLogin_Saml2_XML._parse_etree(xml, forbid_dtd=True, forbid_entities=True)
if isinstance(xml, OneLogin_Saml2_XML._text_class):
- return OneLogin_Saml2_XML._parse_etree(compat.to_bytes(xml), forbid_dtd=True)
+ return OneLogin_Saml2_XML._parse_etree(compat.to_bytes(xml), forbid_dtd=True, forbid_entities=True)
raise ValueError('unsupported type %r' % type(xml))
@@ -172,5 +172,6 @@ def extract_tag_text(xml, tagname):
@staticmethod
def element_text(node):
+ # Double check, the LXML Parser already removes comments
etree.strip_tags(node, etree.Comment)
return node.text
diff --git a/src/onelogin/saml2/xmlparser.py b/src/onelogin/saml2/xmlparser.py
new file mode 100644
index 00000000..33010ac5
--- /dev/null
+++ b/src/onelogin/saml2/xmlparser.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+# Based on the lxml example from defusedxml
+#
+# Copyright (c) 2013 by Christian Heimes
+# Licensed to PSF under a Contributor Agreement.
+# See https://www.python.org/psf/license for licensing details.
+"""lxml.etree protection"""
+
+from __future__ import print_function, absolute_import
+
+import threading
+
+from lxml import etree as _etree
+
+from defusedxml.lxml import DTDForbidden, EntitiesForbidden, NotSupportedError
+
+LXML3 = _etree.LXML_VERSION[0] >= 3
+
+__origin__ = "lxml.etree"
+
+tostring = _etree.tostring
+
+
+class RestrictedElement(_etree.ElementBase):
+ """A restricted Element class that filters out instances of some classes
+ """
+
+ __slots__ = ()
+ blacklist = (_etree._Entity, _etree._ProcessingInstruction, _etree._Comment)
+
+ def _filter(self, iterator):
+ blacklist = self.blacklist
+ for child in iterator:
+ if isinstance(child, blacklist):
+ continue
+ yield child
+
+ def __iter__(self):
+ iterator = super(RestrictedElement, self).__iter__()
+ return self._filter(iterator)
+
+ def iterchildren(self, tag=None, reversed=False):
+ iterator = super(RestrictedElement, self).iterchildren(tag=tag, reversed=reversed)
+ return self._filter(iterator)
+
+ def iter(self, tag=None, *tags):
+ iterator = super(RestrictedElement, self).iter(tag=tag, *tags)
+ return self._filter(iterator)
+
+ def iterdescendants(self, tag=None, *tags):
+ iterator = super(RestrictedElement, self).iterdescendants(tag=tag, *tags)
+ return self._filter(iterator)
+
+ def itersiblings(self, tag=None, preceding=False):
+ iterator = super(RestrictedElement, self).itersiblings(tag=tag, preceding=preceding)
+ return self._filter(iterator)
+
+ def getchildren(self):
+ iterator = super(RestrictedElement, self).__iter__()
+ return list(self._filter(iterator))
+
+ def getiterator(self, tag=None):
+ iterator = super(RestrictedElement, self).getiterator(tag)
+ return self._filter(iterator)
+
+
+class GlobalParserTLS(threading.local):
+ """Thread local context for custom parser instances
+ """
+
+ parser_config = {
+ "resolve_entities": False,
+ 'remove_comments': True,
+ 'no_network': True,
+ 'remove_pis': True,
+ 'huge_tree': False
+ }
+
+ element_class = RestrictedElement
+
+ def createDefaultParser(self):
+ parser = _etree.XMLParser(**self.parser_config)
+ element_class = self.element_class
+ if self.element_class is not None:
+ lookup = _etree.ElementDefaultClassLookup(element=element_class)
+ parser.set_element_class_lookup(lookup)
+ return parser
+
+ def setDefaultParser(self, parser):
+ self._default_parser = parser
+
+ def getDefaultParser(self):
+ parser = getattr(self, "_default_parser", None)
+ if parser is None:
+ parser = self.createDefaultParser()
+ self.setDefaultParser(parser)
+ return parser
+
+
+_parser_tls = GlobalParserTLS()
+getDefaultParser = _parser_tls.getDefaultParser
+
+
+def check_docinfo(elementtree, forbid_dtd=False, forbid_entities=True):
+ """Check docinfo of an element tree for DTD and entity declarations
+ The check for entity declarations needs lxml 3 or newer. lxml 2.x does
+ not support dtd.iterentities().
+ """
+ docinfo = elementtree.docinfo
+ if docinfo.doctype:
+ if forbid_dtd:
+ raise DTDForbidden(docinfo.doctype, docinfo.system_url, docinfo.public_id)
+ if forbid_entities and not LXML3:
+ # lxml < 3 has no iterentities()
+ raise NotSupportedError("Unable to check for entity declarations " "in lxml 2.x")
+
+ if forbid_entities:
+ for dtd in docinfo.internalDTD, docinfo.externalDTD:
+ if dtd is None:
+ continue
+ for entity in dtd.iterentities():
+ raise EntitiesForbidden(entity.name, entity.content, None, None, None, None)
+
+
+def parse(source, parser=None, base_url=None, forbid_dtd=True, forbid_entities=True):
+ if parser is None:
+ parser = getDefaultParser()
+ elementtree = _etree.parse(source, parser, base_url=base_url)
+ check_docinfo(elementtree, forbid_dtd, forbid_entities)
+ return elementtree
+
+
+def fromstring(text, parser=None, base_url=None, forbid_dtd=True, forbid_entities=True):
+ if parser is None:
+ parser = getDefaultParser()
+ rootelement = _etree.fromstring(text, parser, base_url=base_url)
+ elementtree = rootelement.getroottree()
+ check_docinfo(elementtree, forbid_dtd, forbid_entities)
+ return rootelement
+
+
+XML = fromstring
+
+
+def iterparse(*args, **kwargs):
+ raise NotSupportedError("iterparse not available")
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 2ccd3994..ea8cf1d3 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
from base64 import b64decode, b64encode
diff --git a/tests/src/OneLogin/saml2_tests/authn_request_test.py b/tests/src/OneLogin/saml2_tests/authn_request_test.py
index 3f1262f2..b23c633a 100644
--- a/tests/src/OneLogin/saml2_tests/authn_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/authn_request_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import json
diff --git a/tests/src/OneLogin/saml2_tests/error_test.py b/tests/src/OneLogin/saml2_tests/error_test.py
index cd7546b7..9a36a283 100644
--- a/tests/src/OneLogin/saml2_tests/error_test.py
+++ b/tests/src/OneLogin/saml2_tests/error_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import unittest
diff --git a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
index f4859244..4aa653b5 100644
--- a/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
+++ b/tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
try:
diff --git a/tests/src/OneLogin/saml2_tests/logout_request_test.py b/tests/src/OneLogin/saml2_tests/logout_request_test.py
index e1510c4d..c18eaee4 100644
--- a/tests/src/OneLogin/saml2_tests/logout_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_request_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import json
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index 90872f10..e6f31b0f 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import json
diff --git a/tests/src/OneLogin/saml2_tests/metadata_test.py b/tests/src/OneLogin/saml2_tests/metadata_test.py
index 58afb5b9..0a12a6d4 100644
--- a/tests/src/OneLogin/saml2_tests/metadata_test.py
+++ b/tests/src/OneLogin/saml2_tests/metadata_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import json
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 3ace583e..5110f6e7 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
from base64 import b64decode
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index ddf8c531..4cb271f5 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import json
diff --git a/tests/src/OneLogin/saml2_tests/signed_response_test.py b/tests/src/OneLogin/saml2_tests/signed_response_test.py
index 45afb505..b820e6c7 100644
--- a/tests/src/OneLogin/saml2_tests/signed_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/signed_response_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import json
diff --git a/tests/src/OneLogin/saml2_tests/utils_test.py b/tests/src/OneLogin/saml2_tests/utils_test.py
index c332a5e8..87112b0c 100644
--- a/tests/src/OneLogin/saml2_tests/utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/utils_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
from base64 import b64decode
@@ -8,13 +8,13 @@
from lxml import etree
from os.path import dirname, join, exists
import unittest
-from defusedxml.lxml import fromstring
from xml.dom.minidom import parseString
from onelogin.saml2 import compat
from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils
+from onelogin.saml2.xmlparser import fromstring
class OneLogin_Saml2_Utils_Test(unittest.TestCase):
diff --git a/tests/src/OneLogin/saml2_tests/xml_utils_test.py b/tests/src/OneLogin/saml2_tests/xml_utils_test.py
index 507575f1..c9e01a61 100644
--- a/tests/src/OneLogin/saml2_tests/xml_utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/xml_utils_test.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
+# Copyright (c) 2010-2021 OneLogin, Inc.
# MIT License
import json
From 3ccf9ab5a0bb2669afa30a8d6ee601f56c97fedc Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 14 Jan 2021 11:29:15 +0100
Subject: [PATCH 190/331] Release 1.10.0
---
changelog.md | 13 +++++++++++++
setup.py | 4 ++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/changelog.md b/changelog.md
index b5e5da52..d83c1b3b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,17 @@
# python3-saml changelog
+### 1.10.0 (Jan 14, 2021)
+* Added custom lxml parser based on the one defined at xmldefused. Parser will ignore comments and processing instructions and by default have deactivated huge_tree, DTD and access to external documents
+* Destination URL Comparison is now case-insensitive for netloc
+* Support single-label-domains as valid. New security parameter allowSingleLabelDomains
+* Added get_idp_sso_url, get_idp_slo_url and get_idp_slo_response_url methods to the Settings class and use it in the toolkit
+* [#212](https://github.com/onelogin/python3-saml/pull/212) Overridability enhancements. Made classes overridable by subclassing. Use of classmethods instead staticmethods
+* Add get_friendlyname_attributes support
+* Remove external lib method get_ext_lib_path. Add set_cert_path in order to allow set the cert path in a different folder than the toolkit
+* Add sha256 instead sha1 algorithm for sign/digest as recommended value on documentation and settings
+* [#178](https://github.com/onelogin/python3-saml/pull/178) Support for adding idp.crt from filesystem
+* Add samlUserdata to demo-flask session
+* Fix autoreloading in demo-tornado
+
### 1.9.0 (Nov 20, 2019)
* Allow any number of decimal places for seconds on SAML datetimes
* Fix failOnAuthnContextMismatch code
diff --git a/setup.py b/setup.py
index 3053b82c..5efa23f4 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.9.0',
+ version='1.10.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
@@ -38,7 +38,7 @@
install_requires=[
'isodate>=0.5.0',
'lxml>=3.3.5',
- 'xmlsec>=0.6.0',
+ 'xmlsec>=1.0.5',
'defusedxml==0.6.0'
],
dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'],
From 615bb73452ddd714827f74efbbb0d1a94292f989 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 14 Jan 2021 11:39:45 +0100
Subject: [PATCH 191/331] Add reference to python 3.8 and 3.9 support
---
.travis.yml | 2 ++
setup.py | 2 ++
2 files changed, 4 insertions(+)
diff --git a/.travis.yml b/.travis.yml
index 43d4b210..b690a1d0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,8 @@ python:
- '3.5'
- '3.6'
- '3.7'
+ - '3.8'
+ - '3.9'
matrix:
include:
diff --git a/setup.py b/setup.py
index 5efa23f4..f97f0189 100644
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,8 @@
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
],
author='OneLogin',
author_email='support@onelogin.com',
From d5304e09d7cf8980fddadc2fdb164e3b7d2d305a Mon Sep 17 00:00:00 2001
From: "Volm, David"
Date: Fri, 15 Jan 2021 11:04:51 -0600
Subject: [PATCH 192/331] Commented out X-Forwarded bit in `views.py`
---
demo_pyramid/demo_pyramid/views.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/demo_pyramid/demo_pyramid/views.py b/demo_pyramid/demo_pyramid/views.py
index a213e367..6529d67b 100644
--- a/demo_pyramid/demo_pyramid/views.py
+++ b/demo_pyramid/demo_pyramid/views.py
@@ -15,12 +15,13 @@ def init_saml_auth(req):
def prepare_pyramid_request(request):
- # If server is behind proxys or balancers use the HTTP_X_FORWARDED fields
-
- if 'X-Forwarded-Proto' in request.headers:
- request.scheme = request.headers['X-Forwarded-Proto']
- if 'X-Forwarded-Port' in request.headers:
- request.server_port = int(request.headers['X-Forwarded-Port'])
+ ## Uncomment this portion to set the request.scheme and request.server_port
+ ## based on the supplied `X-Forwarded` headers
+ #
+ # if 'X-Forwarded-Proto' in request.headers:
+ # request.scheme = request.headers['X-Forwarded-Proto']
+ # if 'X-Forwarded-Port' in request.headers:
+ # request.server_port = int(request.headers['X-Forwarded-Port'])
return {
'https': 'on' if request.scheme == 'https' else 'off',
From 3fa8f72fe70ec53fe2996cd58425247408407b51 Mon Sep 17 00:00:00 2001
From: "Volm, David"
Date: Fri, 15 Jan 2021 11:08:39 -0600
Subject: [PATCH 193/331] More description for X-Forwarded header in comments.
---
demo_pyramid/demo_pyramid/views.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/demo_pyramid/demo_pyramid/views.py b/demo_pyramid/demo_pyramid/views.py
index 6529d67b..93ec55eb 100644
--- a/demo_pyramid/demo_pyramid/views.py
+++ b/demo_pyramid/demo_pyramid/views.py
@@ -16,7 +16,8 @@ def init_saml_auth(req):
def prepare_pyramid_request(request):
## Uncomment this portion to set the request.scheme and request.server_port
- ## based on the supplied `X-Forwarded` headers
+ ## based on the supplied `X-Forwarded` headers.
+ ## Useful for running behind reverse proxies or balancers.
#
# if 'X-Forwarded-Proto' in request.headers:
# request.scheme = request.headers['X-Forwarded-Proto']
From 2db92842dc65a318ddd605443b3aa75a808813c9 Mon Sep 17 00:00:00 2001
From: nltommynl
Date: Tue, 26 Jan 2021 17:09:47 +0100
Subject: [PATCH 194/331] Update logout_request.py
fix for incorrect slo url.
---
src/onelogin/saml2/logout_request.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index caf0701f..3ed3fb15 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -109,7 +109,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
{
'id': self.id,
'issue_instant': issue_instant,
- 'single_logout_url': self.__settings.get_idp_slo_response_url(),
+ 'single_logout_url': self.__settings.get_idp_slo_url(),
'entity_id': sp_data['entityId'],
'name_id': name_id_obj,
'session_index': session_index_str,
From 982560576664175be7018cda8428b6731aabe31e Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 27 Jan 2021 11:45:47 +0100
Subject: [PATCH 195/331] Release 1.10.1
---
changelog.md | 3 +++
demo_pyramid/demo_pyramid/views.py | 6 +++---
setup.py | 6 +++---
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/changelog.md b/changelog.md
index d83c1b3b..4114cc04 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,7 @@
# python3-saml changelog
+### 1.10.1 (Jan 27, 2021)
+* Fix bug on LogoutRequest class, get_idp_slo_response_url was used instead get_idp_slo_url
+
### 1.10.0 (Jan 14, 2021)
* Added custom lxml parser based on the one defined at xmldefused. Parser will ignore comments and processing instructions and by default have deactivated huge_tree, DTD and access to external documents
* Destination URL Comparison is now case-insensitive for netloc
diff --git a/demo_pyramid/demo_pyramid/views.py b/demo_pyramid/demo_pyramid/views.py
index 93ec55eb..6dab4edc 100644
--- a/demo_pyramid/demo_pyramid/views.py
+++ b/demo_pyramid/demo_pyramid/views.py
@@ -15,9 +15,9 @@ def init_saml_auth(req):
def prepare_pyramid_request(request):
- ## Uncomment this portion to set the request.scheme and request.server_port
- ## based on the supplied `X-Forwarded` headers.
- ## Useful for running behind reverse proxies or balancers.
+ # Uncomment this portion to set the request.scheme and request.server_port
+ # based on the supplied `X-Forwarded` headers.
+ # Useful for running behind reverse proxies or balancers.
#
# if 'X-Forwarded-Proto' in request.headers:
# request.scheme = request.headers['X-Forwarded-Proto']
diff --git a/setup.py b/setup.py
index f97f0189..9537c8d4 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.10.0',
+ version='1.10.1',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
@@ -21,8 +21,8 @@
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
],
author='OneLogin',
author_email='support@onelogin.com',
From 4b6c4b1f2ed3f6eab70ff4391e595b808ace168c Mon Sep 17 00:00:00 2001
From: cdrx
Date: Wed, 27 Jan 2021 11:17:29 +0000
Subject: [PATCH 196/331] Remove the dependency on defusedxml
---
README.md | 1 -
setup.py | 3 +--
src/onelogin/saml2/xmlparser.py | 41 +++++++++++++++++++++++++++++++--
3 files changed, 40 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index c5528a2f..6d198601 100644
--- a/README.md
+++ b/README.md
@@ -92,7 +92,6 @@ Installation
* [xmlsec](https://pypi.python.org/pypi/xmlsec) Python bindings for the XML Security Library.
* [isodate](https://pypi.python.org/pypi/isodate) An ISO 8601 date/time/
duration parser and formatter
- * [defusedxml](https://pypi.python.org/pypi/defusedxml) XML bomb protection for Python stdlib modules
Review the ``setup.py`` file to know the version of the library that ``python3-saml`` is using
diff --git a/setup.py b/setup.py
index 9537c8d4..61ee6de2 100644
--- a/setup.py
+++ b/setup.py
@@ -40,8 +40,7 @@
install_requires=[
'isodate>=0.5.0',
'lxml>=3.3.5',
- 'xmlsec>=1.0.5',
- 'defusedxml==0.6.0'
+ 'xmlsec>=1.0.5'
],
dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'],
extras_require={
diff --git a/src/onelogin/saml2/xmlparser.py b/src/onelogin/saml2/xmlparser.py
index 33010ac5..4bf8df3d 100644
--- a/src/onelogin/saml2/xmlparser.py
+++ b/src/onelogin/saml2/xmlparser.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Based on the lxml example from defusedxml
+# DTDForbidden, EntitiesForbidden, NotSupportedError are clones of the classes defined at defusedxml
#
# Copyright (c) 2013 by Christian Heimes
# Licensed to PSF under a Contributor Agreement.
@@ -13,8 +14,6 @@
from lxml import etree as _etree
-from defusedxml.lxml import DTDForbidden, EntitiesForbidden, NotSupportedError
-
LXML3 = _etree.LXML_VERSION[0] >= 3
__origin__ = "lxml.etree"
@@ -22,6 +21,44 @@
tostring = _etree.tostring
+class DTDForbidden(ValueError):
+ """Document type definition is forbidden
+ """
+
+ def __init__(self, name, sysid, pubid):
+ super(DTDForbidden, self).__init__()
+ self.name = name
+ self.sysid = sysid
+ self.pubid = pubid
+
+ def __str__(self):
+ tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})"
+ return tpl.format(self.name, self.sysid, self.pubid)
+
+
+class EntitiesForbidden(ValueError):
+ """Entity definition is forbidden
+ """
+
+ def __init__(self, name, value, base, sysid, pubid, notation_name):
+ super(EntitiesForbidden, self).__init__()
+ self.name = name
+ self.value = value
+ self.base = base
+ self.sysid = sysid
+ self.pubid = pubid
+ self.notation_name = notation_name
+
+ def __str__(self):
+ tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})"
+ return tpl.format(self.name, self.sysid, self.pubid)
+
+
+class NotSupportedError(ValueError):
+ """The operation is not supported
+ """
+
+
class RestrictedElement(_etree.ElementBase):
"""A restricted Element class that filters out instances of some classes
"""
From c735bfb52bb475fff3691c8cd59646ba289cbdf5 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 28 Jan 2021 01:02:23 +0100
Subject: [PATCH 197/331] Update dev dependency due lack of python support 3.8
and 3.9
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 61ee6de2..452f1b9e 100644
--- a/setup.py
+++ b/setup.py
@@ -46,7 +46,7 @@
extras_require={
'test': (
'coverage>=4.5.2',
- 'freezegun==0.3.11',
+ 'freezegun>=0.3.11, <=1.1.0',
'pylint==1.9.4',
'flake8==3.6.0',
'coveralls==1.5.1',
From 22734cd4d2c51b37b128fbfabc422aad48174563 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 28 Jan 2021 10:37:56 +0100
Subject: [PATCH 198/331] Update dev dependency due lack of python support 3.8
(flake8)
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 452f1b9e..a999c1fa 100644
--- a/setup.py
+++ b/setup.py
@@ -48,7 +48,7 @@
'coverage>=4.5.2',
'freezegun>=0.3.11, <=1.1.0',
'pylint==1.9.4',
- 'flake8==3.6.0',
+ 'flake8>=3.6.0',
'coveralls==1.5.1',
),
},
From be1c1c8ae7d472518acbcfb0fcf2b48a9c5a7365 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Thu, 28 Jan 2021 13:41:51 +0100
Subject: [PATCH 199/331] Fix pep8
---
demo-django/demo/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index c02d226e..418ab0c8 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -87,7 +87,7 @@ def index(request):
if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))
elif auth.get_settings().is_debug_active():
- error_reason = auth.get_last_error_reason()
+ error_reason = auth.get_last_error_reason()
elif 'sls' in req['get_data']:
request_id = None
if 'LogoutRequestID' in request.session:
From 2f78c44c107024b9c0e982c7d5e986533a1efd87 Mon Sep 17 00:00:00 2001
From: Alexander Schrijver
Date: Wed, 13 May 2020 16:45:07 +0200
Subject: [PATCH 200/331] Add the abilitity to change the
AttributeConsumingService index in the metadata file.
---
src/onelogin/saml2/metadata.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py
index 89e0af8f..bd839c58 100644
--- a/src/onelogin/saml2/metadata.py
+++ b/src/onelogin/saml2/metadata.py
@@ -162,7 +162,7 @@ def builder(cls, sp, authnsign=False, wsign=False, valid_until=None, cache_durat
requested_attribute_data.append(requested_attribute)
- str_attribute_consuming_service = """
+ str_attribute_consuming_service = """
%(service_name)s
%(attr_cs_desc)s%(requested_attribute_str)s
@@ -170,6 +170,7 @@ def builder(cls, sp, authnsign=False, wsign=False, valid_until=None, cache_durat
{
'service_name': sp['attributeConsumingService']['serviceName'],
'attr_cs_desc': attr_cs_desc_str,
+ 'attribute_consuming_service_index': sp['attributeConsumingService'].get('index', '1'),
'requested_attribute_str': '\n'.join(requested_attribute_data)
}
From 7ed8932d36d550f6c994a0abf6fb2abf53035501 Mon Sep 17 00:00:00 2001
From: Alexander Schrijver
Date: Mon, 11 May 2020 11:16:58 +0200
Subject: [PATCH 201/331] Add the ability to set AttributeConsumingServiceIndex
in the authn request.
---
src/onelogin/saml2/authn_request.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index 48ad9d2a..4af455d2 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -108,7 +108,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
attr_consuming_service_str = ''
if 'attributeConsumingService' in sp_data and sp_data['attributeConsumingService']:
- attr_consuming_service_str = "\n AttributeConsumingServiceIndex=\"1\""
+ attr_consuming_service_str = "\n AttributeConsumingServiceIndex=\"%s\"" % sp_data['attributeConsumingService'].get('index', '1')
request = OneLogin_Saml2_Templates.AUTHN_REQUEST % \
{
From 332ea51b92fe6240855a4113fa0fd37da81b77ce Mon Sep 17 00:00:00 2001
From: Alexander Schrijver
Date: Fri, 29 Jan 2021 16:19:09 +0100
Subject: [PATCH 202/331] Add a comment describing the
attributeConsumingService 'index' configuration option.
---
README.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/README.md b/README.md
index c5528a2f..1001c4b8 100644
--- a/README.md
+++ b/README.md
@@ -257,6 +257,11 @@ This is the ``settings.json`` file:
// attributeConsumingService. nameFormat, attributeValue and
// friendlyName can be ommited
"attributeConsumingService": {
+ // OPTIONAL: only specifiy if SP requires this.
+ // index is an integer which identifies the attributeConsumingService used
+ // to the SP. OneLogin toolkit supports configuring only one attributeConsumingService
+ // but in certain cases the SP requires a different value. Defaults to '1'.
+ // "index": '1',
"serviceName": "SP test",
"serviceDescription": "Test Service",
"requestedAttributes": [
From 7d4351811efa2968ca5048e29701e67a99d090fe Mon Sep 17 00:00:00 2001
From: Alexander Schrijver
Date: Fri, 5 Feb 2021 14:22:24 +0100
Subject: [PATCH 203/331] Move storing the response data into its own method in
the Auth class
This makes it easier to incorporate other response methods into the
code.
---
src/onelogin/saml2/auth.py | 30 ++++++++++++++++--------------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index a67e2071..33de8724 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -94,6 +94,21 @@ def set_strict(self, value):
assert isinstance(value, bool)
self.__settings.set_strict(value)
+ def store_valid_response(self, response):
+ self.__attributes = response.get_attributes()
+ self.__friendlyname_attributes = response.get_friendlyname_attributes()
+ self.__nameid = response.get_nameid()
+ self.__nameid_format = response.get_nameid_format()
+ self.__nameid_nq = response.get_nameid_nq()
+ self.__nameid_spnq = response.get_nameid_spnq()
+ self.__session_index = response.get_session_index()
+ self.__session_expiration = response.get_session_not_on_or_after()
+ self.__last_message_id = response.get_id()
+ self.__last_assertion_id = response.get_assertion_id()
+ self.__last_authn_contexts = response.get_authn_contexts()
+ self.__authenticated = True
+ self.__last_assertion_not_on_or_after = response.get_assertion_not_on_or_after()
+
def process_response(self, request_id=None):
"""
Process the SAML Response sent by the IdP.
@@ -112,20 +127,7 @@ def process_response(self, request_id=None):
self.__last_response = response.get_xml_document()
if response.is_valid(self.__request_data, request_id):
- self.__attributes = response.get_attributes()
- self.__friendlyname_attributes = response.get_friendlyname_attributes()
- self.__nameid = response.get_nameid()
- self.__nameid_format = response.get_nameid_format()
- self.__nameid_nq = response.get_nameid_nq()
- self.__nameid_spnq = response.get_nameid_spnq()
- self.__session_index = response.get_session_index()
- self.__session_expiration = response.get_session_not_on_or_after()
- self.__last_message_id = response.get_id()
- self.__last_assertion_id = response.get_assertion_id()
- self.__last_authn_contexts = response.get_authn_contexts()
- self.__authenticated = True
- self.__last_assertion_not_on_or_after = response.get_assertion_not_on_or_after()
-
+ self.store_valid_response(response)
else:
self.__errors.append('invalid_response')
self.__error_reason = response.get_error()
From ded055793d7a6ab7dbe0fc74b67a70a6a88bcbb7 Mon Sep 17 00:00:00 2001
From: John-Scott Atlakson <24574+jsma@users.noreply.github.com>
Date: Tue, 9 Feb 2021 14:57:40 -0800
Subject: [PATCH 204/331] Update README.md
Update self-sign certificate example to use ``sp.key`` since that is the hard-coded filename that must be used.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6d198601..622db674 100644
--- a/README.md
+++ b/README.md
@@ -162,7 +162,7 @@ publish that X.509 certificate on Service Provider metadata.
If you want to create self-signed certs, you can do it at the https://www.samltool.com/self_signed_certs.php service, or using the command:
```bash
-openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout saml.key
+openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout sp.key
```
#### demo-flask ####
From 9cc63eb2aae97e9c82d11cc2229c37c5cc39f2b1 Mon Sep 17 00:00:00 2001
From: Alexander Schrijver
Date: Mon, 11 May 2020 11:18:11 +0200
Subject: [PATCH 205/331] Add the ability to change the ProtocolBinding in the
authn request.
---
src/onelogin/saml2/authn_request.py | 1 +
src/onelogin/saml2/xml_templates.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index 48ad9d2a..c0ced249 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -124,6 +124,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
'nameid_policy_str': nameid_policy_str,
'requested_authn_context_str': requested_authn_context_str,
'attr_consuming_service_str': attr_consuming_service_str,
+ 'acs_binding': sp_data['assertionConsumerService'].get('binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST')
}
self.__authn_request = request
diff --git a/src/onelogin/saml2/xml_templates.py b/src/onelogin/saml2/xml_templates.py
index 306b1afe..5575d116 100644
--- a/src/onelogin/saml2/xml_templates.py
+++ b/src/onelogin/saml2/xml_templates.py
@@ -27,7 +27,7 @@ class OneLogin_Saml2_Templates(object):
Version="2.0"%(provider_name)s%(force_authn_str)s%(is_passive_str)s
IssueInstant="%(issue_instant)s"
Destination="%(destination)s"
- ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ ProtocolBinding="%(acs_binding)s"
AssertionConsumerServiceURL="%(assertion_url)s"%(attr_consuming_service_str)s>
%(entity_id)s %(subject_str)s%(nameid_policy_str)s
%(requested_authn_context_str)s
From 88f66a858044c9818ad60368320ebd0ba677917f Mon Sep 17 00:00:00 2001
From: sheng zhang
Date: Wed, 24 Feb 2021 14:45:03 -0800
Subject: [PATCH 206/331] Update README.md
for issue https://github.com/onelogin/python3-saml/issues/249
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 8a524de8..dc854bbb 100644
--- a/README.md
+++ b/README.md
@@ -689,7 +689,7 @@ if not errors:
else:
print('Not authenticated')
else:
- print("Error when processing SAML Response: %s" % (', '.join(errors)))
+ print("Error when processing SAML Response: %s %s" % (', '.join(errors), auth.get_last_error_reason()))
```
The SAML response is processed and then checked that there are no errors. It also verifies that the user is authenticated and stored the userdata in session.
@@ -748,7 +748,7 @@ if len(errors) == 0:
else:
print("Sucessfully Logged out")
else:
- print("Error when processing SLO: %s" % (', '.join(errors)))
+ print("Error when processing SLO: %s %s" % (', '.join(errors), auth.get_last_error_reason()))
```
If the SLS endpoints receives a Logout Response, the response is validated and the session could be closed, using the callback.
From 1fbb515ba34255a684ac29125fdc38030446746a Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Mon, 10 May 2021 16:12:19 -0400
Subject: [PATCH 207/331] Added timeout kwarg to metadata retrieval
---
README.md | 8 +++++++-
src/onelogin/saml2/idp_metadata_parser.py | 18 ++++++++++++------
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index dc854bbb..614e6e21 100644
--- a/README.md
+++ b/README.md
@@ -520,12 +520,18 @@ The method above requires a little extra work to manually specify attributes abo
There's an easier method -- use a metadata exchange. Metadata is just an XML file that defines the capabilities of both the IdP and the SP application. It also contains the X.509 public key certificates which add to the trusted relationship. The IdP administrator can also configure custom settings for an SP based on the metadata.
-Using ````parse_remote```` IdP metadata can be obtained and added to the settings withouth further ado.
+Using ````parse_remote```` IdP metadata can be obtained and added to the settings without further ado.
``
idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata')
``
+You can specify a timeout in seconds for metadata retrieval, if not it is not guaranteed that the request will complete
+
+``
+idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata', timeout=5)
+``
+
If the Metadata contains several entities, the relevant ``EntityDescriptor`` can be specified when retrieving the settings from the ``IdpMetadataParser`` by its ``entityId`` value:
``idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(https://example.com/metadatas, entity_id='idp_entity_id')``
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 2d7eec2c..5da7989a 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -26,7 +26,7 @@ class OneLogin_Saml2_IdPMetadataParser(object):
"""
@classmethod
- def get_metadata(cls, url, validate_cert=True):
+ def get_metadata(cls, url, validate_cert=True, timeout=None):
"""
Gets the metadata XML from the provided URL
:param url: Url where the XML of the Identity Provider Metadata is published.
@@ -35,18 +35,21 @@ def get_metadata(cls, url, validate_cert=True):
:param validate_cert: If the url uses https schema, that flag enables or not the verification of the associated certificate.
:type validate_cert: bool
+ :param timeout: Timeout in seconds to wait for metadata response
+ :type timeout: int
+
:returns: metadata XML
:rtype: string
"""
valid = False
if validate_cert:
- response = urllib2.urlopen(url)
+ response = urllib2.urlopen(url, timeout=timeout)
else:
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
- response = urllib2.urlopen(url, context=ctx)
+ response = urllib2.urlopen(url, context=ctx, timeout=timeout)
xml = response.read()
if xml:
@@ -59,12 +62,12 @@ def get_metadata(cls, url, validate_cert=True):
pass
if not valid:
- raise Exception('Not valid IdP XML found from URL: %s' % (url))
+ raise Exception('Not valid IdP XML found from URL: %s' % (url,))
return xml
@classmethod
- def parse_remote(cls, url, validate_cert=True, entity_id=None, **kwargs):
+ def parse_remote(cls, url, validate_cert=True, entity_id=None, timeout=None, **kwargs):
"""
Gets the metadata XML from the provided URL and parse it, returning a dict with extracted data
:param url: Url where the XML of the Identity Provider Metadata is published.
@@ -77,10 +80,13 @@ def parse_remote(cls, url, validate_cert=True, entity_id=None, **kwargs):
that contains multiple EntityDescriptor.
:type entity_id: string
+ :param timeout: Timeout in seconds to wait for metadata response
+ :type timeout: int
+
:returns: settings dict with extracted data
:rtype: dict
"""
- idp_metadata = cls.get_metadata(url, validate_cert)
+ idp_metadata = cls.get_metadata(url, validate_cert, timeout)
return cls.parse(idp_metadata, entity_id=entity_id, **kwargs)
@classmethod
From b14e438cdb5bf51be6df34d3f686598178da1d96 Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Mon, 10 May 2021 16:25:20 -0400
Subject: [PATCH 208/331] Fixed readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 614e6e21..c48f2c55 100644
--- a/README.md
+++ b/README.md
@@ -526,7 +526,7 @@ Using ````parse_remote```` IdP metadata can be obtained and added to the setting
idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata')
``
-You can specify a timeout in seconds for metadata retrieval, if not it is not guaranteed that the request will complete
+You can specify a timeout in seconds for metadata retrieval, without it is not guaranteed that the request will complete
``
idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata', timeout=5)
From 293bcde73b12b7374f120752680c05a399b76774 Mon Sep 17 00:00:00 2001
From: Eugene Toder
Date: Sat, 27 Mar 2021 17:41:59 -0400
Subject: [PATCH 209/331] Add an option to use query string for validation
When validating request or response signature in process_slo() we
currently rebuild query string from 'get_data' elements. This requires
URL encoding components of the string. Unfortunately, some IdPs (Azure
AD, ADFS) use lower-case encoding. To handle this, one needs to pass
lowercase_urlencoding=True. This complicates code that needs to support
different IdPs.
Instead, if 'query_string' is passed, take parts from it directly. This
avoids the need to URL encode them. This is similar to the
`retrieveParametersFromServer` argument in the PHP version.
This feature is disabled by default. Pass validate_signature_from_qs=True
to enable it.
---
README.md | 11 ++++++-----
src/onelogin/saml2/auth.py | 36 ++++++++++++++++++++++++++----------
2 files changed, 32 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index dc854bbb..bb67791d 100644
--- a/README.md
+++ b/README.md
@@ -562,9 +562,10 @@ req = {
# Advanced request options
"https": "",
- "lowercase_urlencoding": "",
"request_uri": "",
- "query_string": ""
+ "query_string": "",
+ "validate_signature_from_qs": False,
+ "lowercase_urlencoding": False
}
```
@@ -596,12 +597,12 @@ An explanation of some advanced request parameters:
* `https` - Defaults to ``off``. Set this to ``on`` if you receive responses over HTTPS.
-* `lowercase_urlencoding` - Defaults to `false`. ADFS users should set this to `true`.
-
-* `request_uri` - The path where your SAML server recieves requests. Set this if requests are not recieved at the server's root.
+* `request_uri` - The path where your SAML server receives requests. Set this if requests are not received at the server's root.
* `query_string` - Set this with additional query parameters that should be passed to the request endpoint.
+* `validate_signature_from_qs` - If `True`, use `query_string` to validate request and response signatures. Otherwise, use `get_data`. Defaults to `False`. Note that when using `get_data`, query parameters need to be url-encoded for validation. By default we use upper-case url-encoding. Some IdPs, notably Microsoft AD, use lower-case url-encoding, which makes signature validation to fail. To fix this issue, either pass `query_string` and set `validate_signature_from_qs` to `True`, which works for all IdPs, or set `lowercase_urlencoding` to `True`, which only works for AD.
+
#### Initiate SSO ####
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 33de8724..cfaee5fd 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -528,6 +528,22 @@ def add_response_signature(self, response_data, sign_algorithm=OneLogin_Saml2_Co
"""
return self.__build_signature(response_data, 'SAMLResponse', sign_algorithm)
+ @staticmethod
+ def __build_sign_query_from_qs(query_string, saml_type):
+ """
+ Build sign query from query string
+
+ :param query_string: The query string
+ :type query_string: str
+
+ :param saml_type: The target URL the user should be redirected to
+ :type saml_type: string SAMLRequest | SAMLResponse
+ """
+ args = ('%s=' % saml_type, 'RelayState=', 'SigAlg=')
+ parts = query_string.split('&')
+ # Join in the order of arguments rather than the original order of parts.
+ return '&'.join(part for arg in args for part in parts if part.startswith(arg))
+
@staticmethod
def __build_sign_query(saml_data, relay_state, algorithm, saml_type, lowercase_urlencoding=False):
"""
@@ -660,16 +676,16 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
if isinstance(sign_alg, bytes):
sign_alg = sign_alg.decode('utf8')
- lowercase_urlencoding = False
- if 'lowercase_urlencoding' in self.__request_data.keys():
- lowercase_urlencoding = self.__request_data['lowercase_urlencoding']
-
- signed_query = self.__build_sign_query(data[saml_type],
- data.get('RelayState', None),
- sign_alg,
- saml_type,
- lowercase_urlencoding
- )
+ query_string = self.__request_data.get('query_string')
+ if query_string and self.__request_data.get('validate_signature_from_qs'):
+ signed_query = self.__build_sign_query_from_qs(query_string, saml_type)
+ else:
+ lowercase_urlencoding = self.__request_data.get('lowercase_urlencoding', False)
+ signed_query = self.__build_sign_query(data[saml_type],
+ data.get('RelayState'),
+ sign_alg,
+ saml_type,
+ lowercase_urlencoding)
if exists_multix509sign:
for cert in idp_data['x509certMulti']['signing']:
From 11cc4f57870a3ffe8109956285834fe47222305b Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Mon, 10 May 2021 23:56:49 -0400
Subject: [PATCH 210/331] Removed comma
---
src/onelogin/saml2/idp_metadata_parser.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 5da7989a..279d80c5 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -62,7 +62,7 @@ def get_metadata(cls, url, validate_cert=True, timeout=None):
pass
if not valid:
- raise Exception('Not valid IdP XML found from URL: %s' % (url,))
+ raise Exception('Not valid IdP XML found from URL: %s' % (url))
return xml
From b04065170aac60d81920196c85dee843ad779d64 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 14 May 2021 04:32:59 +0200
Subject: [PATCH 211/331] Create Makefile. Create python-package.yml
---
.github/workflows/python-package.yml | 35 ++++++++++++++++++++++++++++
.gitignore | 2 ++
Makefile | 35 ++++++++++++++++++++++++++++
setup.py | 3 ++-
4 files changed, 74 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/python-package.yml
create mode 100644 Makefile
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
new file mode 100644
index 00000000..ac505be2
--- /dev/null
+++ b/.github/workflows/python-package.yml
@@ -0,0 +1,35 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Python package
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: [2.7,3.5,3.6,3.7, 3.8,3.9]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install -qq swig python-dev libxml2-dev libxmlsec1-dev
+ make install-req
+ make install-test
+
+ - name: Test
+ run: |
+ make pytest
+ make pycodestyle
+ make flake8
+
diff --git a/.gitignore b/.gitignore
index c16b943e..d449f63f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ __pycache_
/*.eg
*.egg-info
/eggs
+/.eggs
/build
/dist
/venv
@@ -21,6 +22,7 @@ __pycache_
.pypirc
/.idea
.mypy_cache/
+.pytest_cache
*.key
*.crt
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..7f3faddb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+PIP=pip
+FLAKE8=flake8
+PYTEST=pytest
+PYCODESTYLE=pycodestyle
+COVERAGE=coverage
+COVERAGE_CONFIG=tests/coverage.rc
+PEP8_CONFIG=tests/pep8.rc
+MAIN_SOURCE=src/onelogin/saml2
+DEMOS=demo-django demo-flask
+TESTS=tests/src/OneLogin/saml2_tests
+SOURCES=$(MAIN_SOURCE) $(DEMO) $(TESTS)
+
+install-req:
+ $(PIP) install --upgrade 'setuptools<45.0.0'
+ $(PIP) install .
+
+install-test:
+ $(PIP) install -e ".[test]"
+
+pytest:
+ $(COVERAGE) run --source $(MAIN_SOURCE) --rcfile=$(COVERAGE_CONFIG) -m pytest
+ $(COVERAGE) report -m --rcfile=$(COVERAGE_CONFIG)
+
+pycodestyle:
+ $(PYCODESTYLE) --ignore=E501,E731,W504 $(SOURCES) --config=$(PEP8_CONFIG)
+
+flake8:
+ $(FLAKE8) --ignore=E501,E731,W504 $(SOURCES)
+
+clean:
+ rm -rf .pytest_cache/
+ rm -rf .eggs/
+ find . -type d -name "__pycache__" -exec rm -r {} +
+ find . -type d -name "*.egg-info" -exec rm -r {} +
+ rm .coverage
diff --git a/setup.py b/setup.py
index a999c1fa..cff7b7c7 100644
--- a/setup.py
+++ b/setup.py
@@ -49,7 +49,8 @@
'freezegun>=0.3.11, <=1.1.0',
'pylint==1.9.4',
'flake8>=3.6.0',
- 'coveralls==1.5.1',
+ 'coveralls>=1.11.1',
+ 'pytest>=4.6',
),
},
keywords='saml saml2 xmlsec django flask pyramid python3',
From 277b642da278e484b0b983eea38ee1051feff13e Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Wed, 26 May 2021 16:31:33 +0200
Subject: [PATCH 212/331] Add warning about the use of
OneLogin_Saml2_IdPMetadataParser class
---
README.md | 7 +++++++
src/onelogin/saml2/idp_metadata_parser.py | 3 +++
2 files changed, 10 insertions(+)
diff --git a/README.md b/README.md
index 56f279a5..752fcd43 100644
--- a/README.md
+++ b/README.md
@@ -522,6 +522,13 @@ There's an easier method -- use a metadata exchange. Metadata is just an XML fi
Using ````parse_remote```` IdP metadata can be obtained and added to the settings without further ado.
+Take in mind that the OneLogin_Saml2_IdPMetadataParser class does not validate in any way the URL that is introduced in order to be parsed.
+
+Usually the same administrator that handles the Service Provider also sets the URL to the IdP, which should be a trusted resource.
+
+But there are other scenarios, like a SAAS app where the administrator of the app delegates this functionality to other users. In this case, extra precaution should be taken in order to validate such URL inputs and avoid attacks like SSRF.
+
+
``
idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://example.com/auth/saml2/idp/metadata')
``
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index 279d80c5..e341aebc 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -23,6 +23,9 @@
class OneLogin_Saml2_IdPMetadataParser(object):
"""
A class that contain methods related to obtaining and parsing metadata from IdP
+
+ This class does not validate in any way the URL that is introduced,
+ make sure to validate it properly before use it in a get_metadata method.
"""
@classmethod
From 5cef5089bac191a2412e66e3ac8e9cd51c63c97a Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Fri, 4 Jun 2021 11:13:32 -0400
Subject: [PATCH 213/331] Fixed friendlyname
---
src/onelogin/saml2/response.py | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 04264919..df0f85bb 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -613,12 +613,6 @@ def get_friendlyname_attributes(self):
for attribute_node in attribute_nodes:
attr_friendlyname = attribute_node.get('FriendlyName')
if attr_friendlyname:
- if attr_friendlyname in attributes.keys():
- raise OneLogin_Saml2_ValidationError(
- 'Found an Attribute element with duplicated FriendlyName',
- OneLogin_Saml2_ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND
- )
-
values = []
for attr in attribute_node.iterchildren('{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP['saml']):
attr_text = OneLogin_Saml2_XML.element_text(attr)
@@ -636,7 +630,10 @@ def get_friendlyname_attributes(self):
'value': nameid.text
}
})
- attributes[attr_friendlyname] = values
+ if attr_friendlyname in attributes:
+ attributes[attr_friendlyname].extend(values)
+ else:
+ attributes[attr_friendlyname] = values
return attributes
def validate_num_assertions(self):
From f88368aa11d8f9fa31b768557e3156e2679dc302 Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Fri, 4 Jun 2021 11:46:43 -0400
Subject: [PATCH 214/331] Added duplicate friendlyname attribute test
---
.../response1_with_duplicate_friendlynames.xml.base64 | 1 +
tests/src/OneLogin/saml2_tests/response_test.py | 10 ++++++++++
2 files changed, 11 insertions(+)
create mode 100644 tests/data/responses/response1_with_duplicate_friendlynames.xml.base64
diff --git a/tests/data/responses/response1_with_duplicate_friendlynames.xml.base64 b/tests/data/responses/response1_with_duplicate_friendlynames.xml.base64
new file mode 100644
index 00000000..18871d35
--- /dev/null
+++ b/tests/data/responses/response1_with_duplicate_friendlynames.xml.base64
@@ -0,0 +1 @@
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElEPSJwZng4ZmZiMzk4My1jYmY2LTkyYTEtZjJjNC02MTlhZTFiZTFjODYiIElzc3VlSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbC9tZXRhZGF0YS8xMzU5MDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+PGRzOlJlZmVyZW5jZSBVUkk9IiNwZng4ZmZiMzk4My1jYmY2LTkyYTEtZjJjNC02MTlhZTFiZTFjODYiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmhndVFiQ0hhbmliYkRDN3EzWnp4ekhjUG9uST08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+R2FuY0Q5dlJvaDlNYlQwMDJEeTc5dDFtNkk2WWZoVUtQZmJsa21wMnVkb2xYdWp2NmUxTVd2c1ZteE56dHNJR2x4QWEwcUtEaVNNekNORFpzazNqc3lzVWwxbkFLbkFnMTg1amZYanN6aHNkbVIrTTkxZHhrNmtmY0xVb3NPb2xvdmFkV0xQV3FuN1AzajgvNXh6cDlMcFJBM2d2QjQxODJSU2lyV0NCWFBRPTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c3VwcG9ydEBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIiBSZWNpcGllbnQ9IntyZWNpcGllbnR9Ii8+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+PC9zYW1sOlN1YmplY3Q+PHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMTEtMThUMjE6NTI6MzdaIiBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnthdWRpZW5jZX08L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOVQyMTo1NzozN1oiIFNlc3Npb25JbmRleD0iXzUzMWMzMmQyODNiZGZmN2UwNGU0ODdiY2RiYzRkZDhkIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiIEZyaWVuZGx5TmFtZT0idXNlcm5hbWUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJmcmllbmRseTEiIEZyaWVuZGx5TmFtZT0iZnJpZW5kbHl0ZXN0Ij48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmZyaWVuZGx5MTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJmcmllbmRseTIiIEZyaWVuZGx5TmFtZT0iZnJpZW5kbHl0ZXN0Ij48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmZyaWVuZGx5Mjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhbm90aGVyX3ZhbHVlIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnZhbHVlPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 5110f6e7..711cc5d6 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -701,6 +701,7 @@ def testGetFriendlyAttributes(self):
Tests the get_friendlyname_attributes method of the OneLogin_Saml2_Response
"""
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+
xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
response = OneLogin_Saml2_Response(settings, xml)
self.assertEqual({}, response.get_friendlyname_attributes())
@@ -720,6 +721,15 @@ def testGetFriendlyAttributes(self):
response_4 = OneLogin_Saml2_Response(settings, xml_4)
self.assertEqual({}, response_4.get_friendlyname_attributes())
+ expected_attributes = {
+ 'username': ['demo'],
+ 'friendlytest': ['friendly1', 'friendly2']
+ }
+ xml_5 = self.file_contents(join(self.data_path, 'responses',
+ 'response1_with_duplicate_friendlynames.xml.base64'))
+ response_5 = OneLogin_Saml2_Response(settings, xml_5)
+ self.assertEqual(expected_attributes, response_5.get_friendlyname_attributes())
+
def testGetNestedNameIDAttributes(self):
"""
Tests the getAttributes method of the OneLogin_Saml2_Response with nested
From da3d8967cceb30e9701881e6b8d2ef51cea3423c Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Fri, 4 Jun 2021 11:52:19 -0400
Subject: [PATCH 215/331] Fixed PEP8 error
---
src/onelogin/saml2/idp_metadata_parser.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/idp_metadata_parser.py b/src/onelogin/saml2/idp_metadata_parser.py
index e341aebc..1b94e1a8 100644
--- a/src/onelogin/saml2/idp_metadata_parser.py
+++ b/src/onelogin/saml2/idp_metadata_parser.py
@@ -23,7 +23,7 @@
class OneLogin_Saml2_IdPMetadataParser(object):
"""
A class that contain methods related to obtaining and parsing metadata from IdP
-
+
This class does not validate in any way the URL that is introduced,
make sure to validate it properly before use it in a get_metadata method.
"""
From 06b443b01e89086fdcf7dc3cab42e41ee25b7b0a Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Mon, 7 Jun 2021 13:42:04 -0400
Subject: [PATCH 216/331] Added setting to settings file, updated tests
---
src/onelogin/saml2/response.py | 50 ++++++-------------
src/onelogin/saml2/settings.py | 3 ++
...nse1_with_duplicate_attributes.xml.base64} | 2 +-
tests/settings/settings11.json | 48 ++++++++++++++++++
.../src/OneLogin/saml2_tests/response_test.py | 35 +++++++++++--
5 files changed, 99 insertions(+), 39 deletions(-)
rename tests/data/responses/{response1_with_duplicate_friendlynames.xml.base64 => response1_with_duplicate_attributes.xml.base64} (89%)
create mode 100644 tests/settings/settings11.json
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index df0f85bb..c5ca4b86 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -573,46 +573,28 @@ def get_attributes(self):
Gets the Attributes from the AttributeStatement element.
EncryptedAttributes are not supported
"""
- attributes = {}
- attribute_nodes = self.__query_assertion('/saml:AttributeStatement/saml:Attribute')
- for attribute_node in attribute_nodes:
- attr_name = attribute_node.get('Name')
- if attr_name in attributes.keys():
- raise OneLogin_Saml2_ValidationError(
- 'Found an Attribute element with duplicated Name',
- OneLogin_Saml2_ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND
- )
-
- values = []
- for attr in attribute_node.iterchildren('{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP['saml']):
- attr_text = OneLogin_Saml2_XML.element_text(attr)
- if attr_text:
- attr_text = attr_text.strip()
- if attr_text:
- values.append(attr_text)
-
- # Parse any nested NameID children
- for nameid in attr.iterchildren('{%s}NameID' % OneLogin_Saml2_Constants.NSMAP['saml']):
- values.append({
- 'NameID': {
- 'Format': nameid.get('Format'),
- 'NameQualifier': nameid.get('NameQualifier'),
- 'value': nameid.text
- }
- })
- attributes[attr_name] = values
- return attributes
+ return self._get_attributes('Name')
def get_friendlyname_attributes(self):
"""
Gets the Attributes from the AttributeStatement element indexed by FiendlyName.
EncryptedAttributes are not supported
"""
+ return self._get_attributes('FriendlyName')
+
+ def _get_attributes(self, attr_name):
+ allow_duplicates = self.__settings.get_security_data().get('allowRepeatAttributeName', False)
attributes = {}
attribute_nodes = self.__query_assertion('/saml:AttributeStatement/saml:Attribute')
for attribute_node in attribute_nodes:
- attr_friendlyname = attribute_node.get('FriendlyName')
- if attr_friendlyname:
+ attr_key = attribute_node.get(attr_name)
+ if attr_key:
+ if not allow_duplicates and attr_key in attributes:
+ raise OneLogin_Saml2_ValidationError(
+ f'Found an Attribute element with duplicated {attr_name}',
+ OneLogin_Saml2_ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND
+ )
+
values = []
for attr in attribute_node.iterchildren('{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP['saml']):
attr_text = OneLogin_Saml2_XML.element_text(attr)
@@ -630,10 +612,10 @@ def get_friendlyname_attributes(self):
'value': nameid.text
}
})
- if attr_friendlyname in attributes:
- attributes[attr_friendlyname].extend(values)
+ if attr_key in attributes:
+ attributes[attr_key].extend(values)
else:
- attributes[attr_friendlyname] = values
+ attributes[attr_key] = values
return attributes
def validate_num_assertions(self):
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index ab3dbe37..38e79041 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -315,6 +315,9 @@ def __add_default_values(self):
# AttributeStatement required by default
self.__security.setdefault('wantAttributeStatement', True)
+ # Disallow duplicate attribute names by default
+ self.__security.setdefault('allowRepeatAttributeName', False)
+
self.__idp.setdefault('x509cert', '')
self.__idp.setdefault('certFingerprint', '')
self.__idp.setdefault('certFingerprintAlgorithm', 'sha1')
diff --git a/tests/data/responses/response1_with_duplicate_friendlynames.xml.base64 b/tests/data/responses/response1_with_duplicate_attributes.xml.base64
similarity index 89%
rename from tests/data/responses/response1_with_duplicate_friendlynames.xml.base64
rename to tests/data/responses/response1_with_duplicate_attributes.xml.base64
index 18871d35..de113c3c 100644
--- a/tests/data/responses/response1_with_duplicate_friendlynames.xml.base64
+++ b/tests/data/responses/response1_with_duplicate_attributes.xml.base64
@@ -1 +1 @@
-PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElEPSJwZng4ZmZiMzk4My1jYmY2LTkyYTEtZjJjNC02MTlhZTFiZTFjODYiIElzc3VlSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbC9tZXRhZGF0YS8xMzU5MDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+PGRzOlJlZmVyZW5jZSBVUkk9IiNwZng4ZmZiMzk4My1jYmY2LTkyYTEtZjJjNC02MTlhZTFiZTFjODYiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmhndVFiQ0hhbmliYkRDN3EzWnp4ekhjUG9uST08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+R2FuY0Q5dlJvaDlNYlQwMDJEeTc5dDFtNkk2WWZoVUtQZmJsa21wMnVkb2xYdWp2NmUxTVd2c1ZteE56dHNJR2x4QWEwcUtEaVNNekNORFpzazNqc3lzVWwxbkFLbkFnMTg1amZYanN6aHNkbVIrTTkxZHhrNmtmY0xVb3NPb2xvdmFkV0xQV3FuN1AzajgvNXh6cDlMcFJBM2d2QjQxODJSU2lyV0NCWFBRPTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c3VwcG9ydEBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIiBSZWNpcGllbnQ9IntyZWNpcGllbnR9Ii8+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+PC9zYW1sOlN1YmplY3Q+PHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMTEtMThUMjE6NTI6MzdaIiBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnthdWRpZW5jZX08L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOVQyMTo1NzozN1oiIFNlc3Npb25JbmRleD0iXzUzMWMzMmQyODNiZGZmN2UwNGU0ODdiY2RiYzRkZDhkIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiIEZyaWVuZGx5TmFtZT0idXNlcm5hbWUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJmcmllbmRseTEiIEZyaWVuZGx5TmFtZT0iZnJpZW5kbHl0ZXN0Ij48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmZyaWVuZGx5MTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJmcmllbmRseTIiIEZyaWVuZGx5TmFtZT0iZnJpZW5kbHl0ZXN0Ij48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmZyaWVuZGx5Mjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhbm90aGVyX3ZhbHVlIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnZhbHVlPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBWZXJzaW9uPSIyLjAiIElEPSJwZng4ZmZiMzk4My1jYmY2LTkyYTEtZjJjNC02MTlhZTFiZTFjODYiIElzc3VlSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbC9tZXRhZGF0YS8xMzU5MDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+PGRzOlJlZmVyZW5jZSBVUkk9IiNwZng4ZmZiMzk4My1jYmY2LTkyYTEtZjJjNC02MTlhZTFiZTFjODYiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmhndVFiQ0hhbmliYkRDN3EzWnp4ekhjUG9uST08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+R2FuY0Q5dlJvaDlNYlQwMDJEeTc5dDFtNkk2WWZoVUtQZmJsa21wMnVkb2xYdWp2NmUxTVd2c1ZteE56dHNJR2x4QWEwcUtEaVNNekNORFpzazNqc3lzVWwxbkFLbkFnMTg1amZYanN6aHNkbVIrTTkxZHhrNmtmY0xVb3NPb2xvdmFkV0xQV3FuN1AzajgvNXh6cDlMcFJBM2d2QjQxODJSU2lyV0NCWFBRPTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c3VwcG9ydEBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIiBSZWNpcGllbnQ9IntyZWNpcGllbnR9Ii8+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+PC9zYW1sOlN1YmplY3Q+PHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMTEtMThUMjE6NTI6MzdaIiBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPnthdWRpZW5jZX08L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOVQyMTo1NzozN1oiIFNlc3Npb25JbmRleD0iXzUzMWMzMmQyODNiZGZmN2UwNGU0ODdiY2RiYzRkZDhkIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiIEZyaWVuZGx5TmFtZT0idXNlcm5hbWUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJmcmllbmRseTEiIEZyaWVuZGx5TmFtZT0iZnJpZW5kbHl0ZXN0Ij48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmZyaWVuZGx5MTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJmcmllbmRseTIiIEZyaWVuZGx5TmFtZT0iZnJpZW5kbHl0ZXN0Ij48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPmZyaWVuZGx5Mjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhbm90aGVyX3ZhbHVlIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnZhbHVlPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImR1cGxpY2F0ZV9uYW1lIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPm5hbWUxPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImR1cGxpY2F0ZV9uYW1lIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPm5hbWUyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
diff --git a/tests/settings/settings11.json b/tests/settings/settings11.json
new file mode 100644
index 00000000..a039b32d
--- /dev/null
+++ b/tests/settings/settings11.json
@@ -0,0 +1,48 @@
+{
+ "strict": false,
+ "debug": false,
+ "custom_base_path": "../../../tests/data/customPath/",
+ "sp": {
+ "entityId": "http://stuff.com/endpoints/metadata.php",
+ "assertionConsumerService": {
+ "url": "http://stuff.com/endpoints/endpoints/acs.php"
+ },
+ "singleLogoutService": {
+ "url": "http://stuff.com/endpoints/endpoints/sls.php"
+ },
+ "NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ },
+ "idp": {
+ "entityId": "http://idp.example.com/",
+ "singleSignOnService": {
+ "url": "http://idp.example.com/SSOService.php"
+ },
+ "singleLogoutService": {
+ "url": "http://idp.example.com/SingleLogoutService.php"
+ },
+ "x509cert": "MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo"
+ },
+ "security": {
+ "authnRequestsSigned": false,
+ "wantAssertionsSigned": false,
+ "signMetadata": false,
+ "allowRepeatAttributeName": true
+ },
+ "contactPerson": {
+ "technical": {
+ "givenName": "technical_name",
+ "emailAddress": "technical@example.com"
+ },
+ "support": {
+ "givenName": "support_name",
+ "emailAddress": "support@example.com"
+ }
+ },
+ "organization": {
+ "en-US": {
+ "name": "sp_test",
+ "displayname": "SP test",
+ "url": "http://sp.example.com"
+ }
+ }
+}
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 711cc5d6..c8fee9d5 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -14,6 +14,7 @@
from xml.dom.minidom import parseString
from onelogin.saml2 import compat
+from onelogin.saml2.errors import OneLogin_Saml2_ValidationError
from onelogin.saml2.response import OneLogin_Saml2_Response
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils
@@ -696,6 +697,24 @@ def testGetAttributes(self):
response_3 = OneLogin_Saml2_Response(settings, xml_3)
self.assertEqual({}, response_3.get_attributes())
+ # Test retrieving duplicate attributes
+ xml_4 = self.file_contents(join(self.data_path, 'responses',
+ 'response1_with_duplicate_attributes.xml.base64'))
+ response_4 = OneLogin_Saml2_Response(settings, xml_4)
+ with self.assertRaises(OneLogin_Saml2_ValidationError) as duplicate_name_exc:
+ response_4.get_attributes()
+ self.assertIn('Found an Attribute element with duplicated Name', str(duplicate_name_exc.exception))
+
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON('settings11.json'))
+ expected_attributes = {'another_value': ['value'],
+ 'duplicate_name': ['name1', 'name2'],
+ 'friendly1': ['friendly1'],
+ 'friendly2': ['friendly2'],
+ 'uid': ['demo']}
+
+ response_5 = OneLogin_Saml2_Response(settings, xml_4)
+ self.assertEqual(expected_attributes, response_5.get_attributes())
+
def testGetFriendlyAttributes(self):
"""
Tests the get_friendlyname_attributes method of the OneLogin_Saml2_Response
@@ -721,14 +740,22 @@ def testGetFriendlyAttributes(self):
response_4 = OneLogin_Saml2_Response(settings, xml_4)
self.assertEqual({}, response_4.get_friendlyname_attributes())
+ # Test retrieving duplicate attributes
+ xml_5 = self.file_contents(join(self.data_path, 'responses',
+ 'response1_with_duplicate_attributes.xml.base64'))
+ response_5 = OneLogin_Saml2_Response(settings, xml_5)
+ with self.assertRaises(OneLogin_Saml2_ValidationError) as duplicate_name_exc:
+ response_5.get_friendlyname_attributes()
+ self.assertIn('Found an Attribute element with duplicated FriendlyName', str(duplicate_name_exc.exception))
+
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON('settings11.json'))
expected_attributes = {
'username': ['demo'],
'friendlytest': ['friendly1', 'friendly2']
}
- xml_5 = self.file_contents(join(self.data_path, 'responses',
- 'response1_with_duplicate_friendlynames.xml.base64'))
- response_5 = OneLogin_Saml2_Response(settings, xml_5)
- self.assertEqual(expected_attributes, response_5.get_friendlyname_attributes())
+
+ response_6 = OneLogin_Saml2_Response(settings, xml_5)
+ self.assertEqual(expected_attributes, response_6.get_friendlyname_attributes())
def testGetNestedNameIDAttributes(self):
"""
From 50cbbf77448c20a482b60e342796fc18845a908c Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Mon, 7 Jun 2021 13:44:59 -0400
Subject: [PATCH 217/331] Changed to support Python2 Syntax
---
src/onelogin/saml2/response.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index c5ca4b86..d69242d3 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -591,7 +591,7 @@ def _get_attributes(self, attr_name):
if attr_key:
if not allow_duplicates and attr_key in attributes:
raise OneLogin_Saml2_ValidationError(
- f'Found an Attribute element with duplicated {attr_name}',
+ 'Found an Attribute element with duplicated ' + attr_name,
OneLogin_Saml2_ValidationError.DUPLICATED_ATTRIBUTE_NAME_FOUND
)
From b794a89916e8e0aaed3d1b9c6b149964a2e98320 Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Mon, 7 Jun 2021 17:27:41 -0400
Subject: [PATCH 218/331] Added to readme
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 752fcd43..6a237212 100644
--- a/README.md
+++ b/README.md
@@ -451,7 +451,11 @@ In addition to the required settings data (idp, sp), extra settings can be defin
// 'http://www.w3.org/2001/04/xmlenc#sha256'
// 'http://www.w3.org/2001/04/xmldsig-more#sha384'
// 'http://www.w3.org/2001/04/xmlenc#sha512'
- 'digestAlgorithm': "http://www.w3.org/2001/04/xmlenc#sha256"
+ 'digestAlgorithm': "http://www.w3.org/2001/04/xmlenc#sha256",
+
+ // Specify if you want the SP to view assertions with duplicated Name or FriendlyName attributes to be valid
+ // Defaults to false if not specified
+ 'allowRepeatAttributeName': false
},
// Contact information template, it is recommended to suply a
From a6878640b408e892f53364a41494d5e494c8cb14 Mon Sep 17 00:00:00 2001
From: "Mahoney, John"
Date: Tue, 15 Jun 2021 22:04:39 -0400
Subject: [PATCH 219/331] Added test for nested nameid attributes in
FriendlyName attribute retrieval
---
...ponse_with_nested_nameid_values.xml.base64 | 72 +------------------
.../src/OneLogin/saml2_tests/response_test.py | 11 +++
2 files changed, 12 insertions(+), 71 deletions(-)
diff --git a/tests/data/responses/response_with_nested_nameid_values.xml.base64 b/tests/data/responses/response_with_nested_nameid_values.xml.base64
index 3092c3cf..3b99e137 100644
--- a/tests/data/responses/response_with_nested_nameid_values.xml.base64
+++ b/tests/data/responses/response_with_nested_nameid_values.xml.base64
@@ -1,71 +1 @@
-PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDph
-c3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9j
-b2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50
-PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4KICA8c2Ft
-bHA6U3RhdHVzPgogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0
-YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPgogIDxzYW1sOkFzc2Vy
-dGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhz
-aT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIu
-MCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MiIgSXNzdWVJbnN0
-YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+CiAgICA8c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAu
-b25lbG9naW4uY29tL3NhbWwvbWV0YWRhdGEvMTM1OTA8L3NhbWw6SXNzdWVyPgogICAgPGRzOlNp
-Z25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAg
-ICAgIDxkczpTaWduZWRJbmZvPgogICAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFs
-Z29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CiAgICAg
-ICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAv
-MDkveG1sZHNpZyNyc2Etc2hhMSIvPgogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2
-NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4KICAgICAgICAgIDxkczpUcmFuc2Zv
-cm1zPgogICAgICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5v
-cmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KICAgICAgICAgICAgPGRz
-OlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1j
-MTRuIyIvPgogICAgICAgICAgPC9kczpUcmFuc2Zvcm1zPgogICAgICAgICAgPGRzOkRpZ2VzdE1l
-dGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+
-CiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwv
-ZHM6RGlnZXN0VmFsdWU+CiAgICAgICAgPC9kczpSZWZlcmVuY2U+CiAgICAgIDwvZHM6U2lnbmVk
-SW5mbz4KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9j
-QTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRit6
-eEd5ZWw1Z09vd21veVhKcitBT3Ira1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lF
-YXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPgogICAg
-ICA8ZHM6S2V5SW5mbz4KICAgICAgICA8ZHM6WDUwOURhdGE+CiAgICAgICAgICA8ZHM6WDUwOUNl
-cnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxW
-VE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVw
-WTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5h
-VzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1B
-a0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZO
-aGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3
-Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JB
-T2pTdTFmalB5OGQ1dzRReUwxK3pkNGhJdzFNa2tmZjRXWS9UTEc4T1prVTVZVFNXbW1IUEQ1a3ZZ
-SDV1b1hTLzZxUTgxcVhwUjJ3VjhDVG93WkpVTGcwOWRkUmRSbjhRc3FqMUZ5T0M1c2xFM3kyYloy
-b0Z1YTcyb2YvNDlmcHVqbkZUNktuUTYxQ0JNcWxEb1RRcU9UNjJ2R0o4blA2TVpXdkE2c3hxdWQ1
-QWdNQkFBRXdBd1lCQUFNQkFBPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT4KICAgICAgICA8L2RzOlg1
-MDlEYXRhPgogICAgICA8L2RzOktleUluZm8+CiAgICA8L2RzOlNpZ25hdHVyZT4KICAgIDxzYW1s
-OlN1YmplY3Q+CiAgICAgIDxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpT
-QU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c3VwcG9ydEBvbmVsb2dpbi5jb208
-L3NhbWw6TmFtZUlEPgogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJu
-Om9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+CiAgICAgICAgPHNhbWw6U3ViamVj
-dENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDEwLTExLTE4VDIyOjAyOjM3WiIgUmVj
-aXBpZW50PSJ7cmVjaXBpZW50fSIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPgogICAgPC9z
-YW1sOlN1YmplY3Q+CiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQy
-MTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPgogICAgICA8c2Ft
-bDpBdWRpZW5jZVJlc3RyaWN0aW9uPgogICAgICAgIDxzYW1sOkF1ZGllbmNlPnthdWRpZW5jZX08
-L3NhbWw6QXVkaWVuY2U+CiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPgogICAgPC9z
-YW1sOkNvbmRpdGlvbnM+CiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIw
-MTAtMTEtMThUMjE6NTc6MzdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDEwLTExLTE5VDIxOjU3
-OjM3WiIgU2Vzc2lvbkluZGV4PSJfNTMxYzMyZDI4M2JkZmY3ZTA0ZTQ4N2JjZGJjNGRkOGQiPgog
-ICAgICA8c2FtbDpBdXRobkNvbnRleHQ+CiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NS
-ZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6
-QXV0aG5Db250ZXh0Q2xhc3NSZWY+CiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+CiAgICA8L3Nh
-bWw6QXV0aG5TdGF0ZW1lbnQ+CiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+CiAgICAgIDxz
-YW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiPgogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHht
-bG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRw
-Oi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmlu
-ZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4KICAg
-ICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImFub3RoZXJfdmFsdWUiPgogICAgICAgIDxzYW1sOkF0
-dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIg
-eG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNp
-OnR5cGU9InhzOnN0cmluZyI+CiAgICAgICAgICAgIDxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpv
-YXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiIE5hbWVRdWFs
-aWZpZXI9Imh0dHBzOi8vaWRwSUQiIFNQTmFtZVF1YWxpZmllcj0iaHR0cHM6Ly9zcElEIj52YWx1
-ZTwvc2FtbDpOYW1lSUQ+CiAgICAgICAgPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPgogICAgICA8L3Nh
-bWw6QXR0cmlidXRlPgogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4KICA8L3NhbWw6QXNz
-ZXJ0aW9uPgo8L3NhbWxwOlJlc3BvbnNlPgo=
+PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MiIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj4NCiAgICA8ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgICAgIDxkczpTaWduZWRJbmZvPg0KICAgICAgICA8ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4NCiAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICAgIDwvZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICA8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz4NCiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwvZHM6RGlnZXN0VmFsdWU+DQogICAgICAgIDwvZHM6UmVmZXJlbmNlPg0KICAgICAgPC9kczpTaWduZWRJbmZvPg0KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9jQTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRit6eEd5ZWw1Z09vd21veVhKcitBT3Ira1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lFYXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPg0KICAgICAgPGRzOktleUluZm8+DQogICAgICAgIDxkczpYNTA5RGF0YT4NCiAgICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVwWTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5hVzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZOaGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBT2pTdTFmalB5OGQ1dzRReUwxK3pkNGhJdzFNa2tmZjRXWS9UTEc4T1prVTVZVFNXbW1IUEQ1a3ZZSDV1b1hTLzZxUTgxcVhwUjJ3VjhDVG93WkpVTGcwOWRkUmRSbjhRc3FqMUZ5T0M1c2xFM3kyYloyb0Z1YTcyb2YvNDlmcHVqbkZUNktuUTYxQ0JNcWxEb1RRcU9UNjJ2R0o4blA2TVpXdkE2c3hxdWQ1QWdNQkFBRXdBd1lCQUFNQkFBPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT4NCiAgICAgICAgPC9kczpYNTA5RGF0YT4NCiAgICAgIDwvZHM6S2V5SW5mbz4NCiAgICA8L2RzOlNpZ25hdHVyZT4NCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zdXBwb3J0QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6MzdaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSIgRnJpZW5kbHlOYW1lPSJhbm90aGVyX2ZyaWVuZGx5X3ZhbHVlIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj4NCiAgICAgICAgICAgIDxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiIE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vaWRwSUQiIFNQTmFtZVF1YWxpZmllcj0iaHR0cHM6Ly9zcElEIj52YWx1ZTwvc2FtbDpOYW1lSUQ+DQogICAgICAgIDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index c8fee9d5..a51f8e60 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -777,6 +777,17 @@ def testGetNestedNameIDAttributes(self):
}
self.assertEqual(expected_attributes, response.get_attributes())
+ expected_attributes = {
+ 'another_friendly_value': [{
+ 'NameID': {
+ 'Format': 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ 'NameQualifier': 'https://idpID',
+ 'value': 'value'
+ }
+ }]
+ }
+ self.assertEqual(expected_attributes, response.get_friendlyname_attributes())
+
def testOnlyRetrieveAssertionWithIDThatMatchesSignatureReference(self):
"""
Tests the get_nameid method of the OneLogin_Saml2_Response
From 5eaad0a3c6cb06769b0d21cdf1f294aac2f364f9 Mon Sep 17 00:00:00 2001
From: Arne Schwabe
Date: Mon, 28 Jun 2021 13:57:37 +0200
Subject: [PATCH 220/331] Fix misleading comment with fingerprint hash weaker
than a certificate verification
The reasoning of a fingerprint hash weaker than providing a certificate like a CA is wrong.
A X509 signature of a certificate always uses a Hash like SHA1, SHA256, etc, which is then signed. E.g.
openssl1.1 x509 -text -in sp-test.pem
Signature Algorithm: ecdsa-with-SHA256
So these are as vulnerable to collision attacks as fingeprints.
Depending on the implementation of the fingerprint, there are other for not using them. E.g. some implementation ignore other problem with a certificate like validity or missing EKUs.
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 6a237212..3bbf2a1f 100644
--- a/README.md
+++ b/README.md
@@ -323,8 +323,8 @@ This is the ``settings.json`` file:
/*
* Instead of using the whole X.509cert you can use a fingerprint in order to
* validate a SAMLResponse (but you still need the X.509cert to validate LogoutRequest and LogoutResponse using the HTTP-Redirect binding).
- * But take in mind that the fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass,
- * that why we don't recommend it use for production environments.
+ * But take in mind that the algortithm for the fingerprint should be as strong as the algorithm in a normal certificate signature
+ * (e.g. SHA256 or strong)
*
* (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
* or add for example the -sha256 , -sha384 or -sha512 parameter)
From 53ee46fe2632c5f137fa6d1df72403cbd703b9be Mon Sep 17 00:00:00 2001
From: Chris Volny
Date: Wed, 30 Jun 2021 16:48:46 -0400
Subject: [PATCH 221/331] make the redirect scheme matcher case-insensitive.
---
src/onelogin/saml2/utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 1290033e..db88bd34 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -210,7 +210,7 @@ def redirect(url, parameters={}, request_data={}):
url = '%s%s' % (OneLogin_Saml2_Utils.get_self_url_host(request_data), url)
# Verify that the URL is to a http or https site.
- if re.search('^https?://', url) is None:
+ if re.search('^https?://', url, flags=re.IGNORECASE) is None:
raise OneLogin_Saml2_Error(
'Redirect to invalid URL: ' + url,
OneLogin_Saml2_Error.REDIRECT_INVALID_URL
From e7f6c67eee9eb25ab7746af96ab00ad0a3fd145e Mon Sep 17 00:00:00 2001
From: Zhaofeng Li
Date: Sat, 3 Jul 2021 18:35:58 -0700
Subject: [PATCH 222/331] tests: Update expiry dates for responses
---
tests/data/responses/invalids/encrypted_attrs.xml.base64 | 2 +-
tests/data/responses/invalids/invalid_audience.xml.base64 | 2 +-
.../data/responses/invalids/invalid_issuer_assertion.xml.base64 | 2 +-
tests/data/responses/invalids/invalid_issuer_message.xml.base64 | 2 +-
tests/data/responses/invalids/invalid_sessionindex.xml.base64 | 2 +-
.../invalids/invalid_subjectconfirmation_inresponse.xml.base64 | 2 +-
.../invalids/invalid_subjectconfirmation_noa.xml.base64 | 2 +-
.../invalids/invalid_subjectconfirmation_recipient.xml.base64 | 2 +-
tests/data/responses/invalids/no_nameid.xml.base64 | 2 +-
.../responses/invalids/no_subjectconfirmation_data.xml.base64 | 2 +-
.../responses/invalids/no_subjectconfirmation_method.xml.base64 | 2 +-
.../data/responses/invalids/response_encrypted_attrs.xml.base64 | 2 +-
tests/data/responses/no_audience.xml.base64 | 2 +-
.../data/responses/unsigned_response_with_miliseconds.xm.base64 | 2 +-
14 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/tests/data/responses/invalids/encrypted_attrs.xml.base64 b/tests/data/responses/invalids/encrypted_attrs.xml.base64
index f452075c..a170970a 100644
--- a/tests/data/responses/invalids/encrypted_attrs.xml.base64
+++ b/tests/data/responses/invalids/encrypted_attrs.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaGh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KIDxzYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZSB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj4NCiAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCIgSWQ9Il9GMzk2MjVBRjY4QjRGQzA3OENDNzU4MkQyOEQwNUQ5QyI+DQogICAgICAgICAgICA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMjU2LWNiYyIvPg0KICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICAgICAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+DQogICAgICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogICAgICAgICAgICAgICAgICA8ZHM6S2V5TmFtZT42MjM1NWZiZDFmNjI0NTAzYzVjOTY3NzQwMmVjY2EwMGVmMWY2Mjc3PC9kczpLZXlOYW1lPg0KICAgICAgICAgICAgICAgIDwvZHM6S2V5SW5mbz4NCiAgICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+SzBtQkx4Zkx6aUtWVUtFQU9ZZTdENnVWU0NQeTh2eVdWaDNSZWNuUEVTKzhRa0FoT3VSU3VFL0xRcEZyMGh1SS9pQ0V5OXBkZTFRZ2pZREx0akhjdWpLaTJ4R3FXNmprWFcvRXVLb21xV1BQQTJ4WXMxZnBCMXN1NGFYVU9RQjZPSjcwL29EY09zeTgzNGdoRmFCV2lsRThmcXlEQlVCdlcrMkl2YU1VWmFid04vczltVmtXek0zcjMwdGxraExLN2lPcmJHQWxkSUh3RlU1ejdQUFI2Uk8zWTNmSXhqSFU0ME9uTHNKYzN4SXFkTEgzZlhwQzBrZ2k1VXNwTGRxMTRlNU9vWGpMb1BHM0JPM3p3T0FJSjhYTkJXWTV1UW9mNktyS2JjdnRaU1kwZk12UFloWWZOanRSRnk4eTQ5b3ZMOWZ3akNSVERsVDUrYUhxc0NUQnJ3PT08L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICAgICAgPC94ZW5jOkNpcGhlckRhdGE+DQogICAgICAgICAgICAgIDwveGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICA8L2RzOktleUluZm8+DQogICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5aekN1NmF4R2dBWVpIVmY3N05YOGFwWktCL0dKRGV1VjZiRkJ5QlMwQUlnaVhrdkRVQW1MQ3BhYlRBV0JNK3l6MTlvbEE2cnJ5dU9mcjgyZXYyYnpQTlVSdm00U1l4YWh2dUw0UGlibjV3Smt5MEJsNTRWcW1jVStBcWowZEF2T2dxRzF5M1g0d085bjliUnNUdjY5MjFtMGVxUkFGcGg4a0s4TDloaXJLMUJ4WUJZajJSeUZDb0ZEUHhWWjV3eXJhM3E0cW1FNC9FTFFwRlA2bWZVOExYYjB1b1dKVWpHVWVsUzJBYTdiWmlzOHpFcHdvdjRDd3RsTmpsdFFpaDRtdjd0dENBZllxY1FJRnpCVEIrREFhMCtYZ2d4Q0xjZEIzK21RaVJjRUNCZndISEo3Z1JtbnVCRWdlV1QzQ0dLYTNOYjdHTVhPZnV4RktGNXBJZWhXZ28za2ROUUxhbG9yOFJWVzZJOFAvSThmUTMzRmUrTnNIVm5KM3p3U0EvL2E8L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4NCiAgICAgICAgICA8L3hlbmM6RW5jcnlwdGVkRGF0YT4NCiAgICAgICAgPC9zYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaGh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KIDxzYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZSB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj4NCiAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCIgSWQ9Il9GMzk2MjVBRjY4QjRGQzA3OENDNzU4MkQyOEQwNUQ5QyI+DQogICAgICAgICAgICA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMjU2LWNiYyIvPg0KICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICAgICAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+DQogICAgICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogICAgICAgICAgICAgICAgICA8ZHM6S2V5TmFtZT42MjM1NWZiZDFmNjI0NTAzYzVjOTY3NzQwMmVjY2EwMGVmMWY2Mjc3PC9kczpLZXlOYW1lPg0KICAgICAgICAgICAgICAgIDwvZHM6S2V5SW5mbz4NCiAgICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+SzBtQkx4Zkx6aUtWVUtFQU9ZZTdENnVWU0NQeTh2eVdWaDNSZWNuUEVTKzhRa0FoT3VSU3VFL0xRcEZyMGh1SS9pQ0V5OXBkZTFRZ2pZREx0akhjdWpLaTJ4R3FXNmprWFcvRXVLb21xV1BQQTJ4WXMxZnBCMXN1NGFYVU9RQjZPSjcwL29EY09zeTgzNGdoRmFCV2lsRThmcXlEQlVCdlcrMkl2YU1VWmFid04vczltVmtXek0zcjMwdGxraExLN2lPcmJHQWxkSUh3RlU1ejdQUFI2Uk8zWTNmSXhqSFU0ME9uTHNKYzN4SXFkTEgzZlhwQzBrZ2k1VXNwTGRxMTRlNU9vWGpMb1BHM0JPM3p3T0FJSjhYTkJXWTV1UW9mNktyS2JjdnRaU1kwZk12UFloWWZOanRSRnk4eTQ5b3ZMOWZ3akNSVERsVDUrYUhxc0NUQnJ3PT08L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICAgICAgPC94ZW5jOkNpcGhlckRhdGE+DQogICAgICAgICAgICAgIDwveGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICA8L2RzOktleUluZm8+DQogICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5aekN1NmF4R2dBWVpIVmY3N05YOGFwWktCL0dKRGV1VjZiRkJ5QlMwQUlnaVhrdkRVQW1MQ3BhYlRBV0JNK3l6MTlvbEE2cnJ5dU9mcjgyZXYyYnpQTlVSdm00U1l4YWh2dUw0UGlibjV3Smt5MEJsNTRWcW1jVStBcWowZEF2T2dxRzF5M1g0d085bjliUnNUdjY5MjFtMGVxUkFGcGg4a0s4TDloaXJLMUJ4WUJZajJSeUZDb0ZEUHhWWjV3eXJhM3E0cW1FNC9FTFFwRlA2bWZVOExYYjB1b1dKVWpHVWVsUzJBYTdiWmlzOHpFcHdvdjRDd3RsTmpsdFFpaDRtdjd0dENBZllxY1FJRnpCVEIrREFhMCtYZ2d4Q0xjZEIzK21RaVJjRUNCZndISEo3Z1JtbnVCRWdlV1QzQ0dLYTNOYjdHTVhPZnV4RktGNXBJZWhXZ28za2ROUUxhbG9yOFJWVzZJOFAvSThmUTMzRmUrTnNIVm5KM3p3U0EvL2E8L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4NCiAgICAgICAgICA8L3hlbmM6RW5jcnlwdGVkRGF0YT4NCiAgICAgICAgPC9zYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
\ No newline at end of file
diff --git a/tests/data/responses/invalids/invalid_audience.xml.base64 b/tests/data/responses/invalids/invalid_audience.xml.base64
index a2575836..787c8a79 100644
--- a/tests/data/responses/invalids/invalid_audience.xml.base64
+++ b/tests/data/responses/invalids/invalid_audience.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9pbnZhbGlkLmF1ZGllbmNlLmNvbTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9pbnZhbGlkLmF1ZGllbmNlLmNvbTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
diff --git a/tests/data/responses/invalids/invalid_issuer_assertion.xml.base64 b/tests/data/responses/invalids/invalid_issuer_assertion.xml.base64
index 07748ece..426ea5c2 100644
--- a/tests/data/responses/invalids/invalid_issuer_assertion.xml.base64
+++ b/tests/data/responses/invalids/invalid_issuer_assertion.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2ludmFsaWQuaXNzdWVyLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+ICAgIA0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJoZWxsby5jb20iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIwLTA2LTE3VDE0OjU5OjE0WiIgUmVjaXBpZW50PSJodHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9lbmRwb2ludHMvYWNzLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2ludmFsaWQuaXNzdWVyLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+ICAgIA0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJoZWxsby5jb20iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIwLTA2LTE3VDE0OjU5OjE0WiIgUmVjaXBpZW50PSJodHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9lbmRwb2ludHMvYWNzLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
\ No newline at end of file
diff --git a/tests/data/responses/invalids/invalid_issuer_message.xml.base64 b/tests/data/responses/invalids/invalid_issuer_message.xml.base64
index f1f49fe5..0c06a25e 100644
--- a/tests/data/responses/invalids/invalid_issuer_message.xml.base64
+++ b/tests/data/responses/invalids/invalid_issuer_message.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaW52YWxpZC5pc3Nlci5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZng3ODQxOTkxYy1jNzNmLTQwMzUtZTJlZS1jMTcwYzBlMWQzZTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjE0WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vPC9zYW1sOklzc3Vlcj4gICAgDQogICAgPHNhbWw6U3ViamVjdD4NCiAgICAgIDxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9ImhlbGxvLmNvbSIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6TmFtZUlEPg0KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg0KICA=
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaW52YWxpZC5pc3Nlci5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZng3ODQxOTkxYy1jNzNmLTQwMzUtZTJlZS1jMTcwYzBlMWQzZTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjE0WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vPC9zYW1sOklzc3Vlcj4gICAgDQogICAgPHNhbWw6U3ViamVjdD4NCiAgICAgIDxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9ImhlbGxvLmNvbSIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6TmFtZUlEPg0KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg0KICA=
\ No newline at end of file
diff --git a/tests/data/responses/invalids/invalid_sessionindex.xml.base64 b/tests/data/responses/invalids/invalid_sessionindex.xml.base64
index cc5a581c..f2e4c4c6 100644
--- a/tests/data/responses/invalids/invalid_sessionindex.xml.base64
+++ b/tests/data/responses/invalids/invalid_sessionindex.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTMtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTMtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
diff --git a/tests/data/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 b/tests/data/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64
index 2ce545f8..b6a4e2eb 100644
--- a/tests/data/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64
+++ b/tests/data/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iaW52YWxpZF9pbnJlc3BvbnNlIi8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iaW52YWxpZF9pbnJlc3BvbnNlIi8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
\ No newline at end of file
diff --git a/tests/data/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 b/tests/data/responses/invalids/invalid_subjectconfirmation_noa.xml.base64
index 6d4b70aa..4f922132 100644
--- a/tests/data/responses/invalids/invalid_subjectconfirmation_noa.xml.base64
+++ b/tests/data/responses/invalids/invalid_subjectconfirmation_noa.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
diff --git a/tests/data/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 b/tests/data/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64
index 1bf1d25a..30c55eb2 100644
--- a/tests/data/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64
+++ b/tests/data/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL2ludmFsaWQucmVjaXBlbnQuZXhhbXBsZS5jb20iIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL2ludmFsaWQucmVjaXBlbnQuZXhhbXBsZS5jb20iIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
diff --git a/tests/data/responses/invalids/no_nameid.xml.base64 b/tests/data/responses/invalids/no_nameid.xml.base64
index 3c2b6d9b..db8879b2 100644
--- a/tests/data/responses/invalids/no_nameid.xml.base64
+++ b/tests/data/responses/invalids/no_nameid.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
\ No newline at end of file
diff --git a/tests/data/responses/invalids/no_subjectconfirmation_data.xml.base64 b/tests/data/responses/invalids/no_subjectconfirmation_data.xml.base64
index 3c92f561..bc28d907 100644
--- a/tests/data/responses/invalids/no_subjectconfirmation_data.xml.base64
+++ b/tests/data/responses/invalids/no_subjectconfirmation_data.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+ICAgICAgICANCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+ICAgICAgICANCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
\ No newline at end of file
diff --git a/tests/data/responses/invalids/no_subjectconfirmation_method.xml.base64 b/tests/data/responses/invalids/no_subjectconfirmation_method.xml.base64
index 07ce153a..37b69370 100644
--- a/tests/data/responses/invalids/no_subjectconfirmation_method.xml.base64
+++ b/tests/data/responses/invalids/no_subjectconfirmation_method.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmhvbGRlci1vZi1rZXkiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmhvbGRlci1vZi1rZXkiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
\ No newline at end of file
diff --git a/tests/data/responses/invalids/response_encrypted_attrs.xml.base64 b/tests/data/responses/invalids/response_encrypted_attrs.xml.base64
index 32449899..ad6392c5 100644
--- a/tests/data/responses/invalids/response_encrypted_attrs.xml.base64
+++ b/tests/data/responses/invalids/response_encrypted_attrs.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaGh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KIDxzYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZSB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj4NCiAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCIgSWQ9Il9GMzk2MjVBRjY4QjRGQzA3OENDNzU4MkQyOEQwNUQ5QyI+DQogICAgICAgICAgICA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMjU2LWNiYyIvPg0KICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICAgICAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+DQogICAgICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogICAgICAgICAgICAgICAgICA8ZHM6S2V5TmFtZT42MjM1NWZiZDFmNjI0NTAzYzVjOTY3NzQwMmVjY2EwMGVmMWY2Mjc3PC9kczpLZXlOYW1lPg0KICAgICAgICAgICAgICAgIDwvZHM6S2V5SW5mbz4NCiAgICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+SzBtQkx4Zkx6aUtWVUtFQU9ZZTdENnVWU0NQeTh2eVdWaDNSZWNuUEVTKzhRa0FoT3VSU3VFL0xRcEZyMGh1SS9pQ0V5OXBkZTFRZ2pZREx0akhjdWpLaTJ4R3FXNmprWFcvRXVLb21xV1BQQTJ4WXMxZnBCMXN1NGFYVU9RQjZPSjcwL29EY09zeTgzNGdoRmFCV2lsRThmcXlEQlVCdlcrMkl2YU1VWmFid04vczltVmtXek0zcjMwdGxraExLN2lPcmJHQWxkSUh3RlU1ejdQUFI2Uk8zWTNmSXhqSFU0ME9uTHNKYzN4SXFkTEgzZlhwQzBrZ2k1VXNwTGRxMTRlNU9vWGpMb1BHM0JPM3p3T0FJSjhYTkJXWTV1UW9mNktyS2JjdnRaU1kwZk12UFloWWZOanRSRnk4eTQ5b3ZMOWZ3akNSVERsVDUrYUhxc0NUQnJ3PT08L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICAgICAgPC94ZW5jOkNpcGhlckRhdGE+DQogICAgICAgICAgICAgIDwveGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICA8L2RzOktleUluZm8+DQogICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5aekN1NmF4R2dBWVpIVmY3N05YOGFwWktCL0dKRGV1VjZiRkJ5QlMwQUlnaVhrdkRVQW1MQ3BhYlRBV0JNK3l6MTlvbEE2cnJ5dU9mcjgyZXYyYnpQTlVSdm00U1l4YWh2dUw0UGlibjV3Smt5MEJsNTRWcW1jVStBcWowZEF2T2dxRzF5M1g0d085bjliUnNUdjY5MjFtMGVxUkFGcGg4a0s4TDloaXJLMUJ4WUJZajJSeUZDb0ZEUHhWWjV3eXJhM3E0cW1FNC9FTFFwRlA2bWZVOExYYjB1b1dKVWpHVWVsUzJBYTdiWmlzOHpFcHdvdjRDd3RsTmpsdFFpaDRtdjd0dENBZllxY1FJRnpCVEIrREFhMCtYZ2d4Q0xjZEIzK21RaVJjRUNCZndISEo3Z1JtbnVCRWdlV1QzQ0dLYTNOYjdHTVhPZnV4RktGNXBJZWhXZ28za2ROUUxhbG9yOFJWVzZJOFAvSThmUTMzRmUrTnNIVm5KM3p3U0EvL2E8L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4NCiAgICAgICAgICA8L3hlbmM6RW5jcnlwdGVkRGF0YT4NCiAgICAgICAgPC9zYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaGh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KIDxzYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZSB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj4NCiAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCIgSWQ9Il9GMzk2MjVBRjY4QjRGQzA3OENDNzU4MkQyOEQwNUQ5QyI+DQogICAgICAgICAgICA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMjU2LWNiYyIvPg0KICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICAgICAgICAgICAgICA8eGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+DQogICAgICAgICAgICAgICAgPGRzOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogICAgICAgICAgICAgICAgICA8ZHM6S2V5TmFtZT42MjM1NWZiZDFmNjI0NTAzYzVjOTY3NzQwMmVjY2EwMGVmMWY2Mjc3PC9kczpLZXlOYW1lPg0KICAgICAgICAgICAgICAgIDwvZHM6S2V5SW5mbz4NCiAgICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+SzBtQkx4Zkx6aUtWVUtFQU9ZZTdENnVWU0NQeTh2eVdWaDNSZWNuUEVTKzhRa0FoT3VSU3VFL0xRcEZyMGh1SS9pQ0V5OXBkZTFRZ2pZREx0akhjdWpLaTJ4R3FXNmprWFcvRXVLb21xV1BQQTJ4WXMxZnBCMXN1NGFYVU9RQjZPSjcwL29EY09zeTgzNGdoRmFCV2lsRThmcXlEQlVCdlcrMkl2YU1VWmFid04vczltVmtXek0zcjMwdGxraExLN2lPcmJHQWxkSUh3RlU1ejdQUFI2Uk8zWTNmSXhqSFU0ME9uTHNKYzN4SXFkTEgzZlhwQzBrZ2k1VXNwTGRxMTRlNU9vWGpMb1BHM0JPM3p3T0FJSjhYTkJXWTV1UW9mNktyS2JjdnRaU1kwZk12UFloWWZOanRSRnk4eTQ5b3ZMOWZ3akNSVERsVDUrYUhxc0NUQnJ3PT08L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICAgICAgPC94ZW5jOkNpcGhlckRhdGE+DQogICAgICAgICAgICAgIDwveGVuYzpFbmNyeXB0ZWRLZXk+DQogICAgICAgICAgICA8L2RzOktleUluZm8+DQogICAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5aekN1NmF4R2dBWVpIVmY3N05YOGFwWktCL0dKRGV1VjZiRkJ5QlMwQUlnaVhrdkRVQW1MQ3BhYlRBV0JNK3l6MTlvbEE2cnJ5dU9mcjgyZXYyYnpQTlVSdm00U1l4YWh2dUw0UGlibjV3Smt5MEJsNTRWcW1jVStBcWowZEF2T2dxRzF5M1g0d085bjliUnNUdjY5MjFtMGVxUkFGcGg4a0s4TDloaXJLMUJ4WUJZajJSeUZDb0ZEUHhWWjV3eXJhM3E0cW1FNC9FTFFwRlA2bWZVOExYYjB1b1dKVWpHVWVsUzJBYTdiWmlzOHpFcHdvdjRDd3RsTmpsdFFpaDRtdjd0dENBZllxY1FJRnpCVEIrREFhMCtYZ2d4Q0xjZEIzK21RaVJjRUNCZndISEo3Z1JtbnVCRWdlV1QzQ0dLYTNOYjdHTVhPZnV4RktGNXBJZWhXZ28za2ROUUxhbG9yOFJWVzZJOFAvSThmUTMzRmUrTnNIVm5KM3p3U0EvL2E8L3hlbmM6Q2lwaGVyVmFsdWU+DQogICAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4NCiAgICAgICAgICA8L3hlbmM6RW5jcnlwdGVkRGF0YT4NCiAgICAgICAgPC9zYW1sOkVuY3J5cHRlZEF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4=
\ No newline at end of file
diff --git a/tests/data/responses/no_audience.xml.base64 b/tests/data/responses/no_audience.xml.base64
index 6ac34337..25550e3b 100644
--- a/tests/data/responses/no_audience.xml.base64
+++ b/tests/data/responses/no_audience.xml.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDk5LTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg==
\ No newline at end of file
diff --git a/tests/data/responses/unsigned_response_with_miliseconds.xm.base64 b/tests/data/responses/unsigned_response_with_miliseconds.xm.base64
index 76522b7e..1722cbf9 100644
--- a/tests/data/responses/unsigned_response_with_miliseconds.xm.base64
+++ b/tests/data/responses/unsigned_response_with_miliseconds.xm.base64
@@ -1 +1 @@
-PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTQuMTIwWiIgRGVzdGluYXRpb249Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZng3ODQxOTkxYy1jNzNmLTQwMzUtZTJlZS1jMTcwYzBlMWQzZTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjE0LjEyMFoiPg0KICAgIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+ICAgIA0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJoZWxsby5jb20iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIwLTA2LTE3VDE0OjU5OjE0WiIgUmVjaXBpZW50PSJodHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9lbmRwb2ludHMvYWNzLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NC4xNzNaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTQuMjM1WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDcuMTIwWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QyMjo1NDoxNC4xMjBaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
+PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTQuMTIwWiIgRGVzdGluYXRpb249Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZng3ODQxOTkxYy1jNzNmLTQwMzUtZTJlZS1jMTcwYzBlMWQzZTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjE0LjEyMFoiPg0KICAgIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+ICAgIA0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJoZWxsby5jb20iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIwLTA2LTE3VDE0OjU5OjE0WiIgUmVjaXBpZW50PSJodHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9lbmRwb2ludHMvYWNzLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NC4xNzNaIiBOb3RPbk9yQWZ0ZXI9IjIwOTktMDYtMTdUMTQ6NTk6MTQuMjM1WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDcuMTIwWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjA5OS0wNi0xN1QyMjo1NDoxNC4xMjBaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+
\ No newline at end of file
From aa1481eae7f315e24d9f96dfd2ee0aca90f27d4f Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Tue, 20 Jul 2021 15:08:55 +0300
Subject: [PATCH 223/331] Replace double-underscored names with single
underscores
This will avoid Python's class-name-prefix mangling of attributes and methods
while still retaining the signal that they're meant for private use of the library.
This makes it easier to e.g. subclass classes should one need to, and to access
data from outside (while being mindful of the fact that you're accessing notionally
private data).
Only one change to a test was required (and that test was indeed accessing a private
field from outside...).
---
src/onelogin/saml2/auth.py | 280 +++++++++---------
src/onelogin/saml2/authn_request.py | 22 +-
src/onelogin/saml2/logout_request.py | 40 +--
src/onelogin/saml2/logout_response.py | 42 +--
src/onelogin/saml2/metadata.py | 6 +-
src/onelogin/saml2/response.py | 92 +++---
src/onelogin/saml2/settings.py | 246 +++++++--------
.../saml2_tests/authn_request_test.py | 2 +-
8 files changed, 365 insertions(+), 365 deletions(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index cfaee5fd..c284fe46 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -52,29 +52,29 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
:param custom_base_path: Optional. Path where are stored the settings file and the cert folder
:type custom_base_path: string
"""
- self.__request_data = request_data
+ self._request_data = request_data
if isinstance(old_settings, OneLogin_Saml2_Settings):
- self.__settings = old_settings
+ self._settings = old_settings
else:
- self.__settings = OneLogin_Saml2_Settings(old_settings, custom_base_path)
- self.__attributes = dict()
- self.__friendlyname_attributes = dict()
- self.__nameid = None
- self.__nameid_format = None
- self.__nameid_nq = None
- self.__nameid_spnq = None
- self.__session_index = None
- self.__session_expiration = None
- self.__authenticated = False
- self.__errors = []
- self.__error_reason = None
- self.__last_request_id = None
- self.__last_message_id = None
- self.__last_assertion_id = None
- self.__last_authn_contexts = []
- self.__last_request = None
- self.__last_response = None
- self.__last_assertion_not_on_or_after = None
+ self._settings = OneLogin_Saml2_Settings(old_settings, custom_base_path)
+ self._attributes = dict()
+ self._friendlyname_attributes = dict()
+ self._nameid = None
+ self._nameid_format = None
+ self._nameid_nq = None
+ self._nameid_spnq = None
+ self._session_index = None
+ self._session_expiration = None
+ self._authenticated = False
+ self._errors = []
+ self._error_reason = None
+ self._last_request_id = None
+ self._last_message_id = None
+ self._last_assertion_id = None
+ self._last_authn_contexts = []
+ self._last_request = None
+ self._last_response = None
+ self._last_assertion_not_on_or_after = None
def get_settings(self):
"""
@@ -82,7 +82,7 @@ def get_settings(self):
:return: Setting info
:rtype: OneLogin_Saml2_Setting object
"""
- return self.__settings
+ return self._settings
def set_strict(self, value):
"""
@@ -92,22 +92,22 @@ def set_strict(self, value):
:type value: bool
"""
assert isinstance(value, bool)
- self.__settings.set_strict(value)
+ self._settings.set_strict(value)
def store_valid_response(self, response):
- self.__attributes = response.get_attributes()
- self.__friendlyname_attributes = response.get_friendlyname_attributes()
- self.__nameid = response.get_nameid()
- self.__nameid_format = response.get_nameid_format()
- self.__nameid_nq = response.get_nameid_nq()
- self.__nameid_spnq = response.get_nameid_spnq()
- self.__session_index = response.get_session_index()
- self.__session_expiration = response.get_session_not_on_or_after()
- self.__last_message_id = response.get_id()
- self.__last_assertion_id = response.get_assertion_id()
- self.__last_authn_contexts = response.get_authn_contexts()
- self.__authenticated = True
- self.__last_assertion_not_on_or_after = response.get_assertion_not_on_or_after()
+ self._attributes = response.get_attributes()
+ self._friendlyname_attributes = response.get_friendlyname_attributes()
+ self._nameid = response.get_nameid()
+ self._nameid_format = response.get_nameid_format()
+ self._nameid_nq = response.get_nameid_nq()
+ self._nameid_spnq = response.get_nameid_spnq()
+ self._session_index = response.get_session_index()
+ self._session_expiration = response.get_session_not_on_or_after()
+ self._last_message_id = response.get_id()
+ self._last_assertion_id = response.get_assertion_id()
+ self._last_authn_contexts = response.get_authn_contexts()
+ self._authenticated = True
+ self._last_assertion_not_on_or_after = response.get_assertion_not_on_or_after()
def process_response(self, request_id=None):
"""
@@ -118,22 +118,22 @@ def process_response(self, request_id=None):
:raises: OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND, when a POST with a SAMLResponse is not found
"""
- self.__errors = []
- self.__error_reason = None
+ self._errors = []
+ self._error_reason = None
- if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data['post_data']:
+ if 'post_data' in self._request_data and 'SAMLResponse' in self._request_data['post_data']:
# AuthnResponse -- HTTP_POST Binding
- response = self.response_class(self.__settings, self.__request_data['post_data']['SAMLResponse'])
- self.__last_response = response.get_xml_document()
+ response = self.response_class(self._settings, self._request_data['post_data']['SAMLResponse'])
+ self._last_response = response.get_xml_document()
- if response.is_valid(self.__request_data, request_id):
+ if response.is_valid(self._request_data, request_id):
self.store_valid_response(response)
else:
- self.__errors.append('invalid_response')
- self.__error_reason = response.get_error()
+ self._errors.append('invalid_response')
+ self._error_reason = response.get_error()
else:
- self.__errors.append('invalid_binding')
+ self._errors.append('invalid_binding')
raise OneLogin_Saml2_Error(
'SAML Response not found, Only supported HTTP_POST Binding',
OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND
@@ -151,57 +151,57 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
:returns: Redirection url
"""
- self.__errors = []
- self.__error_reason = None
+ self._errors = []
+ self._error_reason = None
- get_data = 'get_data' in self.__request_data and self.__request_data['get_data']
+ get_data = 'get_data' in self._request_data and self._request_data['get_data']
if get_data and 'SAMLResponse' in get_data:
- logout_response = self.logout_response_class(self.__settings, get_data['SAMLResponse'])
- self.__last_response = logout_response.get_xml()
+ logout_response = self.logout_response_class(self._settings, get_data['SAMLResponse'])
+ self._last_response = logout_response.get_xml()
if not self.validate_response_signature(get_data):
- self.__errors.append('invalid_logout_response_signature')
- self.__errors.append('Signature validation failed. Logout Response rejected')
- elif not logout_response.is_valid(self.__request_data, request_id):
- self.__errors.append('invalid_logout_response')
- self.__error_reason = logout_response.get_error()
+ self._errors.append('invalid_logout_response_signature')
+ self._errors.append('Signature validation failed. Logout Response rejected')
+ elif not logout_response.is_valid(self._request_data, request_id):
+ self._errors.append('invalid_logout_response')
+ self._error_reason = logout_response.get_error()
elif logout_response.get_status() != OneLogin_Saml2_Constants.STATUS_SUCCESS:
- self.__errors.append('logout_not_success')
+ self._errors.append('logout_not_success')
else:
- self.__last_message_id = logout_response.id
+ self._last_message_id = logout_response.id
if not keep_local_session:
OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
elif get_data and 'SAMLRequest' in get_data:
- logout_request = self.logout_request_class(self.__settings, get_data['SAMLRequest'])
- self.__last_request = logout_request.get_xml()
+ logout_request = self.logout_request_class(self._settings, get_data['SAMLRequest'])
+ self._last_request = logout_request.get_xml()
if not self.validate_request_signature(get_data):
- self.__errors.append("invalid_logout_request_signature")
- self.__errors.append('Signature validation failed. Logout Request rejected')
- elif not logout_request.is_valid(self.__request_data):
- self.__errors.append('invalid_logout_request')
- self.__error_reason = logout_request.get_error()
+ self._errors.append("invalid_logout_request_signature")
+ self._errors.append('Signature validation failed. Logout Request rejected')
+ elif not logout_request.is_valid(self._request_data):
+ self._errors.append('invalid_logout_request')
+ self._error_reason = logout_request.get_error()
else:
if not keep_local_session:
OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
in_response_to = logout_request.id
- self.__last_message_id = logout_request.id
- response_builder = self.logout_response_class(self.__settings)
+ self._last_message_id = logout_request.id
+ response_builder = self.logout_response_class(self._settings)
response_builder.build(in_response_to)
- self.__last_response = response_builder.get_xml()
+ self._last_response = response_builder.get_xml()
logout_response = response_builder.get_response()
parameters = {'SAMLResponse': logout_response}
- if 'RelayState' in self.__request_data['get_data']:
- parameters['RelayState'] = self.__request_data['get_data']['RelayState']
+ if 'RelayState' in self._request_data['get_data']:
+ parameters['RelayState'] = self._request_data['get_data']['RelayState']
- security = self.__settings.get_security_data()
+ security = self._settings.get_security_data()
if security['logoutResponseSigned']:
self.add_response_signature(parameters, security['signatureAlgorithm'])
return self.redirect_to(self.get_slo_response_url(), parameters)
else:
- self.__errors.append('invalid_binding')
+ self._errors.append('invalid_binding')
raise OneLogin_Saml2_Error(
'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding',
OneLogin_Saml2_Error.SAML_LOGOUTMESSAGE_NOT_FOUND
@@ -218,9 +218,9 @@ def redirect_to(self, url=None, parameters={}):
:returns: Redirection URL
"""
- if url is None and 'RelayState' in self.__request_data['get_data']:
- url = self.__request_data['get_data']['RelayState']
- return OneLogin_Saml2_Utils.redirect(url, parameters, request_data=self.__request_data)
+ if url is None and 'RelayState' in self._request_data['get_data']:
+ url = self._request_data['get_data']['RelayState']
+ return OneLogin_Saml2_Utils.redirect(url, parameters, request_data=self._request_data)
def is_authenticated(self):
"""
@@ -229,7 +229,7 @@ def is_authenticated(self):
:returns: True if is authenticated, False if not
:rtype: bool
"""
- return self.__authenticated
+ return self._authenticated
def get_attributes(self):
"""
@@ -238,7 +238,7 @@ def get_attributes(self):
:returns: SAML attributes
:rtype: dict
"""
- return self.__attributes
+ return self._attributes
def get_friendlyname_attributes(self):
"""
@@ -247,7 +247,7 @@ def get_friendlyname_attributes(self):
:returns: SAML attributes
:rtype: dict
"""
- return self.__friendlyname_attributes
+ return self._friendlyname_attributes
def get_nameid(self):
"""
@@ -256,7 +256,7 @@ def get_nameid(self):
:returns: NameID
:rtype: string|None
"""
- return self.__nameid
+ return self._nameid
def get_nameid_format(self):
"""
@@ -265,7 +265,7 @@ def get_nameid_format(self):
:returns: NameID Format
:rtype: string|None
"""
- return self.__nameid_format
+ return self._nameid_format
def get_nameid_nq(self):
"""
@@ -274,7 +274,7 @@ def get_nameid_nq(self):
:returns: NameID NameQualifier
:rtype: string|None
"""
- return self.__nameid_nq
+ return self._nameid_nq
def get_nameid_spnq(self):
"""
@@ -283,7 +283,7 @@ def get_nameid_spnq(self):
:returns: NameID SP NameQualifier
:rtype: string|None
"""
- return self.__nameid_spnq
+ return self._nameid_spnq
def get_session_index(self):
"""
@@ -291,7 +291,7 @@ def get_session_index(self):
:returns: The SessionIndex of the assertion
:rtype: string
"""
- return self.__session_index
+ return self._session_index
def get_session_expiration(self):
"""
@@ -299,14 +299,14 @@ def get_session_expiration(self):
:returns: The SessionNotOnOrAfter of the assertion
:rtype: unix/posix timestamp|None
"""
- return self.__session_expiration
+ return self._session_expiration
def get_last_assertion_not_on_or_after(self):
"""
The NotOnOrAfter value of the valid SubjectConfirmationData node
(if any) of the last assertion processed
"""
- return self.__last_assertion_not_on_or_after
+ return self._last_assertion_not_on_or_after
def get_errors(self):
"""
@@ -315,7 +315,7 @@ def get_errors(self):
:returns: List of errors
:rtype: list
"""
- return self.__errors
+ return self._errors
def get_last_error_reason(self):
"""
@@ -324,7 +324,7 @@ def get_last_error_reason(self):
:returns: Reason of the last error
:rtype: None | string
"""
- return self.__error_reason
+ return self._error_reason
def get_attribute(self, name):
"""
@@ -337,7 +337,7 @@ def get_attribute(self, name):
:rtype: list
"""
assert isinstance(name, compat.str_type)
- return self.__attributes.get(name)
+ return self._attributes.get(name)
def get_friendlyname_attribute(self, friendlyname):
"""
@@ -350,35 +350,35 @@ def get_friendlyname_attribute(self, friendlyname):
:rtype: list
"""
assert isinstance(friendlyname, compat.str_type)
- return self.__friendlyname_attributes.get(friendlyname)
+ return self._friendlyname_attributes.get(friendlyname)
def get_last_request_id(self):
"""
:returns: The ID of the last Request SAML message generated.
:rtype: string
"""
- return self.__last_request_id
+ return self._last_request_id
def get_last_message_id(self):
"""
:returns: The ID of the last Response SAML message processed.
:rtype: string
"""
- return self.__last_message_id
+ return self._last_message_id
def get_last_assertion_id(self):
"""
:returns: The ID of the last assertion processed.
:rtype: string
"""
- return self.__last_assertion_id
+ return self._last_assertion_id
def get_last_authn_contexts(self):
"""
:returns: The list of authentication contexts sent in the last SAML Response.
:rtype: list
"""
- return self.__last_authn_contexts
+ return self._last_authn_contexts
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
"""
@@ -402,9 +402,9 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
:returns: Redirection URL
:rtype: string
"""
- authn_request = self.authn_request_class(self.__settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
- self.__last_request = authn_request.get_xml()
- self.__last_request_id = authn_request.get_id()
+ authn_request = self.authn_request_class(self._settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
+ self._last_request = authn_request.get_xml()
+ self._last_request_id = authn_request.get_id()
saml_request = authn_request.get_request()
parameters = {'SAMLRequest': saml_request}
@@ -412,9 +412,9 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
if return_to is not None:
parameters['RelayState'] = return_to
else:
- parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self.__request_data)
+ parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self._request_data)
- security = self.__settings.get_security_data()
+ security = self._settings.get_security_data()
if security.get('authnRequestsSigned', False):
self.add_request_signature(parameters, security['signatureAlgorithm'])
return self.redirect_to(self.get_sso_url(), parameters)
@@ -450,30 +450,30 @@ def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name
OneLogin_Saml2_Error.SAML_SINGLE_LOGOUT_NOT_SUPPORTED
)
- if name_id is None and self.__nameid is not None:
- name_id = self.__nameid
+ if name_id is None and self._nameid is not None:
+ name_id = self._nameid
- if name_id_format is None and self.__nameid_format is not None:
- name_id_format = self.__nameid_format
+ if name_id_format is None and self._nameid_format is not None:
+ name_id_format = self._nameid_format
logout_request = self.logout_request_class(
- self.__settings,
+ self._settings,
name_id=name_id,
session_index=session_index,
nq=nq,
name_id_format=name_id_format,
spnq=spnq
)
- self.__last_request = logout_request.get_xml()
- self.__last_request_id = logout_request.id
+ self._last_request = logout_request.get_xml()
+ self._last_request_id = logout_request.id
parameters = {'SAMLRequest': logout_request.get_request()}
if return_to is not None:
parameters['RelayState'] = return_to
else:
- parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self.__request_data)
+ parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self._request_data)
- security = self.__settings.get_security_data()
+ security = self._settings.get_security_data()
if security.get('logoutRequestSigned', False):
self.add_request_signature(parameters, security['signatureAlgorithm'])
return self.redirect_to(slo_url, parameters)
@@ -485,7 +485,7 @@ def get_sso_url(self):
:returns: An URL, the SSO endpoint of the IdP
:rtype: string
"""
- return self.__settings.get_idp_sso_url()
+ return self._settings.get_idp_sso_url()
def get_slo_url(self):
"""
@@ -494,7 +494,7 @@ def get_slo_url(self):
:returns: An URL, the SLO endpoint of the IdP
:rtype: string
"""
- return self.__settings.get_idp_slo_url()
+ return self._settings.get_idp_slo_url()
def get_slo_response_url(self):
"""
@@ -503,7 +503,7 @@ def get_slo_response_url(self):
:returns: an URL, the SLO return endpoint of the IdP
:rtype: string
"""
- return self.__settings.get_idp_slo_response_url()
+ return self._settings.get_idp_slo_response_url()
def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
"""
@@ -515,7 +515,7 @@ def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Cons
:param sign_algorithm: Signature algorithm method
:type sign_algorithm: string
"""
- return self.__build_signature(request_data, 'SAMLRequest', sign_algorithm)
+ return self._build_signature(request_data, 'SAMLRequest', sign_algorithm)
def add_response_signature(self, response_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
"""
@@ -526,10 +526,10 @@ def add_response_signature(self, response_data, sign_algorithm=OneLogin_Saml2_Co
:param sign_algorithm: Signature algorithm method
:type sign_algorithm: string
"""
- return self.__build_signature(response_data, 'SAMLResponse', sign_algorithm)
+ return self._build_signature(response_data, 'SAMLResponse', sign_algorithm)
@staticmethod
- def __build_sign_query_from_qs(query_string, saml_type):
+ def _build_sign_query_from_qs(query_string, saml_type):
"""
Build sign query from query string
@@ -545,7 +545,7 @@ def __build_sign_query_from_qs(query_string, saml_type):
return '&'.join(part for arg in args for part in parts if part.startswith(arg))
@staticmethod
- def __build_sign_query(saml_data, relay_state, algorithm, saml_type, lowercase_urlencoding=False):
+ def _build_sign_query(saml_data, relay_state, algorithm, saml_type, lowercase_urlencoding=False):
"""
Build sign query
@@ -570,7 +570,7 @@ def __build_sign_query(saml_data, relay_state, algorithm, saml_type, lowercase_u
sign_data.append('SigAlg=%s' % OneLogin_Saml2_Utils.escape_url(algorithm, lowercase_urlencoding))
return '&'.join(sign_data)
- def __build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
+ def _build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
"""
Builds the Signature
:param data: The Request data
@@ -591,10 +591,10 @@ def __build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Const
OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND
)
- msg = self.__build_sign_query(data[saml_type],
- data.get('RelayState', None),
- sign_algorithm,
- saml_type)
+ msg = self._build_sign_query(data[saml_type],
+ data.get('RelayState', None),
+ sign_algorithm,
+ saml_type)
sign_algorithm_transform_map = {
OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1,
@@ -605,7 +605,7 @@ def __build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Const
}
sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.Transform.RSA_SHA1)
- signature = OneLogin_Saml2_Utils.sign_binary(msg, key, sign_algorithm_transform, self.__settings.is_debug_active())
+ signature = OneLogin_Saml2_Utils.sign_binary(msg, key, sign_algorithm_transform, self._settings.is_debug_active())
data['Signature'] = OneLogin_Saml2_Utils.b64encode(signature)
data['SigAlg'] = sign_algorithm
@@ -618,7 +618,7 @@ def validate_request_signature(self, request_data):
"""
- return self.__validate_signature(request_data, 'SAMLRequest')
+ return self._validate_signature(request_data, 'SAMLRequest')
def validate_response_signature(self, request_data):
"""
@@ -629,9 +629,9 @@ def validate_response_signature(self, request_data):
"""
- return self.__validate_signature(request_data, 'SAMLResponse')
+ return self._validate_signature(request_data, 'SAMLResponse')
- def __validate_signature(self, data, saml_type, raise_exceptions=False):
+ def _validate_signature(self, data, saml_type, raise_exceptions=False):
"""
Validate Signature
@@ -650,7 +650,7 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
try:
signature = data.get('Signature', None)
if signature is None:
- if self.__settings.is_strict() and self.__settings.get_security_data().get('wantMessagesSigned', False):
+ if self._settings.is_strict() and self._settings.get_security_data().get('wantMessagesSigned', False):
raise OneLogin_Saml2_ValidationError(
'The %s is not signed. Rejected.' % saml_type,
OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE
@@ -666,7 +666,7 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
if not (exists_x509cert or exists_multix509sign):
error_msg = 'In order to validate the sign on the %s, the x509cert of the IdP is required' % saml_type
- self.__errors.append(error_msg)
+ self._errors.append(error_msg)
raise OneLogin_Saml2_Error(
error_msg,
OneLogin_Saml2_Error.CERT_NOT_FOUND
@@ -676,16 +676,16 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
if isinstance(sign_alg, bytes):
sign_alg = sign_alg.decode('utf8')
- query_string = self.__request_data.get('query_string')
- if query_string and self.__request_data.get('validate_signature_from_qs'):
- signed_query = self.__build_sign_query_from_qs(query_string, saml_type)
+ query_string = self._request_data.get('query_string')
+ if query_string and self._request_data.get('validate_signature_from_qs'):
+ signed_query = self._build_sign_query_from_qs(query_string, saml_type)
else:
- lowercase_urlencoding = self.__request_data.get('lowercase_urlencoding', False)
- signed_query = self.__build_sign_query(data[saml_type],
- data.get('RelayState'),
- sign_alg,
- saml_type,
- lowercase_urlencoding)
+ lowercase_urlencoding = self._request_data.get('lowercase_urlencoding', False)
+ signed_query = self._build_sign_query(data[saml_type],
+ data.get('RelayState'),
+ sign_alg,
+ saml_type,
+ lowercase_urlencoding)
if exists_multix509sign:
for cert in idp_data['x509certMulti']['signing']:
@@ -705,14 +705,14 @@ def __validate_signature(self, data, saml_type, raise_exceptions=False):
OneLogin_Saml2_Utils.b64decode(signature),
cert,
sign_alg,
- self.__settings.is_debug_active()):
+ self._settings.is_debug_active()):
raise OneLogin_Saml2_ValidationError(
'Signature validation failed. %s rejected' % saml_type,
OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
)
return True
except Exception as e:
- self.__error_reason = str(e)
+ self._error_reason = str(e)
if raise_exceptions:
raise e
return False
@@ -725,11 +725,11 @@ def get_last_response_xml(self, pretty_print_if_possible=False):
:rtype: string|None
"""
response = None
- if self.__last_response is not None:
- if isinstance(self.__last_response, compat.str_type):
- response = self.__last_response
+ if self._last_response is not None:
+ if isinstance(self._last_response, compat.str_type):
+ response = self._last_response
else:
- response = tostring(self.__last_response, encoding='unicode', pretty_print=pretty_print_if_possible)
+ response = tostring(self._last_response, encoding='unicode', pretty_print=pretty_print_if_possible)
return response
def get_last_request_xml(self):
@@ -738,4 +738,4 @@ def get_last_request_xml(self):
:returns: SAML request XML
:rtype: string|None
"""
- return self.__last_request or None
+ return self._last_request or None
diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py
index cdbf2c44..0aa1623d 100644
--- a/src/onelogin/saml2/authn_request.py
+++ b/src/onelogin/saml2/authn_request.py
@@ -41,13 +41,13 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
:param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated
:type name_id_value_req: string
"""
- self.__settings = settings
+ self._settings = settings
- sp_data = self.__settings.get_sp_data()
- idp_data = self.__settings.get_idp_data()
- security = self.__settings.get_security_data()
+ sp_data = self._settings.get_sp_data()
+ idp_data = self._settings.get_idp_data()
+ security = self._settings.get_security_data()
- self.__id = self._generate_request_id()
+ self._id = self._generate_request_id()
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
destination = idp_data['singleSignOnService']['url']
@@ -112,7 +112,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
request = OneLogin_Saml2_Templates.AUTHN_REQUEST % \
{
- 'id': self.__id,
+ 'id': self._id,
'provider_name': provider_name_str,
'force_authn_str': force_authn_str,
'is_passive_str': is_passive_str,
@@ -127,7 +127,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
'acs_binding': sp_data['assertionConsumerService'].get('binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST')
}
- self.__authn_request = request
+ self._authn_request = request
def _generate_request_id(self):
"""
@@ -144,9 +144,9 @@ def get_request(self, deflate=True):
:rtype: str object
"""
if deflate:
- request = OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__authn_request)
+ request = OneLogin_Saml2_Utils.deflate_and_base64_encode(self._authn_request)
else:
- request = OneLogin_Saml2_Utils.b64encode(self.__authn_request)
+ request = OneLogin_Saml2_Utils.b64encode(self._authn_request)
return request
def get_id(self):
@@ -155,7 +155,7 @@ def get_id(self):
:return: AuthNRequest ID
:rtype: string
"""
- return self.__id
+ return self._id
def get_xml(self):
"""
@@ -163,4 +163,4 @@ def get_xml(self):
:return: XML request body
:rtype: string
"""
- return self.__authn_request
+ return self._authn_request
diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py
index 3ed3fb15..84cdd86b 100644
--- a/src/onelogin/saml2/logout_request.py
+++ b/src/onelogin/saml2/logout_request.py
@@ -50,14 +50,14 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
:param spnq: SP Name Qualifier
:type: string
"""
- self.__settings = settings
- self.__error = None
+ self._settings = settings
+ self._error = None
self.id = None
if request is None:
- sp_data = self.__settings.get_sp_data()
- idp_data = self.__settings.get_idp_data()
- security = self.__settings.get_security_data()
+ sp_data = self._settings.get_sp_data()
+ idp_data = self._settings.get_idp_data()
+ security = self._settings.get_security_data()
self.id = self._generate_request_id()
@@ -71,7 +71,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
if exists_multix509enc:
cert = idp_data['x509certMulti']['encryption'][0]
else:
- cert = self.__settings.get_idp_cert()
+ cert = self._settings.get_idp_cert()
if name_id is not None:
if not name_id_format and sp_data['NameIDFormat'] != OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED:
@@ -109,7 +109,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
{
'id': self.id,
'issue_instant': issue_instant,
- 'single_logout_url': self.__settings.get_idp_slo_url(),
+ 'single_logout_url': self._settings.get_idp_slo_url(),
'entity_id': sp_data['entityId'],
'name_id': name_id_obj,
'session_index': session_index_str,
@@ -118,7 +118,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None, nq=
logout_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(request, ignore_zip=True)
self.id = self.get_id(logout_request)
- self.__logout_request = compat.to_string(logout_request)
+ self._logout_request = compat.to_string(logout_request)
def get_request(self, deflate=True):
"""
@@ -129,9 +129,9 @@ def get_request(self, deflate=True):
:rtype: str object
"""
if deflate:
- request = OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__logout_request)
+ request = OneLogin_Saml2_Utils.deflate_and_base64_encode(self._logout_request)
else:
- request = OneLogin_Saml2_Utils.b64encode(self.__logout_request)
+ request = OneLogin_Saml2_Utils.b64encode(self._logout_request)
return request
def get_xml(self):
@@ -141,7 +141,7 @@ def get_xml(self):
:return: XML request body
:rtype: string
"""
- return self.__logout_request
+ return self._logout_request
@classmethod
def get_id(cls, request):
@@ -279,24 +279,24 @@ def is_valid(self, request_data, raise_exceptions=False):
:return: If the Logout Request is or not valid
:rtype: boolean
"""
- self.__error = None
+ self._error = None
try:
- root = OneLogin_Saml2_XML.to_etree(self.__logout_request)
+ root = OneLogin_Saml2_XML.to_etree(self._logout_request)
- idp_data = self.__settings.get_idp_data()
+ idp_data = self._settings.get_idp_data()
idp_entity_id = idp_data['entityId']
get_data = ('get_data' in request_data and request_data['get_data']) or dict()
- if self.__settings.is_strict():
- res = OneLogin_Saml2_XML.validate_xml(root, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
+ if self._settings.is_strict():
+ res = OneLogin_Saml2_XML.validate_xml(root, 'saml-schema-protocol-2.0.xsd', self._settings.is_debug_active())
if isinstance(res, str):
raise OneLogin_Saml2_ValidationError(
'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd',
OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
)
- security = self.__settings.get_security_data()
+ security = self._settings.get_security_data()
current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
@@ -345,8 +345,8 @@ def is_valid(self, request_data, raise_exceptions=False):
return True
except Exception as err:
# pylint: disable=R0801
- self.__error = str(err)
- debug = self.__settings.is_debug_active()
+ self._error = str(err)
+ debug = self._settings.is_debug_active()
if debug:
print(err)
if raise_exceptions:
@@ -357,7 +357,7 @@ def get_error(self):
"""
After executing a validation process, if it fails this method returns the cause
"""
- return self.__error
+ return self._error
def _generate_request_id(self):
"""
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index bd942200..e9368e8a 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -33,13 +33,13 @@ def __init__(self, settings, response=None):
* (string) response. An UUEncoded SAML Logout
response from the IdP.
"""
- self.__settings = settings
- self.__error = None
+ self._settings = settings
+ self._error = None
self.id = None
if response is not None:
- self.__logout_response = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(response, ignore_zip=True))
- self.document = OneLogin_Saml2_XML.to_etree(self.__logout_response)
+ self._logout_response = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(response, ignore_zip=True))
+ self.document = OneLogin_Saml2_XML.to_etree(self._logout_response)
self.id = self.document.get('ID', None)
def get_issuer(self):
@@ -49,7 +49,7 @@ def get_issuer(self):
:rtype: string
"""
issuer = None
- issuer_nodes = self.__query('/samlp:LogoutResponse/saml:Issuer')
+ issuer_nodes = self._query('/samlp:LogoutResponse/saml:Issuer')
if len(issuer_nodes) == 1:
issuer = OneLogin_Saml2_XML.element_text(issuer_nodes[0])
return issuer
@@ -60,7 +60,7 @@ def get_status(self):
:return: The Status
:rtype: string
"""
- entries = self.__query('/samlp:LogoutResponse/samlp:Status/samlp:StatusCode')
+ entries = self._query('/samlp:LogoutResponse/samlp:Status/samlp:StatusCode')
if len(entries) == 0:
return None
status = entries[0].attrib['Value']
@@ -78,21 +78,21 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
:return: Returns if the SAML LogoutResponse is or not valid
:rtype: boolean
"""
- self.__error = None
+ self._error = None
try:
- idp_data = self.__settings.get_idp_data()
+ idp_data = self._settings.get_idp_data()
idp_entity_id = idp_data['entityId']
get_data = request_data['get_data']
- if self.__settings.is_strict():
- res = OneLogin_Saml2_XML.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
+ if self._settings.is_strict():
+ res = OneLogin_Saml2_XML.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self._settings.is_debug_active())
if isinstance(res, str):
raise OneLogin_Saml2_ValidationError(
'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd',
OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
)
- security = self.__settings.get_security_data()
+ security = self._settings.get_security_data()
# Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided
in_response_to = self.get_in_response_to()
@@ -134,15 +134,15 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
return True
# pylint: disable=R0801
except Exception as err:
- self.__error = str(err)
- debug = self.__settings.is_debug_active()
+ self._error = str(err)
+ debug = self._settings.is_debug_active()
if debug:
print(err)
if raise_exceptions:
raise
return False
- def __query(self, query):
+ def _query(self, query):
"""
Extracts a node from the Etree (Logout Response Message)
:param query: Xpath Expression
@@ -158,7 +158,7 @@ def build(self, in_response_to):
:param in_response_to: InResponseTo value for the Logout Response.
:type in_response_to: string
"""
- sp_data = self.__settings.get_sp_data()
+ sp_data = self._settings.get_sp_data()
self.id = self._generate_request_id()
@@ -168,13 +168,13 @@ def build(self, in_response_to):
{
'id': self.id,
'issue_instant': issue_instant,
- 'destination': self.__settings.get_idp_slo_response_url(),
+ 'destination': self._settings.get_idp_slo_response_url(),
'in_response_to': in_response_to,
'entity_id': sp_data['entityId'],
'status': "urn:oasis:names:tc:SAML:2.0:status:Success"
}
- self.__logout_response = logout_response
+ self._logout_response = logout_response
def get_in_response_to(self):
"""
@@ -193,16 +193,16 @@ def get_response(self, deflate=True):
:rtype: string
"""
if deflate:
- response = OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__logout_response)
+ response = OneLogin_Saml2_Utils.deflate_and_base64_encode(self._logout_response)
else:
- response = OneLogin_Saml2_Utils.b64encode(self.__logout_response)
+ response = OneLogin_Saml2_Utils.b64encode(self._logout_response)
return response
def get_error(self):
"""
After executing a validation process, if it fails this method returns the cause
"""
- return self.__error
+ return self._error
def get_xml(self):
"""
@@ -211,7 +211,7 @@ def get_xml(self):
:return: XML response body
:rtype: string
"""
- return self.__logout_response
+ return self._logout_response
def _generate_request_id(self):
"""
diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py
index bd839c58..34c5f2c1 100644
--- a/src/onelogin/saml2/metadata.py
+++ b/src/onelogin/saml2/metadata.py
@@ -218,7 +218,7 @@ def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.R
return OneLogin_Saml2_Utils.add_sign(metadata, key, cert, False, sign_algorithm, digest_algorithm)
@staticmethod
- def __add_x509_key_descriptors(root, cert, signing):
+ def _add_x509_key_descriptors(root, cert, signing):
key_descriptor = OneLogin_Saml2_XML.make_child(root, '{%s}KeyDescriptor' % OneLogin_Saml2_Constants.NS_MD)
root.remove(key_descriptor)
root.insert(0, key_descriptor)
@@ -261,6 +261,6 @@ def add_x509_key_descriptors(cls, metadata, cert=None, add_encryption=True):
raise Exception('Malformed metadata.')
if add_encryption:
- cls.__add_x509_key_descriptors(sp_sso_descriptor, cert, False)
- cls.__add_x509_key_descriptors(sp_sso_descriptor, cert, True)
+ cls._add_x509_key_descriptors(sp_sso_descriptor, cert, False)
+ cls._add_x509_key_descriptors(sp_sso_descriptor, cert, True)
return OneLogin_Saml2_XML.to_string(root)
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index d69242d3..7d372ab6 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -33,8 +33,8 @@ def __init__(self, settings, response):
:param response: The base64 encoded, XML string containing the samlp:Response
:type response: string
"""
- self.__settings = settings
- self.__error = None
+ self._settings = settings
+ self._error = None
self.response = OneLogin_Saml2_Utils.b64decode(response)
self.document = OneLogin_Saml2_XML.to_etree(self.response)
self.decrypted_document = None
@@ -42,11 +42,11 @@ def __init__(self, settings, response):
self.valid_scd_not_on_or_after = None
# Quick check for the presence of EncryptedAssertion
- encrypted_assertion_nodes = self.__query('/samlp:Response/saml:EncryptedAssertion')
+ encrypted_assertion_nodes = self._query('/samlp:Response/saml:EncryptedAssertion')
if encrypted_assertion_nodes:
decrypted_document = deepcopy(self.document)
self.encrypted = True
- self.decrypted_document = self.__decrypt_assertion(decrypted_document)
+ self.decrypted_document = self._decrypt_assertion(decrypted_document)
def is_valid(self, request_data, request_id=None, raise_exceptions=False):
"""
@@ -64,7 +64,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
:returns: True if the SAML Response is valid, False if not
:rtype: bool
"""
- self.__error = None
+ self._error = None
try:
# Checks SAML version
if self.document.get('Version', None) != '2.0':
@@ -90,9 +90,9 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS
)
- idp_data = self.__settings.get_idp_data()
+ idp_data = self._settings.get_idp_data()
idp_entity_id = idp_data['entityId']
- sp_data = self.__settings.get_sp_data()
+ sp_data = self._settings.get_sp_data()
sp_entity_id = sp_data['entityId']
signed_elements = self.process_signed_elements()
@@ -100,9 +100,9 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements
has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements
- if self.__settings.is_strict():
+ if self._settings.is_strict():
no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
- res = OneLogin_Saml2_XML.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
+ res = OneLogin_Saml2_XML.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self._settings.is_debug_active())
if isinstance(res, str):
raise OneLogin_Saml2_ValidationError(
no_valid_xml_msg,
@@ -111,14 +111,14 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# If encrypted, check also the decrypted document
if self.encrypted:
- res = OneLogin_Saml2_XML.validate_xml(self.decrypted_document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
+ res = OneLogin_Saml2_XML.validate_xml(self.decrypted_document, 'saml-schema-protocol-2.0.xsd', self._settings.is_debug_active())
if isinstance(res, str):
raise OneLogin_Saml2_ValidationError(
no_valid_xml_msg,
OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
)
- security = self.__settings.get_security_data()
+ security = self._settings.get_security_data()
current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
# Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
@@ -137,7 +137,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
)
if security['wantNameIdEncrypted']:
- encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
+ encrypted_nameid_nodes = self._query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
if len(encrypted_nameid_nodes) != 1:
raise OneLogin_Saml2_ValidationError(
'The NameID of the Response is not encrypted and the SP require it',
@@ -174,14 +174,14 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
)
# Checks that there is at least one AttributeStatement if required
- attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement')
+ attribute_statement_nodes = self._query_assertion('/saml:AttributeStatement')
if security.get('wantAttributeStatement', True) and not attribute_statement_nodes:
raise OneLogin_Saml2_ValidationError(
'There is no AttributeStatement on the Response',
OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT
)
- encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute')
+ encrypted_attributes_nodes = self._query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute')
if encrypted_attributes_nodes:
raise OneLogin_Saml2_ValidationError(
'There is an EncryptedAttribute in the Response and this SP not support them',
@@ -236,7 +236,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
# Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
any_subject_confirmation = False
- subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation')
+ subject_confirmation_nodes = self._query_assertion('/saml:Subject/saml:SubjectConfirmation')
for scn in subject_confirmation_nodes:
method = scn.get('Method', None)
@@ -293,7 +293,7 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND
)
else:
- cert = self.__settings.get_idp_cert()
+ cert = self._settings.get_idp_cert()
fingerprint = idp_data.get('certFingerprint', None)
if fingerprint:
fingerprint = OneLogin_Saml2_Utils.format_finger_print(fingerprint)
@@ -319,8 +319,8 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
return True
except Exception as err:
- self.__error = str(err)
- debug = self.__settings.is_debug_active()
+ self._error = str(err)
+ debug = self._settings.is_debug_active()
if debug:
print(err)
if raise_exceptions:
@@ -351,7 +351,7 @@ def check_one_condition(self):
"""
Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique.
"""
- condition_nodes = self.__query_assertion('/saml:Conditions')
+ condition_nodes = self._query_assertion('/saml:Conditions')
if len(condition_nodes) == 1:
return True
else:
@@ -361,7 +361,7 @@ def check_one_authnstatement(self):
"""
Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique.
"""
- authnstatement_nodes = self.__query_assertion('/saml:AuthnStatement')
+ authnstatement_nodes = self._query_assertion('/saml:AuthnStatement')
if len(authnstatement_nodes) == 1:
return True
else:
@@ -374,7 +374,7 @@ def get_audiences(self):
:returns: The valid audiences for the SAML Response
:rtype: list
"""
- audience_nodes = self.__query_assertion('/saml:Conditions/saml:AudienceRestriction/saml:Audience')
+ audience_nodes = self._query_assertion('/saml:Conditions/saml:AudienceRestriction/saml:Audience')
return [OneLogin_Saml2_XML.element_text(node) for node in audience_nodes if OneLogin_Saml2_XML.element_text(node) is not None]
def get_authn_contexts(self):
@@ -384,7 +384,7 @@ def get_authn_contexts(self):
:returns: The authentication classes for the SAML Response
:rtype: list
"""
- authn_context_nodes = self.__query_assertion('/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef')
+ authn_context_nodes = self._query_assertion('/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef')
return [OneLogin_Saml2_XML.element_text(node) for node in authn_context_nodes]
def get_in_response_to(self):
@@ -416,7 +416,7 @@ def get_issuers(self):
OneLogin_Saml2_ValidationError.ISSUER_MULTIPLE_IN_RESPONSE
)
- assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
+ assertion_issuer_nodes = self._query_assertion('/saml:Issuer')
if len(assertion_issuer_nodes) == 1:
issuer_value = OneLogin_Saml2_XML.element_text(assertion_issuer_nodes[0])
if issuer_value:
@@ -439,18 +439,18 @@ def get_nameid_data(self):
nameid = None
nameid_data = {}
- encrypted_id_data_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
+ encrypted_id_data_nodes = self._query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
if encrypted_id_data_nodes:
encrypted_data = encrypted_id_data_nodes[0]
- key = self.__settings.get_sp_key()
+ key = self._settings.get_sp_key()
nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key)
else:
- nameid_nodes = self.__query_assertion('/saml:Subject/saml:NameID')
+ nameid_nodes = self._query_assertion('/saml:Subject/saml:NameID')
if nameid_nodes:
nameid = nameid_nodes[0]
- is_strict = self.__settings.is_strict()
- want_nameid = self.__settings.get_security_data().get('wantNameId', True)
+ is_strict = self._settings.is_strict()
+ want_nameid = self._settings.get_security_data().get('wantNameId', True)
if nameid is None:
if is_strict and want_nameid:
raise OneLogin_Saml2_ValidationError(
@@ -469,7 +469,7 @@ def get_nameid_data(self):
value = nameid.get(attr, None)
if value:
if is_strict and attr == 'SPNameQualifier':
- sp_data = self.__settings.get_sp_data()
+ sp_data = self._settings.get_sp_data()
sp_entity_id = sp_data.get('entityId', '')
if sp_entity_id != value:
raise OneLogin_Saml2_ValidationError(
@@ -541,7 +541,7 @@ def get_session_not_on_or_after(self):
:rtype: time|None
"""
not_on_or_after = None
- authn_statement_nodes = self.__query_assertion('/saml:AuthnStatement[@SessionNotOnOrAfter]')
+ authn_statement_nodes = self._query_assertion('/saml:AuthnStatement[@SessionNotOnOrAfter]')
if authn_statement_nodes:
not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(authn_statement_nodes[0].get('SessionNotOnOrAfter'))
return not_on_or_after
@@ -563,7 +563,7 @@ def get_session_index(self):
:rtype: string|None
"""
session_index = None
- authn_statement_nodes = self.__query_assertion('/saml:AuthnStatement[@SessionIndex]')
+ authn_statement_nodes = self._query_assertion('/saml:AuthnStatement[@SessionIndex]')
if authn_statement_nodes:
session_index = authn_statement_nodes[0].get('SessionIndex')
return session_index
@@ -583,9 +583,9 @@ def get_friendlyname_attributes(self):
return self._get_attributes('FriendlyName')
def _get_attributes(self, attr_name):
- allow_duplicates = self.__settings.get_security_data().get('allowRepeatAttributeName', False)
+ allow_duplicates = self._settings.get_security_data().get('allowRepeatAttributeName', False)
attributes = {}
- attribute_nodes = self.__query_assertion('/saml:AttributeStatement/saml:Attribute')
+ attribute_nodes = self._query_assertion('/saml:AttributeStatement/saml:Attribute')
for attribute_node in attribute_nodes:
attr_key = attribute_node.get(attr_name)
if attr_key:
@@ -645,7 +645,7 @@ def process_signed_elements(self):
:returns: The signed elements tag names
:rtype: list
"""
- sign_nodes = self.__query('//ds:Signature')
+ sign_nodes = self._query('//ds:Signature')
signed_elements = []
verified_seis = []
@@ -737,7 +737,7 @@ def validate_signed_elements(self, signed_elements):
)
if assertion_tag in signed_elements:
- expected_signature_nodes = self.__query(OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)
+ expected_signature_nodes = self._query(OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)
if len(expected_signature_nodes) != 1:
raise OneLogin_Saml2_ValidationError(
'Unexpected number of Assertion signatures found. SAML Response rejected.',
@@ -754,7 +754,7 @@ def validate_timestamps(self):
:returns: True if the condition is valid, False otherwise
:rtype: bool
"""
- conditions_nodes = self.__query_assertion('/saml:Conditions')
+ conditions_nodes = self._query_assertion('/saml:Conditions')
for conditions_node in conditions_nodes:
nb_attr = conditions_node.get('NotBefore')
@@ -771,7 +771,7 @@ def validate_timestamps(self):
)
return True
- def __query_assertion(self, xpath_expr):
+ def _query_assertion(self, xpath_expr):
"""
Extracts nodes that match the query from the Assertion
@@ -785,13 +785,13 @@ def __query_assertion(self, xpath_expr):
assertion_expr = '/saml:Assertion'
signature_expr = '/ds:Signature/ds:SignedInfo/ds:Reference'
signed_assertion_query = '/samlp:Response' + assertion_expr + signature_expr
- assertion_reference_nodes = self.__query(signed_assertion_query)
+ assertion_reference_nodes = self._query(signed_assertion_query)
tagid = None
if not assertion_reference_nodes:
# Check if the message is signed
signed_message_query = '/samlp:Response' + signature_expr
- message_reference_nodes = self.__query(signed_message_query)
+ message_reference_nodes = self._query(signed_message_query)
if message_reference_nodes:
message_id = message_reference_nodes[0].get('URI')
final_query = "/samlp:Response[@ID=$tagid]/"
@@ -804,9 +804,9 @@ def __query_assertion(self, xpath_expr):
final_query = '/samlp:Response' + assertion_expr + "[@ID=$tagid]"
tagid = assertion_id[1:]
final_query += xpath_expr
- return self.__query(final_query, tagid)
+ return self._query(final_query, tagid)
- def __query(self, query, tagid=None):
+ def _query(self, query, tagid=None):
"""
Extracts nodes that match the query from the Response
@@ -825,7 +825,7 @@ def __query(self, query, tagid=None):
document = self.document
return OneLogin_Saml2_XML.query(document, query, None, tagid)
- def __decrypt_assertion(self, xml):
+ def _decrypt_assertion(self, xml):
"""
Decrypts the Assertion
@@ -835,8 +835,8 @@ def __decrypt_assertion(self, xml):
:returns: Decrypted Assertion
:rtype: Element
"""
- key = self.__settings.get_sp_key()
- debug = self.__settings.is_debug_active()
+ key = self._settings.get_sp_key()
+ debug = self._settings.is_debug_active()
if not key:
raise OneLogin_Saml2_Error(
@@ -885,7 +885,7 @@ def get_error(self):
"""
After executing a validation process, if it fails this method returns the cause
"""
- return self.__error
+ return self._error
def get_xml_document(self):
"""
@@ -916,4 +916,4 @@ def get_assertion_id(self):
'SAML Response must contain 1 assertion',
OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS
)
- return self.__query_assertion('')[0].get('ID', None)
+ return self._query_assertion('')[0].get('ID', None)
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 38e79041..3163995e 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -98,37 +98,37 @@ def __init__(self, settings=None, custom_base_path=None, sp_validation_only=Fals
:param sp_validation_only: Avoid the IdP validation
:type sp_validation_only: boolean
"""
- self.__sp_validation_only = sp_validation_only
- self.__paths = {}
- self.__strict = True
- self.__debug = False
- self.__sp = {}
- self.__idp = {}
- self.__security = {}
- self.__contacts = {}
- self.__organization = {}
- self.__errors = []
-
- self.__load_paths(base_path=custom_base_path)
- self.__update_paths(settings)
+ self._sp_validation_only = sp_validation_only
+ self._paths = {}
+ self._strict = True
+ self._debug = False
+ self._sp = {}
+ self._idp = {}
+ self._security = {}
+ self._contacts = {}
+ self._organization = {}
+ self._errors = []
+
+ self._load_paths(base_path=custom_base_path)
+ self._update_paths(settings)
if settings is None:
try:
- valid = self.__load_settings_from_file()
+ valid = self._load_settings_from_file()
except Exception as e:
raise e
if not valid:
raise OneLogin_Saml2_Error(
'Invalid dict settings at the file: %s',
OneLogin_Saml2_Error.SETTINGS_INVALID,
- ','.join(self.__errors)
+ ','.join(self._errors)
)
elif isinstance(settings, dict):
- if not self.__load_settings_from_dict(settings):
+ if not self._load_settings_from_dict(settings):
raise OneLogin_Saml2_Error(
'Invalid dict settings: %s',
OneLogin_Saml2_Error.SETTINGS_INVALID,
- ','.join(self.__errors)
+ ','.join(self._errors)
)
else:
raise OneLogin_Saml2_Error(
@@ -137,14 +137,14 @@ def __init__(self, settings=None, custom_base_path=None, sp_validation_only=Fals
)
self.format_idp_cert()
- if 'x509certMulti' in self.__idp:
+ if 'x509certMulti' in self._idp:
self.format_idp_cert_multi()
self.format_sp_cert()
- if 'x509certNew' in self.__sp:
+ if 'x509certNew' in self._sp:
self.format_sp_cert_new()
self.format_sp_key()
- def __load_paths(self, base_path=None):
+ def _load_paths(self, base_path=None):
"""
Set the paths of the different folders
"""
@@ -152,13 +152,13 @@ def __load_paths(self, base_path=None):
base_path = dirname(dirname(dirname(__file__)))
if not base_path.endswith(sep):
base_path += sep
- self.__paths = {
+ self._paths = {
'base': base_path,
'cert': base_path + 'certs' + sep,
'lib': dirname(__file__) + sep
}
- def __update_paths(self, settings):
+ def _update_paths(self, settings):
"""
Set custom paths if necessary
"""
@@ -168,7 +168,7 @@ def __update_paths(self, settings):
if 'custom_base_path' in settings:
base_path = settings['custom_base_path']
base_path = join(dirname(__file__), base_path)
- self.__load_paths(base_path)
+ self._load_paths(base_path)
def get_base_path(self):
"""
@@ -177,7 +177,7 @@ def get_base_path(self):
:return: The base toolkit folder path
:rtype: string
"""
- return self.__paths['base']
+ return self._paths['base']
def get_cert_path(self):
"""
@@ -186,13 +186,13 @@ def get_cert_path(self):
:return: The cert folder path
:rtype: string
"""
- return self.__paths['cert']
+ return self._paths['cert']
def set_cert_path(self, path):
"""
Set a new cert path
"""
- self.__paths['cert'] = path
+ self._paths['cert'] = path
def get_lib_path(self):
"""
@@ -201,7 +201,7 @@ def get_lib_path(self):
:return: The library folder path
:rtype: string
"""
- return self.__paths['lib']
+ return self._paths['lib']
def get_schemas_path(self):
"""
@@ -210,9 +210,9 @@ def get_schemas_path(self):
:return: The schema folder path
:rtype: string
"""
- return self.__paths['lib'] + 'schemas/'
+ return self._paths['lib'] + 'schemas/'
- def __load_settings_from_dict(self, settings):
+ def _load_settings_from_dict(self, settings):
"""
Loads settings info from a settings Dict
@@ -224,22 +224,22 @@ def __load_settings_from_dict(self, settings):
"""
errors = self.check_settings(settings)
if len(errors) == 0:
- self.__errors = []
- self.__sp = settings['sp']
- self.__idp = settings.get('idp', {})
- self.__strict = settings.get('strict', True)
- self.__debug = settings.get('debug', False)
- self.__security = settings.get('security', {})
- self.__contacts = settings.get('contactPerson', {})
- self.__organization = settings.get('organization', {})
-
- self.__add_default_values()
+ self._errors = []
+ self._sp = settings['sp']
+ self._idp = settings.get('idp', {})
+ self._strict = settings.get('strict', True)
+ self._debug = settings.get('debug', False)
+ self._security = settings.get('security', {})
+ self._contacts = settings.get('contactPerson', {})
+ self._organization = settings.get('organization', {})
+
+ self._add_default_values()
return True
- self.__errors = errors
+ self._errors = errors
return False
- def __load_settings_from_file(self):
+ def _load_settings_from_file(self):
"""
Loads settings info from the settings json file
@@ -265,69 +265,69 @@ def __load_settings_from_file(self):
with open(advanced_filename, 'r') as json_data:
settings.update(json.loads(json_data.read())) # Merge settings
- return self.__load_settings_from_dict(settings)
+ return self._load_settings_from_dict(settings)
- def __add_default_values(self):
+ def _add_default_values(self):
"""
Add default values if the settings info is not complete
"""
- self.__sp.setdefault('assertionConsumerService', {})
- self.__sp['assertionConsumerService'].setdefault('binding', OneLogin_Saml2_Constants.BINDING_HTTP_POST)
+ self._sp.setdefault('assertionConsumerService', {})
+ self._sp['assertionConsumerService'].setdefault('binding', OneLogin_Saml2_Constants.BINDING_HTTP_POST)
- self.__sp.setdefault('attributeConsumingService', {})
+ self._sp.setdefault('attributeConsumingService', {})
- self.__sp.setdefault('singleLogoutService', {})
- self.__sp['singleLogoutService'].setdefault('binding', OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT)
+ self._sp.setdefault('singleLogoutService', {})
+ self._sp['singleLogoutService'].setdefault('binding', OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT)
- self.__idp.setdefault('singleLogoutService', {})
+ self._idp.setdefault('singleLogoutService', {})
# Related to nameID
- self.__sp.setdefault('NameIDFormat', OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED)
- self.__security.setdefault('nameIdEncrypted', False)
+ self._sp.setdefault('NameIDFormat', OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED)
+ self._security.setdefault('nameIdEncrypted', False)
# Metadata format
- self.__security.setdefault('metadataValidUntil', None) # None means use default
- self.__security.setdefault('metadataCacheDuration', None) # None means use default
+ self._security.setdefault('metadataValidUntil', None) # None means use default
+ self._security.setdefault('metadataCacheDuration', None) # None means use default
# Sign provided
- self.__security.setdefault('authnRequestsSigned', False)
- self.__security.setdefault('logoutRequestSigned', False)
- self.__security.setdefault('logoutResponseSigned', False)
- self.__security.setdefault('signMetadata', False)
+ self._security.setdefault('authnRequestsSigned', False)
+ self._security.setdefault('logoutRequestSigned', False)
+ self._security.setdefault('logoutResponseSigned', False)
+ self._security.setdefault('signMetadata', False)
# Sign expected
- self.__security.setdefault('wantMessagesSigned', False)
- self.__security.setdefault('wantAssertionsSigned', False)
+ self._security.setdefault('wantMessagesSigned', False)
+ self._security.setdefault('wantAssertionsSigned', False)
# NameID element expected
- self.__security.setdefault('wantNameId', True)
+ self._security.setdefault('wantNameId', True)
# Encrypt expected
- self.__security.setdefault('wantAssertionsEncrypted', False)
- self.__security.setdefault('wantNameIdEncrypted', False)
+ self._security.setdefault('wantAssertionsEncrypted', False)
+ self._security.setdefault('wantNameIdEncrypted', False)
# Signature Algorithm
- self.__security.setdefault('signatureAlgorithm', OneLogin_Saml2_Constants.RSA_SHA1)
+ self._security.setdefault('signatureAlgorithm', OneLogin_Saml2_Constants.RSA_SHA1)
# Digest Algorithm
- self.__security.setdefault('digestAlgorithm', OneLogin_Saml2_Constants.SHA1)
+ self._security.setdefault('digestAlgorithm', OneLogin_Saml2_Constants.SHA1)
# AttributeStatement required by default
- self.__security.setdefault('wantAttributeStatement', True)
+ self._security.setdefault('wantAttributeStatement', True)
# Disallow duplicate attribute names by default
- self.__security.setdefault('allowRepeatAttributeName', False)
+ self._security.setdefault('allowRepeatAttributeName', False)
- self.__idp.setdefault('x509cert', '')
- self.__idp.setdefault('certFingerprint', '')
- self.__idp.setdefault('certFingerprintAlgorithm', 'sha1')
+ self._idp.setdefault('x509cert', '')
+ self._idp.setdefault('certFingerprint', '')
+ self._idp.setdefault('certFingerprintAlgorithm', 'sha1')
- self.__sp.setdefault('x509cert', '')
- self.__sp.setdefault('privateKey', '')
+ self._sp.setdefault('x509cert', '')
+ self._sp.setdefault('privateKey', '')
- self.__security.setdefault('requestedAuthnContext', True)
- self.__security.setdefault('requestedAuthnContextComparison', 'exact')
- self.__security.setdefault('failOnAuthnContextMismatch', False)
+ self._security.setdefault('requestedAuthnContext', True)
+ self._security.setdefault('requestedAuthnContextComparison', 'exact')
+ self._security.setdefault('failOnAuthnContextMismatch', False)
def check_settings(self, settings):
"""
@@ -345,7 +345,7 @@ def check_settings(self, settings):
if not isinstance(settings, dict) or len(settings) == 0:
errors.append('invalid_syntax')
else:
- if not self.__sp_validation_only:
+ if not self._sp_validation_only:
errors += self.check_idp_settings(settings)
sp_errors = self.check_sp_settings(settings)
errors += sp_errors
@@ -425,9 +425,9 @@ def check_sp_settings(self, settings):
errors.append('sp_not_found')
else:
allow_single_domain_urls = self._get_allow_single_label_domain(settings)
- # check_sp_certs uses self.__sp so I add it
- old_sp = self.__sp
- self.__sp = settings['sp']
+ # check_sp_certs uses self._sp so I add it
+ old_sp = self._sp
+ self._sp = settings['sp']
sp = settings['sp']
security = settings.get('security', {})
@@ -508,9 +508,9 @@ def check_sp_settings(self, settings):
('url' not in organization or len(organization['url']) == 0):
errors.append('organization_not_enought_data')
break
- # Restores the value that had the self.__sp
+ # Restores the value that had the self._sp
if 'old_sp' in locals():
- self.__sp = old_sp
+ self._sp = old_sp
return errors
@@ -562,8 +562,8 @@ def get_sp_key(self):
:returns: SP private key
:rtype: string or None
"""
- key = self.__sp.get('privateKey')
- key_file_name = self.__paths['cert'] + 'sp.key'
+ key = self._sp.get('privateKey')
+ key_file_name = self._paths['cert'] + 'sp.key'
if not key and exists(key_file_name):
with open(key_file_name) as f:
@@ -577,8 +577,8 @@ def get_sp_cert(self):
:returns: SP public cert
:rtype: string or None
"""
- cert = self.__sp.get('x509cert')
- cert_file_name = self.__paths['cert'] + 'sp.crt'
+ cert = self._sp.get('x509cert')
+ cert_file_name = self._paths['cert'] + 'sp.crt'
if not cert and exists(cert_file_name):
with open(cert_file_name) as f:
@@ -593,8 +593,8 @@ def get_sp_cert_new(self):
:returns: SP public cert new
:rtype: string or None
"""
- cert = self.__sp.get('x509certNew')
- cert_file_name = self.__paths['cert'] + 'sp_new.crt'
+ cert = self._sp.get('x509certNew')
+ cert_file_name = self._paths['cert'] + 'sp_new.crt'
if not cert and exists(cert_file_name):
with open(cert_file_name) as f:
@@ -608,7 +608,7 @@ def get_idp_cert(self):
:returns: IdP public cert
:rtype: string
"""
- cert = self.__idp.get('x509cert')
+ cert = self._idp.get('x509cert')
cert_file_name = self.get_cert_path() + 'idp.crt'
if not cert and exists(cert_file_name):
with open(cert_file_name) as f:
@@ -622,7 +622,7 @@ def get_idp_data(self):
:returns: IdP info
:rtype: dict
"""
- return self.__idp
+ return self._idp
def get_sp_data(self):
"""
@@ -631,7 +631,7 @@ def get_sp_data(self):
:returns: SP info
:rtype: dict
"""
- return self.__sp
+ return self._sp
def get_security_data(self):
"""
@@ -640,7 +640,7 @@ def get_security_data(self):
:returns: Security info
:rtype: dict
"""
- return self.__security
+ return self._security
def get_contacts(self):
"""
@@ -649,7 +649,7 @@ def get_contacts(self):
:returns: Contacts info
:rtype: dict
"""
- return self.__contacts
+ return self._contacts
def get_organization(self):
"""
@@ -658,7 +658,7 @@ def get_organization(self):
:returns: Organization info
:rtype: dict
"""
- return self.__organization
+ return self._organization
def get_sp_metadata(self):
"""
@@ -667,14 +667,14 @@ def get_sp_metadata(self):
:rtype: string
"""
metadata = self.metadata_class.builder(
- self.__sp, self.__security['authnRequestsSigned'],
- self.__security['wantAssertionsSigned'],
- self.__security['metadataValidUntil'],
- self.__security['metadataCacheDuration'],
+ self._sp, self._security['authnRequestsSigned'],
+ self._security['wantAssertionsSigned'],
+ self._security['metadataValidUntil'],
+ self._security['metadataCacheDuration'],
self.get_contacts(), self.get_organization()
)
- add_encryption = self.__security['wantNameIdEncrypted'] or self.__security['wantAssertionsEncrypted']
+ add_encryption = self._security['wantNameIdEncrypted'] or self._security['wantAssertionsEncrypted']
cert_new = self.get_sp_cert_new()
metadata = self.metadata_class.add_x509_key_descriptors(metadata, cert_new, add_encryption)
@@ -683,8 +683,8 @@ def get_sp_metadata(self):
metadata = self.metadata_class.add_x509_key_descriptors(metadata, cert, add_encryption)
# Sign metadata
- if 'signMetadata' in self.__security and self.__security['signMetadata'] is not False:
- if self.__security['signMetadata'] is True:
+ if 'signMetadata' in self._security and self._security['signMetadata'] is not False:
+ if self._security['signMetadata'] is True:
# Use the SP's normal key to sign the metadata:
if not cert:
raise OneLogin_Saml2_Error(
@@ -700,16 +700,16 @@ def get_sp_metadata(self):
)
else:
# Use a custom key to sign the metadata:
- if ('keyFileName' not in self.__security['signMetadata'] or
- 'certFileName' not in self.__security['signMetadata']):
+ if ('keyFileName' not in self._security['signMetadata'] or
+ 'certFileName' not in self._security['signMetadata']):
raise OneLogin_Saml2_Error(
'Invalid Setting: signMetadata value of the sp is not valid',
OneLogin_Saml2_Error.SETTINGS_INVALID_SYNTAX
)
- key_file_name = self.__security['signMetadata']['keyFileName']
- cert_file_name = self.__security['signMetadata']['certFileName']
- key_metadata_file = self.__paths['cert'] + key_file_name
- cert_metadata_file = self.__paths['cert'] + cert_file_name
+ key_file_name = self._security['signMetadata']['keyFileName']
+ cert_file_name = self._security['signMetadata']['certFileName']
+ key_metadata_file = self._paths['cert'] + key_file_name
+ cert_metadata_file = self._paths['cert'] + cert_file_name
try:
with open(key_metadata_file, 'r') as f_metadata_key:
@@ -731,8 +731,8 @@ def get_sp_metadata(self):
cert_metadata_file
)
- signature_algorithm = self.__security['signatureAlgorithm']
- digest_algorithm = self.__security['digestAlgorithm']
+ signature_algorithm = self._security['signatureAlgorithm']
+ digest_algorithm = self._security['digestAlgorithm']
metadata = self.metadata_class.sign_metadata(metadata, key_metadata, cert_metadata, signature_algorithm, digest_algorithm)
@@ -755,7 +755,7 @@ def validate_metadata(self, xml):
raise Exception('Empty string supplied as input')
errors = []
- root = OneLogin_Saml2_XML.validate_xml(xml, 'saml-schema-metadata-2.0.xsd', self.__debug)
+ root = OneLogin_Saml2_XML.validate_xml(xml, 'saml-schema-metadata-2.0.xsd', self._debug)
if isinstance(root, str):
errors.append(root)
else:
@@ -781,38 +781,38 @@ def format_idp_cert(self):
"""
Formats the IdP cert.
"""
- self.__idp['x509cert'] = OneLogin_Saml2_Utils.format_cert(self.__idp['x509cert'])
+ self._idp['x509cert'] = OneLogin_Saml2_Utils.format_cert(self._idp['x509cert'])
def format_idp_cert_multi(self):
"""
Formats the Multple IdP certs.
"""
- if 'x509certMulti' in self.__idp:
- if 'signing' in self.__idp['x509certMulti']:
- for idx in range(len(self.__idp['x509certMulti']['signing'])):
- self.__idp['x509certMulti']['signing'][idx] = OneLogin_Saml2_Utils.format_cert(self.__idp['x509certMulti']['signing'][idx])
+ if 'x509certMulti' in self._idp:
+ if 'signing' in self._idp['x509certMulti']:
+ for idx in range(len(self._idp['x509certMulti']['signing'])):
+ self._idp['x509certMulti']['signing'][idx] = OneLogin_Saml2_Utils.format_cert(self._idp['x509certMulti']['signing'][idx])
- if 'encryption' in self.__idp['x509certMulti']:
- for idx in range(len(self.__idp['x509certMulti']['encryption'])):
- self.__idp['x509certMulti']['encryption'][idx] = OneLogin_Saml2_Utils.format_cert(self.__idp['x509certMulti']['encryption'][idx])
+ if 'encryption' in self._idp['x509certMulti']:
+ for idx in range(len(self._idp['x509certMulti']['encryption'])):
+ self._idp['x509certMulti']['encryption'][idx] = OneLogin_Saml2_Utils.format_cert(self._idp['x509certMulti']['encryption'][idx])
def format_sp_cert(self):
"""
Formats the SP cert.
"""
- self.__sp['x509cert'] = OneLogin_Saml2_Utils.format_cert(self.__sp['x509cert'])
+ self._sp['x509cert'] = OneLogin_Saml2_Utils.format_cert(self._sp['x509cert'])
def format_sp_cert_new(self):
"""
Formats the SP cert.
"""
- self.__sp['x509certNew'] = OneLogin_Saml2_Utils.format_cert(self.__sp['x509certNew'])
+ self._sp['x509certNew'] = OneLogin_Saml2_Utils.format_cert(self._sp['x509certNew'])
def format_sp_key(self):
"""
Formats the private key.
"""
- self.__sp['privateKey'] = OneLogin_Saml2_Utils.format_private_key(self.__sp['privateKey'])
+ self._sp['privateKey'] = OneLogin_Saml2_Utils.format_private_key(self._sp['privateKey'])
def get_errors(self):
"""
@@ -821,7 +821,7 @@ def get_errors(self):
:returns: Errors
:rtype: list
"""
- return self.__errors
+ return self._errors
def set_strict(self, value):
"""
@@ -832,7 +832,7 @@ def set_strict(self, value):
"""
assert isinstance(value, bool)
- self.__strict = value
+ self._strict = value
def is_strict(self):
"""
@@ -841,7 +841,7 @@ def is_strict(self):
:returns: Strict parameter
:rtype: boolean
"""
- return self.__strict
+ return self._strict
def is_debug_active(self):
"""
@@ -850,7 +850,7 @@ def is_debug_active(self):
:returns: Debug parameter
:rtype: boolean
"""
- return self.__debug
+ return self._debug
def _get_allow_single_label_domain(self, settings):
security = settings.get('security', {})
diff --git a/tests/src/OneLogin/saml2_tests/authn_request_test.py b/tests/src/OneLogin/saml2_tests/authn_request_test.py
index b23c633a..cbf225a1 100644
--- a/tests/src/OneLogin/saml2_tests/authn_request_test.py
+++ b/tests/src/OneLogin/saml2_tests/authn_request_test.py
@@ -52,7 +52,7 @@ def testCreateRequest(self):
saml_settings = self.loadSettingsJSON()
settings = OneLogin_Saml2_Settings(saml_settings)
- settings._OneLogin_Saml2_Settings__organization = {
+ settings._organization = {
u'en-US': {
u'url': u'http://sp.example.com',
u'name': u'sp_test'
From 3bd411d9ad00a38d02cfa9e4f550ca011a18b514 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 23 Jul 2021 02:26:33 +0200
Subject: [PATCH 224/331] Release 1.11.0
---
changelog.md | 12 ++++++++++++
setup.py | 2 +-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index 4114cc04..4571ed31 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,16 @@
# python3-saml changelog
+### 1.11.0 (Jul 23, 2021)
+* [#261](https://github.com/onelogin/python3-saml/pull/261) Allow duplicate named attributes, controlled by a new setting
+* [#268](https://github.com/onelogin/python3-saml/pull/268) Make the redirect scheme matcher case-insensitive
+* [#256](https://github.com/onelogin/python3-saml/pull/256) Improve signature validation process. Add an option to use query string for validation
+* [#259](https://github.com/onelogin/python3-saml/pull/259) Add get metadata timeout
+* [#246](https://github.com/onelogin/python3-saml/pull/246) Add the ability to change the ProtocolBinding in the authn request.
+* [#248](https://github.com/onelogin/python3-saml/pull/248) Move storing the response data into its own method in the Auth class
+* Remove the dependency on defusedxml
+* [#241](https://github.com/onelogin/python3-saml/pull/241) Improve AttributeConsumingService support
+* Update expired dates from test responses
+* Migrate from Travis to Github Actions
+
### 1.10.1 (Jan 27, 2021)
* Fix bug on LogoutRequest class, get_idp_slo_response_url was used instead get_idp_slo_url
diff --git a/setup.py b/setup.py
index cff7b7c7..c783d224 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.10.1',
+ version='1.11.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From f435584496977bb91e2f62aecf3fe22fa47095b9 Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Fri, 23 Jul 2021 14:34:21 +0300
Subject: [PATCH 225/331] Deprecate server_port from request data dictionary
`server_port` is unnecessary, since the HTTP Host header sent by the client
already includes any non-standard port. In addition, when the Python
application server is sitting behind a reverse proxy/TLS terminator,
SERVER_PORT is likely to be wrong anyway (since it would be the server port
of the non-reverse-proxied server).
See https://github.com/onelogin/python3-saml/issues/273#issuecomment-885566427
---
README.md | 5 +-
demo-django/demo/views.py | 1 -
demo-flask/index.py | 4 +-
demo-tornado/views.py | 3 +-
demo_pyramid/demo_pyramid/views.py | 5 +-
src/onelogin/saml2/utils.py | 49 +++++++------------
.../src/OneLogin/saml2_tests/response_test.py | 24 ++++++---
tests/src/OneLogin/saml2_tests/utils_test.py | 7 +--
8 files changed, 41 insertions(+), 57 deletions(-)
diff --git a/README.md b/README.md
index 3bbf2a1f..05b5faf2 100644
--- a/README.md
+++ b/README.md
@@ -573,7 +573,6 @@ This parameter has the following scheme:
req = {
"http_host": "",
"script_name": "",
- "server_port": "",
"get_data": "",
"post_data": "",
@@ -594,7 +593,6 @@ def prepare_from_django_request(request):
return {
'http_host': request.META['HTTP_HOST'],
'script_name': request.META['PATH_INFO'],
- 'server_port': request.META['SERVER_PORT'],
'get_data': request.GET.copy(),
'post_data': request.POST.copy()
}
@@ -602,8 +600,7 @@ def prepare_from_django_request(request):
def prepare_from_flask_request(request):
url_data = urlparse(request.url)
return {
- 'http_host': request.host,
- 'server_port': url_data.port,
+ 'http_host': request.netloc,
'script_name': request.path,
'get_data': request.args.copy(),
'post_data': request.form.copy()
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 418ab0c8..6003b821 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -20,7 +20,6 @@ def prepare_django_request(request):
'https': 'on' if request.is_secure() else 'off',
'http_host': request.META['HTTP_HOST'],
'script_name': request.META['PATH_INFO'],
- 'server_port': request.META['SERVER_PORT'],
'get_data': request.GET.copy(),
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
# 'lowercase_urlencoding': True,
diff --git a/demo-flask/index.py b/demo-flask/index.py
index b523cb92..8a251a0a 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -21,11 +21,9 @@ def init_saml_auth(req):
def prepare_flask_request(request):
# If server is behind proxys or balancers use the HTTP_X_FORWARDED fields
- url_data = urlparse(request.url)
return {
'https': 'on' if request.scheme == 'https' else 'off',
- 'http_host': request.host,
- 'server_port': url_data.port,
+ 'http_host': request.netloc,
'script_name': request.path,
'get_data': request.args.copy(),
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
diff --git a/demo-tornado/views.py b/demo-tornado/views.py
index 70cfea68..e5d062aa 100644
--- a/demo-tornado/views.py
+++ b/demo-tornado/views.py
@@ -157,9 +157,8 @@ def prepare_tornado_request(request):
result = {
'https': 'on' if request == 'https' else 'off',
- 'http_host': tornado.httputil.split_host_and_port(request.host)[0],
+ 'http_host': request.host,
'script_name': request.path,
- 'server_port': tornado.httputil.split_host_and_port(request.host)[1],
'get_data': dataDict,
'post_data': dataDict,
'query_string': request.query
diff --git a/demo_pyramid/demo_pyramid/views.py b/demo_pyramid/demo_pyramid/views.py
index 6dab4edc..96434b8b 100644
--- a/demo_pyramid/demo_pyramid/views.py
+++ b/demo_pyramid/demo_pyramid/views.py
@@ -15,19 +15,16 @@ def init_saml_auth(req):
def prepare_pyramid_request(request):
- # Uncomment this portion to set the request.scheme and request.server_port
+ # Uncomment this portion to set the request.scheme
# based on the supplied `X-Forwarded` headers.
# Useful for running behind reverse proxies or balancers.
#
# if 'X-Forwarded-Proto' in request.headers:
# request.scheme = request.headers['X-Forwarded-Proto']
- # if 'X-Forwarded-Port' in request.headers:
- # request.server_port = int(request.headers['X-Forwarded-Port'])
return {
'https': 'on' if request.scheme == 'https' else 'off',
'http_host': request.host,
- 'server_port': request.server_port,
'script_name': request.path,
'get_data': request.GET.copy(),
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index db88bd34..51ab4e00 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -10,6 +10,7 @@
"""
import base64
+import warnings
from copy import deepcopy
import calendar
from datetime import datetime
@@ -254,27 +255,25 @@ def get_self_url_host(request_data):
:rtype: string
"""
current_host = OneLogin_Saml2_Utils.get_self_host(request_data)
- port = ''
- if OneLogin_Saml2_Utils.is_https(request_data):
- protocol = 'https'
- else:
- protocol = 'http'
-
- if 'server_port' in request_data and request_data['server_port'] is not None:
- port_number = str(request_data['server_port'])
- port = ':' + port_number
+ protocol = 'https' if OneLogin_Saml2_Utils.is_https(request_data) else 'http'
- if protocol == 'http' and port_number == '80':
- port = ''
- elif protocol == 'https' and port_number == '443':
- port = ''
+ if request_data.get('server_port') is not None:
+ warnings.warn(
+ 'The server_port key in request data is deprecated. '
+ 'The http_host key should include a port, if required.',
+ category=DeprecationWarning,
+ )
+ port_suffix = ':%s' % request_data['server_port']
+ if not current_host.endswith(port_suffix):
+ if not ((protocol == 'https' and port_suffix == ':443') or (protocol == 'http' and port_suffix == ':80')):
+ current_host += port_suffix
- return '%s://%s%s' % (protocol, current_host, port)
+ return '%s://%s' % (protocol, current_host)
@staticmethod
def get_self_host(request_data):
"""
- Returns the current host.
+ Returns the current host (which may include a port number part).
:param request_data: The request as a dict
:type: dict
@@ -283,22 +282,11 @@ def get_self_host(request_data):
:rtype: string
"""
if 'http_host' in request_data:
- current_host = request_data['http_host']
+ return request_data['http_host']
elif 'server_name' in request_data:
- current_host = request_data['server_name']
- else:
- raise Exception('No hostname defined')
-
- if ':' in current_host:
- current_host_data = current_host.split(':')
- possible_port = current_host_data[-1]
- try:
- int(possible_port)
- current_host = current_host_data[0]
- except ValueError:
- current_host = ':'.join(current_host_data)
-
- return current_host
+ warnings.warn("The server_name key in request data is undocumented & deprecated.", category=DeprecationWarning)
+ return request_data['server_name']
+ raise Exception('No hostname defined')
@staticmethod
def is_https(request_data):
@@ -312,6 +300,7 @@ def is_https(request_data):
:rtype: boolean
"""
is_https = 'https' in request_data and request_data['https'] != 'off'
+ # TODO: this use of server_port should be removed too
is_https = is_https or ('server_port' in request_data and str(request_data['server_port']) == '443')
return is_https
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index a51f8e60..3f83bc2b 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -4,6 +4,7 @@
# MIT License
from base64 import b64decode
+
from lxml import etree
from datetime import datetime
from datetime import timedelta
@@ -1528,22 +1529,31 @@ def testIsInValidEncIssues(self):
self.assertFalse(response_5.is_valid(request_data))
self.assertEqual('The NameID of the Response is not encrypted and the SP require it', response_5.get_error())
+ def testIsInValidEncIssues_2(self):
settings_info_2 = self.loadSettingsJSON('settings3.json')
settings_info_2['strict'] = True
settings_info_2['security']['wantNameIdEncrypted'] = True
settings_2 = OneLogin_Saml2_Settings(settings_info_2)
request_data = {
- 'http_host': 'pytoolkit.com',
- 'server_port': 8000,
'script_name': '',
'request_uri': '?acs',
}
-
- message_2 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion_encrypted_nameid.xml.base64'))
- response_6 = OneLogin_Saml2_Response(settings_2, message_2)
- self.assertFalse(response_6.is_valid(request_data))
- self.assertEqual('The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response', response_6.get_error())
+ for separate_port in (False, True):
+ if separate_port:
+ request_data.update({
+ 'http_host': 'pytoolkit.com',
+ 'server_port': 8000,
+ })
+ else:
+ request_data.update({
+ 'http_host': 'pytoolkit.com:8000',
+ })
+
+ message_2 = self.file_contents(join(self.data_path, 'responses', 'valid_encrypted_assertion_encrypted_nameid.xml.base64'))
+ response_6 = OneLogin_Saml2_Response(settings_2, message_2)
+ self.assertFalse(response_6.is_valid(request_data))
+ self.assertEqual('The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response', response_6.get_error())
def testIsInValidCert(self):
"""
diff --git a/tests/src/OneLogin/saml2_tests/utils_test.py b/tests/src/OneLogin/saml2_tests/utils_test.py
index 87112b0c..54d9ae66 100644
--- a/tests/src/OneLogin/saml2_tests/utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/utils_test.py
@@ -190,7 +190,7 @@ def testGetselfhost(self):
request_data = {
'http_host': 'example.com:443'
}
- self.assertEqual('example.com', OneLogin_Saml2_Utils.get_self_host(request_data))
+ self.assertEqual('example.com:443', OneLogin_Saml2_Utils.get_self_host(request_data))
request_data = {
'http_host': 'example.com:ok'
@@ -211,11 +211,6 @@ def testisHTTPS(self):
}
self.assertTrue(OneLogin_Saml2_Utils.is_https(request_data))
- request_data = {
- 'server_port': '80'
- }
- self.assertFalse(OneLogin_Saml2_Utils.is_https(request_data))
-
request_data = {
'server_port': '443'
}
From 562ceedfefdc0e2b9335488cf537ddc3bd8c897d Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Sun, 25 Jul 2021 15:10:32 +0300
Subject: [PATCH 226/331] Add Pip cache to CI
---
.github/workflows/python-package.yml | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index ac505be2..38ac7f4c 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -19,14 +19,18 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
-
+ - uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq swig python-dev libxml2-dev libxmlsec1-dev
make install-req
make install-test
-
- name: Test
run: |
make pytest
From 69d132c52ba354bb4d9660df2a0606b91ca316bb Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Sun, 25 Jul 2021 15:14:41 +0300
Subject: [PATCH 227/331] Separate linting in CI
---
.github/workflows/python-package.yml | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 38ac7f4c..1cc6b96a 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -32,8 +32,27 @@ jobs:
make install-req
make install-test
- name: Test
+ run: make pytest
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ with:
+ python-version: 3.9
+ - uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
+ - name: Install dependencies
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install -qq swig python-dev libxml2-dev libxmlsec1-dev
+ make install-req
+ make install-test
+ - name: Run linters
run: |
- make pytest
make pycodestyle
make flake8
-
From f7f45a7b6e9a14cbb3ebd8771e945fe022886ccc Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Sun, 25 Jul 2021 15:17:16 +0300
Subject: [PATCH 228/331] Move flake8 ignores to setup.cfg
---
Makefile | 2 +-
setup.cfg | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 7f3faddb..30f8af09 100644
--- a/Makefile
+++ b/Makefile
@@ -25,7 +25,7 @@ pycodestyle:
$(PYCODESTYLE) --ignore=E501,E731,W504 $(SOURCES) --config=$(PEP8_CONFIG)
flake8:
- $(FLAKE8) --ignore=E501,E731,W504 $(SOURCES)
+ $(FLAKE8) $(SOURCES)
clean:
rm -rf .pytest_cache/
diff --git a/setup.cfg b/setup.cfg
index e9d41598..36fa9534 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[flake8]
-ignore = E731,W504
+ignore = E731,W504,E501
max-complexity = 48
max-line-length = 1900
From 603f95132023a410e77fa8ea77f0a45138b4b28e Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Sun, 25 Jul 2021 15:17:56 +0300
Subject: [PATCH 229/331] Fix typos in makefile causing demos to be ignored by
lint
---
Makefile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 30f8af09..45d73ecb 100644
--- a/Makefile
+++ b/Makefile
@@ -6,9 +6,9 @@ COVERAGE=coverage
COVERAGE_CONFIG=tests/coverage.rc
PEP8_CONFIG=tests/pep8.rc
MAIN_SOURCE=src/onelogin/saml2
-DEMOS=demo-django demo-flask
+DEMOS=demo-django demo-flask demo-tornado demo_pyramid
TESTS=tests/src/OneLogin/saml2_tests
-SOURCES=$(MAIN_SOURCE) $(DEMO) $(TESTS)
+SOURCES=$(MAIN_SOURCE) $(DEMOS) $(TESTS)
install-req:
$(PIP) install --upgrade 'setuptools<45.0.0'
From c84af25048554c8f13d94f9fa1544b30a51e78c1 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 13 Aug 2021 18:36:01 +0200
Subject: [PATCH 230/331] Release 1.12.0
---
changelog.md | 3 +++
setup.py | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index 4571ed31..cc17cb4e 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,7 @@
# python3-saml changelog
+### 1.12.0 (Aug 13, 2021)
+* [#276](https://github.com/onelogin/python3-saml/pull/276) Deprecate server_port from request data dictionary
+
### 1.11.0 (Jul 23, 2021)
* [#261](https://github.com/onelogin/python3-saml/pull/261) Allow duplicate named attributes, controlled by a new setting
* [#268](https://github.com/onelogin/python3-saml/pull/268) Make the redirect scheme matcher case-insensitive
diff --git a/setup.py b/setup.py
index c783d224..4aad2c76 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.11.0',
+ version='1.12.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From e77d015ba4ce077787fee321614dee86d78b583d Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 16 Aug 2021 14:13:32 +0200
Subject: [PATCH 231/331] Restore request.host
Flask example hits error: AttributeError: 'Request' object has no attribute 'netloc'
---
demo-flask/index.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/demo-flask/index.py b/demo-flask/index.py
index 8a251a0a..5f43292a 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -23,7 +23,7 @@ def prepare_flask_request(request):
# If server is behind proxys or balancers use the HTTP_X_FORWARDED fields
return {
'https': 'on' if request.scheme == 'https' else 'off',
- 'http_host': request.netloc,
+ 'http_host': request.host,
'script_name': request.path,
'get_data': request.args.copy(),
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
From 8a47e705365035da0e97f96edd49bb6721035c85 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 16 Aug 2021 14:19:10 +0200
Subject: [PATCH 232/331] Fix pycodestyle
---
demo-flask/index.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/demo-flask/index.py b/demo-flask/index.py
index 5f43292a..a10ac8ce 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -3,8 +3,6 @@
from flask import (Flask, request, render_template, redirect, session,
make_response)
-from urllib.parse import urlparse
-
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.utils import OneLogin_Saml2_Utils
From c137dddc6f44c9c5ee3e8884e6d53b0376508126 Mon Sep 17 00:00:00 2001
From: Gunesh Pinar
Date: Thu, 26 Aug 2021 23:19:37 -0700
Subject: [PATCH 233/331] Implement
OneLogin_Saml2_Auth.get_last_assertion_issue_instant()
---
README.md | 1 +
src/onelogin/saml2/auth.py | 9 +++++++++
src/onelogin/saml2/response.py | 13 +++++++++++++
tests/src/OneLogin/saml2_tests/auth_test.py | 7 +++++--
4 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 05b5faf2..3a5f39ff 100644
--- a/README.md
+++ b/README.md
@@ -970,6 +970,7 @@ Main class of OneLogin Python Toolkit
* ***get_last_message_id*** The ID of the last Response SAML message processed.
* ***get_last_assertion_id*** The ID of the last assertion processed.
* ***get_last_assertion_not_on_or_after*** The ``NotOnOrAfter`` value of the valid ``SubjectConfirmationData`` node (if any) of the last assertion processed (is only calculated with strict = true)
+* ***get_last_assertion_issue_instant*** The `IssueInstant` value of the last assertion processed.
#### OneLogin_Saml2_Auth - authn_request.py ####
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index c284fe46..6c72932b 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -71,6 +71,7 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
self._last_request_id = None
self._last_message_id = None
self._last_assertion_id = None
+ self._last_assertion_issue_instant = None
self._last_authn_contexts = []
self._last_request = None
self._last_response = None
@@ -105,6 +106,7 @@ def store_valid_response(self, response):
self._session_expiration = response.get_session_not_on_or_after()
self._last_message_id = response.get_id()
self._last_assertion_id = response.get_assertion_id()
+ self._last_assertion_issue_instant = response.get_assertion_issue_instant()
self._last_authn_contexts = response.get_authn_contexts()
self._authenticated = True
self._last_assertion_not_on_or_after = response.get_assertion_not_on_or_after()
@@ -373,6 +375,13 @@ def get_last_assertion_id(self):
"""
return self._last_assertion_id
+ def get_last_assertion_issue_instant(self):
+ """
+ :returns: The IssueInstant of the last assertion processed.
+ :rtype: unix/posix timestamp|None
+ """
+ return self._last_assertion_issue_instant
+
def get_last_authn_contexts(self):
"""
:returns: The list of authentication contexts sent in the last SAML Response.
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 7d372ab6..4ef0418c 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -917,3 +917,16 @@ def get_assertion_id(self):
OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS
)
return self._query_assertion('')[0].get('ID', None)
+
+ def get_assertion_issue_instant(self):
+ """
+ :returns: the IssueInstant of the assertion in the response
+ :rtype: unix/posix timestamp|None
+ """
+ if not self.validate_num_assertions():
+ raise OneLogin_Saml2_ValidationError(
+ 'SAML Response must contain 1 assertion',
+ OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS
+ )
+ issue_instant = self._query_assertion('')[0].get('IssueInstant', None)
+ return OneLogin_Saml2_Utils.parse_SAML_to_time(issue_instant)
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index ea8cf1d3..87a57f19 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -1415,7 +1415,7 @@ def testGetLastLogoutResponse(self):
def testGetInfoFromLastResponseReceived(self):
"""
- Tests the get_last_message_id, get_last_assertion_id and get_last_assertion_not_on_or_after
+ Tests the get_last_message_id, get_last_assertion_id, get_last_assertion_not_on_or_after and get_last_assertion_issue_instant
of the OneLogin_Saml2_Auth class
"""
settings = self.loadSettingsJSON()
@@ -1431,8 +1431,9 @@ def testGetInfoFromLastResponseReceived(self):
self.assertEqual(auth.get_last_message_id(), 'pfx42be40bf-39c3-77f0-c6ae-8bf2e23a1a2e')
self.assertEqual(auth.get_last_assertion_id(), 'pfx57dfda60-b211-4cda-0f63-6d5deb69e5bb')
self.assertIsNone(auth.get_last_assertion_not_on_or_after())
+ self.assertEqual(auth.get_last_assertion_issue_instant(), 1392773821)
- # NotOnOrAfter is only calculated with strict = true
+ # NotOnOrAfter is only calculated with strict = true
# If invalid, response id and assertion id are not obtained
settings['strict'] = True
@@ -1442,6 +1443,7 @@ def testGetInfoFromLastResponseReceived(self):
self.assertIsNone(auth.get_last_message_id())
self.assertIsNone(auth.get_last_assertion_id())
self.assertIsNone(auth.get_last_assertion_not_on_or_after())
+ self.assertIsNone(auth.get_last_assertion_issue_instant())
request_data['https'] = 'on'
request_data['http_host'] = 'pitbulk.no-ip.org'
@@ -1452,6 +1454,7 @@ def testGetInfoFromLastResponseReceived(self):
self.assertEqual(auth.get_last_message_id(), 'pfx42be40bf-39c3-77f0-c6ae-8bf2e23a1a2e')
self.assertEqual(auth.get_last_assertion_id(), 'pfx57dfda60-b211-4cda-0f63-6d5deb69e5bb')
self.assertEqual(auth.get_last_assertion_not_on_or_after(), 2671081021)
+ self.assertEqual(auth.get_last_assertion_issue_instant(), 1392773821)
def testGetIdFromLogoutRequest(self):
"""
From 149a4666e18a0f54794f4552de0da4a2753ff6a4 Mon Sep 17 00:00:00 2001
From: Gunesh Pinar
Date: Thu, 26 Aug 2021 23:37:37 -0700
Subject: [PATCH 234/331] Revert Whitespace Change
---
tests/src/OneLogin/saml2_tests/auth_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 87a57f19..809ebdfb 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -1433,7 +1433,7 @@ def testGetInfoFromLastResponseReceived(self):
self.assertIsNone(auth.get_last_assertion_not_on_or_after())
self.assertEqual(auth.get_last_assertion_issue_instant(), 1392773821)
- # NotOnOrAfter is only calculated with strict = true
+ # NotOnOrAfter is only calculated with strict = true
# If invalid, response id and assertion id are not obtained
settings['strict'] = True
From b6ffc5932d16dacf314123dcd653a92e1332f32e Mon Sep 17 00:00:00 2001
From: Gunesh Pinar
Date: Mon, 4 Oct 2021 22:40:40 -0700
Subject: [PATCH 235/331] Implement get_last_response_in_response_to()
---
README.md | 1 +
src/onelogin/saml2/auth.py | 9 +++++++++
tests/src/OneLogin/saml2_tests/auth_test.py | 5 ++++-
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3a5f39ff..dccc84f3 100644
--- a/README.md
+++ b/README.md
@@ -967,6 +967,7 @@ Main class of OneLogin Python Toolkit
* ***set_strict*** Set the strict mode active/disable.
* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (``AuthNRequest``, ``LogoutRequest``)
* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (``SAMLResponse``, ``LogoutResponse``). If the SAMLResponse had an encrypted assertion, decrypts it.
+* ***get_last_response_in_response_to*** The `InResponseTo` of the most recently processed SAML Response.
* ***get_last_message_id*** The ID of the last Response SAML message processed.
* ***get_last_assertion_id*** The ID of the last assertion processed.
* ***get_last_assertion_not_on_or_after*** The ``NotOnOrAfter`` value of the valid ``SubjectConfirmationData`` node (if any) of the last assertion processed (is only calculated with strict = true)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index 6c72932b..c097678b 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -75,6 +75,7 @@ def __init__(self, request_data, old_settings=None, custom_base_path=None):
self._last_authn_contexts = []
self._last_request = None
self._last_response = None
+ self._last_response_in_response_to = None
self._last_assertion_not_on_or_after = None
def get_settings(self):
@@ -109,6 +110,7 @@ def store_valid_response(self, response):
self._last_assertion_issue_instant = response.get_assertion_issue_instant()
self._last_authn_contexts = response.get_authn_contexts()
self._authenticated = True
+ self._last_response_in_response_to = response.get_in_response_to()
self._last_assertion_not_on_or_after = response.get_assertion_not_on_or_after()
def process_response(self, request_id=None):
@@ -389,6 +391,13 @@ def get_last_authn_contexts(self):
"""
return self._last_authn_contexts
+ def get_last_response_in_response_to(self):
+ """
+ :returns: InResponseTo attribute of the last Response SAML processed or None if it is not present.
+ :rtype: string
+ """
+ return self._last_response_in_response_to
+
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
"""
Initiates the SSO process.
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 809ebdfb..0f979073 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -1415,7 +1415,7 @@ def testGetLastLogoutResponse(self):
def testGetInfoFromLastResponseReceived(self):
"""
- Tests the get_last_message_id, get_last_assertion_id, get_last_assertion_not_on_or_after and get_last_assertion_issue_instant
+ Tests the get_last_response_in_response_to, get_last_message_id, get_last_assertion_id, get_last_assertion_not_on_or_after and get_last_assertion_issue_instant
of the OneLogin_Saml2_Auth class
"""
settings = self.loadSettingsJSON()
@@ -1428,6 +1428,7 @@ def testGetInfoFromLastResponseReceived(self):
auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
auth.process_response()
+ self.assertEqual(auth.get_last_response_in_response_to(), 'ONELOGIN_5fe9d6e499b2f0913206aab3f7191729049bb807')
self.assertEqual(auth.get_last_message_id(), 'pfx42be40bf-39c3-77f0-c6ae-8bf2e23a1a2e')
self.assertEqual(auth.get_last_assertion_id(), 'pfx57dfda60-b211-4cda-0f63-6d5deb69e5bb')
self.assertIsNone(auth.get_last_assertion_not_on_or_after())
@@ -1440,6 +1441,7 @@ def testGetInfoFromLastResponseReceived(self):
auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
auth.process_response()
self.assertNotEqual(len(auth.get_errors()), 0)
+ self.assertIsNone(auth.get_last_response_in_response_to())
self.assertIsNone(auth.get_last_message_id())
self.assertIsNone(auth.get_last_assertion_id())
self.assertIsNone(auth.get_last_assertion_not_on_or_after())
@@ -1451,6 +1453,7 @@ def testGetInfoFromLastResponseReceived(self):
auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
auth.process_response()
self.assertEqual(len(auth.get_errors()), 0)
+ self.assertEqual(auth.get_last_response_in_response_to(), 'ONELOGIN_5fe9d6e499b2f0913206aab3f7191729049bb807')
self.assertEqual(auth.get_last_message_id(), 'pfx42be40bf-39c3-77f0-c6ae-8bf2e23a1a2e')
self.assertEqual(auth.get_last_assertion_id(), 'pfx57dfda60-b211-4cda-0f63-6d5deb69e5bb')
self.assertEqual(auth.get_last_assertion_not_on_or_after(), 2671081021)
From 138916d1cd5cc391b5f39c9f9eaef494a3fa8834 Mon Sep 17 00:00:00 2001
From: Gunesh Pinar
Date: Mon, 4 Oct 2021 22:41:40 -0700
Subject: [PATCH 236/331] Clarify README description
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index dccc84f3..2d132e9e 100644
--- a/README.md
+++ b/README.md
@@ -967,7 +967,7 @@ Main class of OneLogin Python Toolkit
* ***set_strict*** Set the strict mode active/disable.
* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (``AuthNRequest``, ``LogoutRequest``)
* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (``SAMLResponse``, ``LogoutResponse``). If the SAMLResponse had an encrypted assertion, decrypts it.
-* ***get_last_response_in_response_to*** The `InResponseTo` of the most recently processed SAML Response.
+* ***get_last_response_in_response_to*** The `InResponseTo` ID of the most recently processed SAML Response.
* ***get_last_message_id*** The ID of the last Response SAML message processed.
* ***get_last_assertion_id*** The ID of the last assertion processed.
* ***get_last_assertion_not_on_or_after*** The ``NotOnOrAfter`` value of the valid ``SubjectConfirmationData`` node (if any) of the last assertion processed (is only calculated with strict = true)
From 4c4d54005b8dd3d8ffa630e45b4d217e8f01621e Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 18 Oct 2021 21:21:11 +0200
Subject: [PATCH 237/331] Warn about Open Redirect and Reply attacks
---
README.md | 30 ++++++++++++++++++++++++++++++
demo-django/demo/views.py | 4 ++++
demo-flask/index.py | 4 ++++
demo-tornado/views.py | 4 ++++
demo_pyramid/demo_pyramid/views.py | 4 ++++
5 files changed, 46 insertions(+)
diff --git a/README.md b/README.md
index 3a5f39ff..739ff670 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,36 @@ your environment is not secure and will be exposed to attacks.
In production also we highly recommend to register on the settings the IdP certificate instead of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, we maintain it for compatibility and also to be used on test environment.
+
+### Avoiding Open Redirect attacks ###
+
+Some implementations uses the RelayState parameter as a way to control the flow when SSO and SLO succeeded. So basically the
+user is redirected to the value of the RelayState.
+
+If you are using Signature Validation on the HTTP-Redirect binding, you will have the RelayState value integrity covered, otherwise, and
+on HTTP-POST binding, you can't trust the RelayState so before
+executing the validation, you need to verify that its value belong
+a trusted and expected URL.
+
+Read more about Open Redirect [CWE-601](https://cwe.mitre.org/data/definitions/601.html).
+
+### Avoiding Reply attacks ###
+
+A reply attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).
+
+SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that
+make harder this kind of attacks, but they are still possible.
+
+In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs alredy valdidated and processed. Those values only need
+to be stored the amount of time of the SAML Message life time, so
+we don't need to store all processed message/assertion Ids, but the most recent ones.
+
+The OneLogin_Saml2_Auth class contains the [get_last_request_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L357), [get_last_message_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L364) and [get_last_assertion_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L371) methods to retrieve the IDs
+
+Checking that the ID of the current Message/Assertion does not exists in the lis of the ones already processed will prevent reply
+attacks.
+
+
Getting Started
---------------
diff --git a/demo-django/demo/views.py b/demo-django/demo/views.py
index 6003b821..b9acdd76 100644
--- a/demo-django/demo/views.py
+++ b/demo-django/demo/views.py
@@ -84,6 +84,8 @@ def index(request):
request.session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
request.session['samlSessionIndex'] = auth.get_session_index()
if 'RelayState' in req['post_data'] and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the req['post_data']['RelayState'] is a trusted URL.
return HttpResponseRedirect(auth.redirect_to(req['post_data']['RelayState']))
elif auth.get_settings().is_debug_active():
error_reason = auth.get_last_error_reason()
@@ -96,6 +98,8 @@ def index(request):
errors = auth.get_errors()
if len(errors) == 0:
if url is not None:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the url is a trusted URL
return HttpResponseRedirect(url)
else:
success_slo = True
diff --git a/demo-flask/index.py b/demo-flask/index.py
index a10ac8ce..3ad2a4e6 100644
--- a/demo-flask/index.py
+++ b/demo-flask/index.py
@@ -83,6 +83,8 @@ def index():
session['samlSessionIndex'] = auth.get_session_index()
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in request.form and self_url != request.form['RelayState']:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the request.form['RelayState'] is a trusted URL.
return redirect(auth.redirect_to(request.form['RelayState']))
elif auth.get_settings().is_debug_active():
error_reason = auth.get_last_error_reason()
@@ -95,6 +97,8 @@ def index():
errors = auth.get_errors()
if len(errors) == 0:
if url is not None:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the url is a trusted URL.
return redirect(url)
else:
success_slo = True
diff --git a/demo-tornado/views.py b/demo-tornado/views.py
index e5d062aa..70ef1565 100644
--- a/demo-tornado/views.py
+++ b/demo-tornado/views.py
@@ -46,6 +46,8 @@ def post(self):
session['samlSessionIndex'] = auth.get_session_index()
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in self.request.arguments and self_url != self.request.arguments['RelayState'][0].decode('utf-8'):
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the self.request.arguments['RelayState'][0] is a trusted URL.
return self.redirect(self.request.arguments['RelayState'][0].decode('utf-8'))
elif auth.get_settings().is_debug_active():
error_reason = auth.get_last_error_reason()
@@ -104,6 +106,8 @@ def get(self):
errors = auth.get_errors()
if len(errors) == 0:
if url is not None:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the url is a trusted URL.
return self.redirect(url)
else:
success_slo = True
diff --git a/demo_pyramid/demo_pyramid/views.py b/demo_pyramid/demo_pyramid/views.py
index 96434b8b..5b6be9dd 100644
--- a/demo_pyramid/demo_pyramid/views.py
+++ b/demo_pyramid/demo_pyramid/views.py
@@ -70,6 +70,8 @@ def index(request):
session['samlSessionIndex'] = auth.get_session_index()
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in request.POST and self_url != request.POST['RelayState']:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the request.POST['RelayState'] is a trusted URL.
return HTTPFound(auth.redirect_to(request.POST['RelayState']))
else:
error_reason = auth.get_last_error_reason()
@@ -79,6 +81,8 @@ def index(request):
errors = auth.get_errors()
if len(errors) == 0:
if url is not None:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the url is a trusted URL.
return HTTPFound(url)
else:
success_slo = True
From b4199c5364d4b4c00d9930d9e4dab655ecdfaf81 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Mon, 18 Oct 2021 21:29:09 +0200
Subject: [PATCH 238/331] Modify examples of README as well
---
README.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/README.md b/README.md
index 739ff670..003beb63 100644
--- a/README.md
+++ b/README.md
@@ -727,6 +727,8 @@ if not errors:
request.session['samlUserdata'] = auth.get_attributes()
if 'RelayState' in req['post_data'] and
OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the req['post_data']['RelayState'] is a trusted URL.
auth.redirect_to(req['post_data']['RelayState'])
else:
for attr_name in request.session['samlUserdata'].keys():
@@ -789,6 +791,8 @@ url = auth.process_slo(delete_session_cb=delete_session_callback)
errors = auth.get_errors()
if len(errors) == 0:
if url is not None:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the url is a trusted URL.
return redirect(url)
else:
print("Sucessfully Logged out")
@@ -916,6 +920,8 @@ elif 'acs' in request.args: # Assertion Consumer Service
request.session['samlSessionIndex'] = auth.get_session_index()
self_url = OneLogin_Saml2_Utils.get_self_url(req)
if 'RelayState' in request.form and self_url != request.form['RelayState']:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the request.form['RelayState'] is a trusted URL.
return redirect(auth.redirect_to(request.form['RelayState'])) # Redirect if there is a relayState
else: # If there is user data we save that to print it later.
msg = ''
@@ -927,6 +933,8 @@ elif 'sls' in request.args: # Single
errors = auth.get_errors() # Retrieves possible validation errors
if len(errors) == 0:
if url is not None:
+ # To avoid 'Open Redirect' attacks, before execute the redirection confirm
+ # the value of the url is a trusted URL.
return redirect(url)
else:
msg = "Sucessfully logged out"
From 6f973d181a50530542a73628e4bd3166f05e9aac Mon Sep 17 00:00:00 2001
From: Mateusz Mandera
Date: Mon, 8 Nov 2021 12:46:13 +0100
Subject: [PATCH 239/331] Support building a LogoutResponse with non-success
status
---
src/onelogin/saml2/logout_response.py | 22 ++++++++++---------
.../saml2_tests/logout_response_test.py | 20 +++++++++++++++++
2 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/src/onelogin/saml2/logout_response.py b/src/onelogin/saml2/logout_response.py
index e9368e8a..d1110a73 100644
--- a/src/onelogin/saml2/logout_response.py
+++ b/src/onelogin/saml2/logout_response.py
@@ -10,6 +10,7 @@
"""
from onelogin.saml2 import compat
+from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.utils import OneLogin_Saml2_Utils, OneLogin_Saml2_ValidationError
from onelogin.saml2.xml_templates import OneLogin_Saml2_Templates
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML
@@ -152,11 +153,13 @@ def _query(self, query):
"""
return OneLogin_Saml2_XML.query(self.document, query)
- def build(self, in_response_to):
+ def build(self, in_response_to, status=OneLogin_Saml2_Constants.STATUS_SUCCESS):
"""
Creates a Logout Response object.
:param in_response_to: InResponseTo value for the Logout Response.
:type in_response_to: string
+ :param: status: The status of the response
+ :type: status: string
"""
sp_data = self._settings.get_sp_data()
@@ -164,15 +167,14 @@ def build(self, in_response_to):
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
- logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % \
- {
- 'id': self.id,
- 'issue_instant': issue_instant,
- 'destination': self._settings.get_idp_slo_response_url(),
- 'in_response_to': in_response_to,
- 'entity_id': sp_data['entityId'],
- 'status': "urn:oasis:names:tc:SAML:2.0:status:Success"
- }
+ logout_response = OneLogin_Saml2_Templates.LOGOUT_RESPONSE % {
+ "id": self.id,
+ "issue_instant": issue_instant,
+ "destination": self._settings.get_idp_slo_response_url(),
+ "in_response_to": in_response_to,
+ "entity_id": sp_data["entityId"],
+ "status": status,
+ }
self._logout_response = logout_response
diff --git a/tests/src/OneLogin/saml2_tests/logout_response_test.py b/tests/src/OneLogin/saml2_tests/logout_response_test.py
index e6f31b0f..4b3da96a 100644
--- a/tests/src/OneLogin/saml2_tests/logout_response_test.py
+++ b/tests/src/OneLogin/saml2_tests/logout_response_test.py
@@ -401,3 +401,23 @@ def testGetXML(self):
logout_response_processed = OneLogin_Saml2_Logout_Response(settings, OneLogin_Saml2_Utils.deflate_and_base64_encode(response))
self.assertEqual(response, logout_response_processed.get_xml())
+
+ def testBuildWithStatus(self):
+ """
+ Tests the build method when called specifying a non-default status for the LogoutResponse.
+ """
+ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
+
+ response_builder = OneLogin_Saml2_Logout_Response(settings)
+ response_builder.build("InResponseValue", status=OneLogin_Saml2_Constants.STATUS_REQUESTER)
+ generated_encoded_response = response_builder.get_response()
+
+ # Parse and verify the status of the response, as the receiver will do:
+ parsed_response = OneLogin_Saml2_Logout_Response(settings, generated_encoded_response)
+ expectedFragment = (
+ ' \n'
+ ' \n'
+ ' \n'
+ )
+ self.assertIn(expectedFragment, parsed_response.get_xml())
+ self.assertEqual(parsed_response.get_status(), OneLogin_Saml2_Constants.STATUS_REQUESTER)
From d2716a1642b55e45028d63c653304786973e2108 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 12:58:45 +0100
Subject: [PATCH 240/331] Adding python 3.10 to CI. See #294
---
.github/workflows/python-package.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 1cc6b96a..91d8f7b4 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: [2.7,3.5,3.6,3.7, 3.8,3.9]
+ python-version: [2.7,3.5,3.6,3.7, 3.8,3.9,3.10.2]
steps:
- uses: actions/checkout@v2
From 9726d5c2e1e345023249e7caa7a2a2159e5a2558 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 13:33:19 +0100
Subject: [PATCH 241/331] Upgrade dependencies
---
setup.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/setup.py b/setup.py
index 4aad2c76..d6c5de51 100644
--- a/setup.py
+++ b/setup.py
@@ -38,9 +38,9 @@
},
test_suite='tests',
install_requires=[
- 'isodate>=0.5.0',
- 'lxml>=3.3.5',
- 'xmlsec>=1.0.5'
+ 'lxml>=4.7.1',
+ 'isodate>=0.6.1',
+ 'xmlsec>=1.3.9'
],
dependency_links=['http://github.com/mehcode/python-xmlsec/tarball/master'],
extras_require={
From b5fdfc9b3c8722abbef020aa64a8c4d29d7288b2 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 13:36:21 +0100
Subject: [PATCH 242/331] Remove 3.10 for now from CI
---
.github/workflows/python-package.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 91d8f7b4..1cc6b96a 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -11,7 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: [2.7,3.5,3.6,3.7, 3.8,3.9,3.10.2]
+ python-version: [2.7,3.5,3.6,3.7, 3.8,3.9]
steps:
- uses: actions/checkout@v2
From a55cc9a379da03eff9f96a886f6164fd0792a62e Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 18:00:18 +0100
Subject: [PATCH 243/331] Fixing lxml to 4.7.0, it seems 4.7.1 had conflicts
when the signature inside a encrypted element is validated. See #292
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index d6c5de51..21108ec8 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@
},
test_suite='tests',
install_requires=[
- 'lxml>=4.7.1',
+ 'lxml==4.7.0',
'isodate>=0.6.1',
'xmlsec>=1.3.9'
],
From 6e34b695f0c596f0e07fa6eb297032013e1fb0f0 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 19:03:31 +0100
Subject: [PATCH 244/331] Set sha256 and rsa-sha256 as default algorithms to be
used
---
src/onelogin/saml2/auth.py | 8 ++++----
src/onelogin/saml2/metadata.py | 2 +-
src/onelogin/saml2/settings.py | 4 ++--
src/onelogin/saml2/utils.py | 12 ++++++------
tests/src/OneLogin/saml2_tests/auth_test.py | 10 +++++-----
tests/src/OneLogin/saml2_tests/metadata_test.py | 4 ++--
tests/src/OneLogin/saml2_tests/settings_test.py | 2 +-
tests/src/OneLogin/saml2_tests/utils_test.py | 4 ++--
8 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index c097678b..d17c7d48 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -523,7 +523,7 @@ def get_slo_response_url(self):
"""
return self._settings.get_idp_slo_response_url()
- def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
+ def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA256):
"""
Builds the Signature of the SAML Request.
@@ -535,7 +535,7 @@ def add_request_signature(self, request_data, sign_algorithm=OneLogin_Saml2_Cons
"""
return self._build_signature(request_data, 'SAMLRequest', sign_algorithm)
- def add_response_signature(self, response_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
+ def add_response_signature(self, response_data, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA256):
"""
Builds the Signature of the SAML Response.
:param response_data: The Response parameters
@@ -588,7 +588,7 @@ def _build_sign_query(saml_data, relay_state, algorithm, saml_type, lowercase_ur
sign_data.append('SigAlg=%s' % OneLogin_Saml2_Utils.escape_url(algorithm, lowercase_urlencoding))
return '&'.join(sign_data)
- def _build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
+ def _build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA256):
"""
Builds the Signature
:param data: The Request data
@@ -621,7 +621,7 @@ def _build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Consta
OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384,
OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512
}
- sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.Transform.RSA_SHA1)
+ sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.Transform.RSA_SHA256)
signature = OneLogin_Saml2_Utils.sign_binary(msg, key, sign_algorithm_transform, self._settings.is_debug_active())
data['Signature'] = OneLogin_Saml2_Utils.b64encode(signature)
diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py
index 34c5f2c1..cccd7ce0 100644
--- a/src/onelogin/saml2/metadata.py
+++ b/src/onelogin/saml2/metadata.py
@@ -193,7 +193,7 @@ def builder(cls, sp, authnsign=False, wsign=False, valid_until=None, cache_durat
return metadata
@staticmethod
- def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1):
+ def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA256, digest_algorithm=OneLogin_Saml2_Constants.SHA256):
"""
Signs the metadata with the key/cert provided
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index 3163995e..c258758c 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -307,10 +307,10 @@ def _add_default_values(self):
self._security.setdefault('wantNameIdEncrypted', False)
# Signature Algorithm
- self._security.setdefault('signatureAlgorithm', OneLogin_Saml2_Constants.RSA_SHA1)
+ self._security.setdefault('signatureAlgorithm', OneLogin_Saml2_Constants.RSA_SHA256)
# Digest Algorithm
- self._security.setdefault('digestAlgorithm', OneLogin_Saml2_Constants.SHA1)
+ self._security.setdefault('digestAlgorithm', OneLogin_Saml2_Constants.SHA256)
# AttributeStatement required by default
self._security.setdefault('wantAttributeStatement', True)
diff --git a/src/onelogin/saml2/utils.py b/src/onelogin/saml2/utils.py
index 51ab4e00..34176e25 100644
--- a/src/onelogin/saml2/utils.py
+++ b/src/onelogin/saml2/utils.py
@@ -697,7 +697,7 @@ def decrypt_element(encrypted_data, key, debug=False, inplace=False):
return enc_ctx.decrypt(encrypted_data)
@staticmethod
- def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1):
+ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA256, digest_algorithm=OneLogin_Saml2_Constants.SHA256):
"""
Adds signature key and senders certificate to an element (Message or
Assertion).
@@ -735,7 +735,7 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384,
OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512
}
- sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.Transform.RSA_SHA1)
+ sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.Transform.RSA_SHA256)
signature = xmlsec.template.create(elem, xmlsec.Transform.EXCL_C14N, sign_algorithm_transform, ns='ds')
@@ -770,7 +770,7 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
OneLogin_Saml2_Constants.SHA384: xmlsec.Transform.SHA384,
OneLogin_Saml2_Constants.SHA512: xmlsec.Transform.SHA512
}
- digest_algorithm_transform = digest_algorithm_transform_map.get(digest_algorithm, xmlsec.Transform.SHA1)
+ digest_algorithm_transform = digest_algorithm_transform_map.get(digest_algorithm, xmlsec.Transform.SHA256)
ref = xmlsec.template.add_reference(signature, digest_algorithm_transform, uri=elem_id)
xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
@@ -983,7 +983,7 @@ def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, finger
return True
@staticmethod
- def sign_binary(msg, key, algorithm=xmlsec.Transform.RSA_SHA1, debug=False):
+ def sign_binary(msg, key, algorithm=xmlsec.Transform.RSA_SHA256, debug=False):
"""
Sign binary message
@@ -1009,7 +1009,7 @@ def sign_binary(msg, key, algorithm=xmlsec.Transform.RSA_SHA1, debug=False):
return dsig_ctx.sign_binary(compat.to_bytes(msg), algorithm)
@staticmethod
- def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_Saml2_Constants.RSA_SHA1, debug=False):
+ def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_Saml2_Constants.RSA_SHA256, debug=False):
"""
Validates signed binary data (Used to validate GET Signature).
@@ -1041,7 +1041,7 @@ def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_
OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384,
OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512
}
- sign_algorithm_transform = sign_algorithm_transform_map.get(algorithm, xmlsec.Transform.RSA_SHA1)
+ sign_algorithm_transform = sign_algorithm_transform_map.get(algorithm, xmlsec.Transform.RSA_SHA256)
dsig_ctx.verify_binary(compat.to_bytes(signed_query),
sign_algorithm_transform,
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 0f979073..4a872c81 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -567,7 +567,7 @@ def testProcessSLORequestSignedResponse(self):
self.assertIn('SigAlg', parsed_query)
self.assertIn('Signature', parsed_query)
self.assertIn('http://relaystate.com', parsed_query['RelayState'])
- self.assertIn(OneLogin_Saml2_Constants.RSA_SHA1, parsed_query['SigAlg'])
+ self.assertIn(OneLogin_Saml2_Constants.RSA_SHA256, parsed_query['SigAlg'])
def testLogin(self):
"""
@@ -624,7 +624,7 @@ def testLoginSigned(self):
self.assertIn('SigAlg', parsed_query)
self.assertIn('Signature', parsed_query)
self.assertIn(return_to, parsed_query['RelayState'])
- self.assertIn(OneLogin_Saml2_Constants.RSA_SHA1, parsed_query['SigAlg'])
+ self.assertIn(OneLogin_Saml2_Constants.RSA_SHA256, parsed_query['SigAlg'])
def testLoginForceAuthN(self):
"""
@@ -824,7 +824,7 @@ def testLogoutSigned(self):
self.assertIn('SigAlg', parsed_query)
self.assertIn('Signature', parsed_query)
self.assertIn(return_to, parsed_query['RelayState'])
- self.assertIn(OneLogin_Saml2_Constants.RSA_SHA1, parsed_query['SigAlg'])
+ self.assertIn(OneLogin_Saml2_Constants.RSA_SHA256, parsed_query['SigAlg'])
def testLogoutNoSLO(self):
"""
@@ -1088,7 +1088,7 @@ def testBuildRequestSignature(self):
auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings)
auth.add_request_signature(parameters)
- valid_signature = 'Pb1EXAX5TyipSJ1SndEKZstLQTsT+1D00IZAhEepBM+OkAZQSToivu3njgJu47HZiZAqgXZFgloBuuWE/+GdcSsRYEMkEkiSDWTpUr25zKYLJDSg6GNo6iAHsKSuFt46Z54Xe/keYxYP03Hdy97EwuuSjBzzgRc5tmpV+KC7+a0='
+ valid_signature = 'CqdIlbO6GieeJFV+PYqyqz1QVJunQXdZZl+ZyIby9O3/eMJM0XHi+TWReRrpgNxKkbmmvx5fp/t7mphbLiVYNMgGINEaaa/OfoaGwU9GM5YCVULA2t7qZBel1yrIXGMxijJizB7UPR2ZMo4G+Wdhx1zbmbB0GYM0A27w6YCe/+k='
self.assertEqual(valid_signature, parameters["Signature"])
settings['sp']['privateKey'] = ''
@@ -1109,7 +1109,7 @@ def testBuildResponseSignature(self):
parameters = {"SAMLResponse": message, 'RelayState': relay_state}
auth.add_response_signature(parameters)
- valid_signature = 'IcyWLRX6Dz3wHBfpcUaNLVDMGM3uo6z2Z11Gjq0/APPJaHboKGljffsgMVAGBml497yckq+eYKmmz+jpURV9yTj2sF9qfD6CwX2dEzSzMdRzB40X7pWyHgEJGIhs6BhaOt5oXEk4T+h3AczERqpVYFpL00yo7FNtyQkhZFpHFhM='
+ valid_signature = 'fFGaOuO/2+ch/xlwU5o7iS6R+v2quWchLAtiDyQTxStFQZKY1NsBs/eYIin2Meq7oTl1Ks6tpT6JshH5OwhPh/08K7M2oa6FIKb99cjg+jIJ/WwpuJ5h9SH0XXP8y3RLhCxLIomHDsBOGQK8WvOlXFUg+9nvOaEMNi6raUWrGhA='
self.assertEqual(valid_signature, parameters['Signature'])
settings['sp']['privateKey'] = ''
diff --git a/tests/src/OneLogin/saml2_tests/metadata_test.py b/tests/src/OneLogin/saml2_tests/metadata_test.py
index 0a12a6d4..c95ceead 100644
--- a/tests/src/OneLogin/saml2_tests/metadata_test.py
+++ b/tests/src/OneLogin/saml2_tests/metadata_test.py
@@ -222,8 +222,8 @@ def testSignMetadata(self):
self.assertIn('urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ', signed_metadata)
self.assertIn('\n ', signed_metadata)
- self.assertIn(' ', signed_metadata)
- self.assertIn(' ', signed_metadata)
+ self.assertIn(' ', signed_metadata)
+ self.assertIn(' ', signed_metadata)
self.assertIn('\n\n', signed_metadata)
diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py
index 4cb271f5..16f59e26 100644
--- a/tests/src/OneLogin/saml2_tests/settings_test.py
+++ b/tests/src/OneLogin/saml2_tests/settings_test.py
@@ -593,7 +593,7 @@ def generateAndCheckMetadata(self, settings):
self.assertIn(' ', metadata)
self.assertIn('urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ', metadata)
self.assertIn('\n ', metadata)
- self.assertIn(' ', metadata)
+ self.assertIn(' ', metadata)
self.assertIn('\n\n', metadata)
diff --git a/tests/src/OneLogin/saml2_tests/utils_test.py b/tests/src/OneLogin/saml2_tests/utils_test.py
index 54d9ae66..39cb46de 100644
--- a/tests/src/OneLogin/saml2_tests/utils_test.py
+++ b/tests/src/OneLogin/saml2_tests/utils_test.py
@@ -792,8 +792,8 @@ def testAddSignCheckAlg(self):
xml_authn = b64decode(self.file_contents(join(self.data_path, 'requests', 'authn_request.xml.base64')))
xml_authn_signed = compat.to_string(OneLogin_Saml2_Utils.add_sign(xml_authn, key, cert))
self.assertIn('', xml_authn_signed)
- self.assertIn(' ', xml_authn_signed)
- self.assertIn(' ', xml_authn_signed)
+ self.assertIn(' ', xml_authn_signed)
+ self.assertIn(' ', xml_authn_signed)
xml_authn_signed_2 = compat.to_string(OneLogin_Saml2_Utils.add_sign(xml_authn, key, cert, False, OneLogin_Saml2_Constants.RSA_SHA256, OneLogin_Saml2_Constants.SHA384))
self.assertIn('', xml_authn_signed_2)
From 171a89e75a6e39f9cb38472162418555c85aa02d Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 20:53:13 +0100
Subject: [PATCH 245/331] Add rejectDeprecatedAlgorithm settings. Define
DEPRECATED_ALGORITHMS list on Constants. If flag enabled, reject signatures
on response, logout_request and logout_response with deprecated algorithm
---
README.md | 9 +++-
demo-django/saml/advanced_settings.json | 3 +-
demo-flask/saml/advanced_settings.json | 3 +-
demo-tornado/saml/advanced_settings.json | 3 +-
.../demo_pyramid/saml/advanced_settings.json | 3 +-
src/onelogin/saml2/auth.py | 11 ++++-
src/onelogin/saml2/constants.py | 3 ++
src/onelogin/saml2/errors.py | 2 +
src/onelogin/saml2/response.py | 23 +++++++++
src/onelogin/saml2/settings.py | 3 ++
tests/src/OneLogin/saml2_tests/auth_test.py | 47 ++++++++++++++++++-
.../src/OneLogin/saml2_tests/response_test.py | 13 +++++
12 files changed, 115 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 53e94e6c..43fd5322 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ This version supports Python3. There is a separate version that only support Pyt
#### Warning ####
+Version 1.13.0 sets sha256 and rsa-sha256 as default algorithms
+
Version 1.8.0 sets strict mode active by default
Update ``python3-saml`` to ``1.5.0``, this version includes security improvements for preventing XEE and Xpath Injections.
@@ -485,7 +487,12 @@ In addition to the required settings data (idp, sp), extra settings can be defin
// Specify if you want the SP to view assertions with duplicated Name or FriendlyName attributes to be valid
// Defaults to false if not specified
- 'allowRepeatAttributeName': false
+ 'allowRepeatAttributeName': false,
+
+ // If the toolkit receive a message signed with a
+ // deprecated algoritm (defined at the constant class)
+ // will raise an error and reject the message
+ "rejectDeprecatedAlgorithm": true
},
// Contact information template, it is recommended to suply a
diff --git a/demo-django/saml/advanced_settings.json b/demo-django/saml/advanced_settings.json
index fef16fe9..3960911a 100644
--- a/demo-django/saml/advanced_settings.json
+++ b/demo-django/saml/advanced_settings.json
@@ -12,7 +12,8 @@
"wantAssertionsEncrypted": false,
"allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
- "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256",
+ "rejectDeprecatedAlgorithm": true
},
"contactPerson": {
"technical": {
diff --git a/demo-flask/saml/advanced_settings.json b/demo-flask/saml/advanced_settings.json
index fef16fe9..3960911a 100644
--- a/demo-flask/saml/advanced_settings.json
+++ b/demo-flask/saml/advanced_settings.json
@@ -12,7 +12,8 @@
"wantAssertionsEncrypted": false,
"allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
- "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256",
+ "rejectDeprecatedAlgorithm": true
},
"contactPerson": {
"technical": {
diff --git a/demo-tornado/saml/advanced_settings.json b/demo-tornado/saml/advanced_settings.json
index fef16fe9..3960911a 100644
--- a/demo-tornado/saml/advanced_settings.json
+++ b/demo-tornado/saml/advanced_settings.json
@@ -12,7 +12,8 @@
"wantAssertionsEncrypted": false,
"allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
- "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256",
+ "rejectDeprecatedAlgorithm": true
},
"contactPerson": {
"technical": {
diff --git a/demo_pyramid/demo_pyramid/saml/advanced_settings.json b/demo_pyramid/demo_pyramid/saml/advanced_settings.json
index fef16fe9..3960911a 100644
--- a/demo_pyramid/demo_pyramid/saml/advanced_settings.json
+++ b/demo_pyramid/demo_pyramid/saml/advanced_settings.json
@@ -12,7 +12,8 @@
"wantAssertionsEncrypted": false,
"allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
- "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
+ "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256",
+ "rejectDeprecatedAlgorithm": true
},
"contactPerson": {
"technical": {
diff --git a/src/onelogin/saml2/auth.py b/src/onelogin/saml2/auth.py
index d17c7d48..dc045f25 100644
--- a/src/onelogin/saml2/auth.py
+++ b/src/onelogin/saml2/auth.py
@@ -167,7 +167,6 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
self._errors.append('Signature validation failed. Logout Response rejected')
elif not logout_response.is_valid(self._request_data, request_id):
self._errors.append('invalid_logout_response')
- self._error_reason = logout_response.get_error()
elif logout_response.get_status() != OneLogin_Saml2_Constants.STATUS_SUCCESS:
self._errors.append('logout_not_success')
else:
@@ -183,7 +182,6 @@ def process_slo(self, keep_local_session=False, request_id=None, delete_session_
self._errors.append('Signature validation failed. Logout Request rejected')
elif not logout_request.is_valid(self._request_data):
self._errors.append('invalid_logout_request')
- self._error_reason = logout_request.get_error()
else:
if not keep_local_session:
OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
@@ -694,6 +692,15 @@ def _validate_signature(self, data, saml_type, raise_exceptions=False):
if isinstance(sign_alg, bytes):
sign_alg = sign_alg.decode('utf8')
+ security = self._settings.get_security_data()
+ reject_deprecated_alg = security.get('rejectDeprecatedAlgorithm', False)
+ if reject_deprecated_alg:
+ if sign_alg in OneLogin_Saml2_Constants.DEPRECATED_ALGORITHMS:
+ raise OneLogin_Saml2_ValidationError(
+ 'Deprecated signature algorithm found: %s' % sign_alg,
+ OneLogin_Saml2_ValidationError.DEPRECATED_SIGNATURE_METHOD
+ )
+
query_string = self._request_data.get('query_string')
if query_string and self._request_data.get('validate_signature_from_qs'):
signed_query = self._build_sign_query_from_qs(query_string, saml_type)
diff --git a/src/onelogin/saml2/constants.py b/src/onelogin/saml2/constants.py
index e85a7fb9..a938f8e3 100644
--- a/src/onelogin/saml2/constants.py
+++ b/src/onelogin/saml2/constants.py
@@ -114,3 +114,6 @@ class OneLogin_Saml2_Constants(object):
AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'
RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
+
+ # Define here the deprecated algorithms
+ DEPRECATED_ALGORITHMS = [DSA_SHA1, RSA_SHA1, SHA1]
diff --git a/src/onelogin/saml2/errors.py b/src/onelogin/saml2/errors.py
index 6a50f9f1..b231f982 100644
--- a/src/onelogin/saml2/errors.py
+++ b/src/onelogin/saml2/errors.py
@@ -110,6 +110,8 @@ class OneLogin_Saml2_ValidationError(Exception):
WRONG_NUMBER_OF_SIGNATURES = 43
RESPONSE_EXPIRED = 44
AUTHN_CONTEXT_MISMATCH = 45
+ DEPRECATED_SIGNATURE_METHOD = 46
+ DEPRECATED_DIGEST_METHOD = 47
def __init__(self, message, code=0, errors=None):
"""
diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py
index 4ef0418c..3cf0964e 100644
--- a/src/onelogin/saml2/response.py
+++ b/src/onelogin/saml2/response.py
@@ -653,6 +653,9 @@ def process_signed_elements(self):
response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML
+ security = self._settings.get_security_data()
+ reject_deprecated_alg = security.get('rejectDeprecatedAlgorithm', False)
+
for sign_node in sign_nodes:
signed_element = sign_node.getparent().tag
if signed_element != response_tag and signed_element != assertion_tag:
@@ -695,6 +698,26 @@ def process_signed_elements(self):
)
verified_seis.append(sei)
+ # Check the signature and digest algorithm
+ if reject_deprecated_alg:
+ sig_method_node = OneLogin_Saml2_XML.query(sign_node, './/ds:SignatureMethod')
+ if sig_method_node:
+ sig_method = sig_method_node[0].get("Algorithm")
+ if sig_method in OneLogin_Saml2_Constants.DEPRECATED_ALGORITHMS:
+ raise OneLogin_Saml2_ValidationError(
+ 'Deprecated signature algorithm found: %s' % sig_method,
+ OneLogin_Saml2_ValidationError.DEPRECATED_SIGNATURE_METHOD
+ )
+
+ dig_method_node = OneLogin_Saml2_XML.query(sign_node, './/ds:DigestMethod')
+ if dig_method_node:
+ dig_method = dig_method_node[0].get("Algorithm")
+ if dig_method in OneLogin_Saml2_Constants.DEPRECATED_ALGORITHMS:
+ raise OneLogin_Saml2_ValidationError(
+ 'Deprecated digest algorithm found: %s' % dig_method,
+ OneLogin_Saml2_ValidationError.DEPRECATED_DIGEST_METHOD
+ )
+
signed_elements.append(signed_element)
if signed_elements:
diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py
index c258758c..e6ea7b74 100644
--- a/src/onelogin/saml2/settings.py
+++ b/src/onelogin/saml2/settings.py
@@ -312,6 +312,9 @@ def _add_default_values(self):
# Digest Algorithm
self._security.setdefault('digestAlgorithm', OneLogin_Saml2_Constants.SHA256)
+ # Reject Deprecated Algorithms
+ self._security.setdefault('rejectDeprecatedAlgorithm', False)
+
# AttributeStatement required by default
self._security.setdefault('wantAttributeStatement', True)
diff --git a/tests/src/OneLogin/saml2_tests/auth_test.py b/tests/src/OneLogin/saml2_tests/auth_test.py
index 4a872c81..efaf1b40 100644
--- a/tests/src/OneLogin/saml2_tests/auth_test.py
+++ b/tests/src/OneLogin/saml2_tests/auth_test.py
@@ -1215,9 +1215,31 @@ def testIsInValidLogoutResponseSign(self):
auth.process_slo()
self.assertIn('Signature validation failed. Logout Response rejected', auth.get_errors())
+ def testIsInValidLogoutResponseSignatureRejectingDeprecatedAlgorithm(self):
+ """
+ Tests the process_slo method of the OneLogin_Saml2_Auth
+ """
+ request_data = {
+ 'http_host': 'example.com',
+ 'script_name': 'index.html',
+ 'get_data': {
+ 'SAMLResponse': 'fZHbasJAEIZfJey9ZrNZc1gSodRSBKtQxYveyGQz1kCyu2Q24OM3jS21UHo3p++f4Z+CoGud2th3O/hXJGcNYXDtWkNqapVs6I2yQA0pAx2S8lrtH142Ssy5cr31VtuW3SH/E0CEvW+sYcF6VbLTIktFLMWZgxQR8DSP85wDB4GJGMOqShYVaoBUsOCIPY1kyUahEScacG3Ig/FjiUdyxuOZ4IcoUVGq4vSNBSsk3xjwE3Xx3qkwJD+cz3NtuxBN7WxjPN1F1NLcXdwob77tONiS7bZPm93zenvCqopxgVJmuU50jREsZF4noKWAOuNZJbNznnBky+LTDDVd2S+/dje1m+MVOtfidEER3g8Vt2fsPfiBfmePtsbgCO2A/9tL07TaD1ojEQuXtw0/ouFfD19+AA==',
+ 'RelayState': 'http://stuff.com/endpoints/endpoints/index.php',
+ 'SigAlg': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
+ 'Signature': 'OV9c4R0COSjN69fAKCpV7Uj/yx6/KFxvbluVCzdK3UuortpNMpgHFF2wYNlMSG9GcYGk6p3I8nB7Z+1TQchMWZOlO/StjAqgtZhtpiwPcWryNuq8vm/6hnJ3zMDhHTS7F8KG4qkCXmJ9sQD3Y31UNcuygBwIbNakvhDT5Qo9Nsw='
+ }
+ }
+ settings_info = self.loadSettingsJSON('settings8.json')
+ settings_info['security']['rejectDeprecatedAlgorithm'] = True
+ settings = OneLogin_Saml2_Settings(settings_info)
+ auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
+ auth.process_slo()
+ self.assertIn('Signature validation failed. Logout Response rejected', auth.get_errors())
+ self.assertEqual('Deprecated signature algorithm found: http://www.w3.org/2000/09/xmldsig#rsa-sha1', auth.get_last_error_reason())
+
def testIsValidLogoutRequestSign(self):
"""
- Tests the is_valid method of the OneLogin_Saml2_LogoutRequest
+ Tests the process_slo method of the OneLogin_Saml2_Auth
"""
request_data = {
'http_host': 'example.com',
@@ -1303,6 +1325,29 @@ def testIsValidLogoutRequestSign(self):
auth.process_slo()
self.assertIn('Signature validation failed. Logout Request rejected', auth.get_errors())
+ def testIsInValidLogoutRequestSignatureRejectingDeprecatedAlgorithm(self):
+ """
+ Tests the process_slo method of the OneLogin_Saml2_Auth
+ """
+ request_data = {
+ 'http_host': 'example.com',
+ 'script_name': 'index.html',
+ 'get_data': {
+ 'SAMLRequest': 'fZJNa+MwEIb/itHdiTz6sC0SQyEsBPoB27KHXoIsj7cGW3IlGfLzV7G7kN1DL2KYmeedmRcdgp7GWT26326JP/FzwRCz6zTaoNbKkSzeKqfDEJTVEwYVjXp9eHpUsKNq9i4640Zyh3xP6BDQx8FZkp1PR3KpqexAl72QmpUCS8SW01IiZz2TVVGD4X1VQYlAsl/oQyKPJAklPIQFzzZEbWNK0YLnlOVA3wqpQCoB7yQ7pWsGq+NKfcQ4q/0+xKXvd8ZNe7Td7AYbw10UxrCbP2aSPbv4Yl/8Qx/R3+SB5bTOoXiDQvFNvjnc7lXrIr75kh+6eYdXPc0jrkMO+/umjXhOtpxP2Q/nJx2/9+uWGbq8X1tV9NqGAW0kzaVvoe1AAJeCSWqYaUVRM2SilKKuqDTpFSlszdcK29RthVm9YriZebYdXpsLdhVAB7VJzif3haYMqqTVcl0JMBR4y+s2zak3sf/4v8l/vlHzBw==',
+ 'RelayState': '_1037fbc88ec82ce8e770b2bed1119747bb812a07e6',
+ 'SigAlg': 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
+ 'Signature': 'Ouxo9BV6zmq4yrgamT9EbSKy/UmvSxGS8z26lIMgKOEP4LFR/N23RftdANmo4HafrzSfA0YTXwhKDqbOByS0j+Ql8OdQOes7vGioSjo5qq/Bi+5i6jXwQfphnfcHAQiJL4gYVIifkhhHRWpvYeiysF1Y9J02me0izwazFmoRXr4='
+ }
+ }
+ settings_info = self.loadSettingsJSON('settings8.json')
+ settings_info = self.loadSettingsJSON('settings8.json')
+ settings_info['security']['rejectDeprecatedAlgorithm'] = True
+ settings = OneLogin_Saml2_Settings(settings_info)
+ auth = OneLogin_Saml2_Auth(request_data, old_settings=settings)
+ auth.process_slo()
+ self.assertIn('Signature validation failed. Logout Request rejected', auth.get_errors())
+ self.assertEqual('Deprecated signature algorithm found: http://www.w3.org/2000/09/xmldsig#rsa-sha1', auth.get_last_error_reason())
+
def testGetLastRequestID(self):
settings_info = self.loadSettingsJSON()
request_data = self.get_request()
diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py
index 3f83bc2b..ada973a6 100644
--- a/tests/src/OneLogin/saml2_tests/response_test.py
+++ b/tests/src/OneLogin/saml2_tests/response_test.py
@@ -1030,6 +1030,19 @@ def testIsInValidNoKey(self):
with self.assertRaisesRegex(Exception, 'Signature validation failed. SAML Response rejected'):
response.is_valid(self.get_request_data(), raise_exceptions=True)
+ def testIsInValidDeprecatedAlgorithm(self):
+ """
+ Tests the is_valid method of the OneLogin_Saml2_Response
+ Case Deprecated algorithm used
+ """
+ settings_dict = self.loadSettingsJSON()
+ settings_dict['security']['rejectDeprecatedAlgorithm'] = True
+ settings = OneLogin_Saml2_Settings(settings_dict)
+ xml = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64'))
+ response = OneLogin_Saml2_Response(settings, xml)
+ with self.assertRaisesRegex(Exception, 'Deprecated signature algorithm found: http://www.w3.org/2000/09/xmldsig#rsa-sha1'):
+ response.is_valid(self.get_request_data(), raise_exceptions=True)
+
def testIsInValidMultipleAssertions(self):
"""
Tests the is_valid method of the OneLogin_Saml2_Response
From 4fe73342d61363a0b7b7ec2f8a8fdc2caee21206 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 22:47:16 +0100
Subject: [PATCH 246/331] Release 1.13.0
---
changelog.md | 10 ++++++++++
setup.py | 4 ++--
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/changelog.md b/changelog.md
index cc17cb4e..2dc040c6 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,14 @@
# python3-saml changelog
+### 1.13.0 (Jan 28, 2022)
+
+- [#296](https://github.com/onelogin/python3-saml/pull/296) Add rejectDeprecatedAlgorithm settings in order to be able reject messages signed with deprecated algorithms.
+- Set sha256 and rsa-sha256 as default algorithms
+- [#288](https://github.com/onelogin/python3-saml/pull/288) Support building a LogoutResponse with non-success status
+- Added warning about Open Redirect and Reply attacks
+- [##274](https://github.com/onelogin/python3-saml/pull/274) Replace double-underscored names with single underscores
+- Add at OneLogin_Saml2_Auth get_last_assertion_issue_instant() and get_last_response_in_response_to() methods
+- Upgrade dependencies
+
### 1.12.0 (Aug 13, 2021)
* [#276](https://github.com/onelogin/python3-saml/pull/276) Deprecate server_port from request data dictionary
diff --git a/setup.py b/setup.py
index 21108ec8..c309285b 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2021 OneLogin, Inc.
+# Copyright (c) 2010-2022 OneLogin, Inc.
# MIT License
from setuptools import setup
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.12.0',
+ version='1.13.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From dc05f2bec590088e54d88f78af78034bb0f1c6c0 Mon Sep 17 00:00:00 2001
From: Sixto Martin
Date: Fri, 28 Jan 2022 22:48:38 +0100
Subject: [PATCH 247/331] Remove extra space
---
changelog.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index 2dc040c6..cb4a5024 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,5 @@
# python3-saml changelog
### 1.13.0 (Jan 28, 2022)
-
- [#296](https://github.com/onelogin/python3-saml/pull/296) Add rejectDeprecatedAlgorithm settings in order to be able reject messages signed with deprecated algorithms.
- Set sha256 and rsa-sha256 as default algorithms
- [#288](https://github.com/onelogin/python3-saml/pull/288) Support building a LogoutResponse with non-success status
From c94fe93172de89140bed3fca3957544570aa0c91 Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Mon, 31 Jan 2022 11:29:52 +0200
Subject: [PATCH 248/331] Don't require yanked version of lxml
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index c309285b..b794f921 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@
},
test_suite='tests',
install_requires=[
- 'lxml==4.7.0',
+ 'lxml~=4.7.1',
'isodate>=0.6.1',
'xmlsec>=1.3.9'
],
From b75a927bd0475b3de8cac3ffee6da7aa0ba2e75d Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Mon, 31 Jan 2022 11:23:58 +0200
Subject: [PATCH 249/331] CI: fix targeting rules
---
.github/workflows/python-package.yml | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 1cc6b96a..5dcec1eb 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -3,7 +3,13 @@
name: Python package
-on: [push, pull_request]
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
jobs:
test:
From 34ffd8c46d16f4d5babd16e8e44cd2c7eb429610 Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Mon, 31 Jan 2022 11:09:40 +0200
Subject: [PATCH 250/331] CI: fix quoting for python-version list
---
.github/workflows/python-package.yml | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 5dcec1eb..53fd4645 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -17,8 +17,13 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: [2.7,3.5,3.6,3.7, 3.8,3.9]
-
+ python-version:
+ - "2.7"
+ - "3.5"
+ - "3.6"
+ - "3.7"
+ - "3.8"
+ - "3.9"
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
From 38f44ba9e09e7e4b55e7699f59c7e2a5e71487eb Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Mon, 31 Jan 2022 11:32:20 +0200
Subject: [PATCH 251/331] CI: upgrade, don't downgrade, setuptools
---
.github/workflows/python-package.yml | 2 ++
Makefile | 1 -
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 53fd4645..6330e7f7 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -38,6 +38,7 @@ jobs:
${{ runner.os }}-pip-
- name: Install dependencies
run: |
+ pip install -U setuptools
sudo apt-get update -qq
sudo apt-get install -qq swig python-dev libxml2-dev libxmlsec1-dev
make install-req
@@ -59,6 +60,7 @@ jobs:
${{ runner.os }}-pip-
- name: Install dependencies
run: |
+ pip install -U setuptools
sudo apt-get update -qq
sudo apt-get install -qq swig python-dev libxml2-dev libxmlsec1-dev
make install-req
diff --git a/Makefile b/Makefile
index 45d73ecb..95848867 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,6 @@ TESTS=tests/src/OneLogin/saml2_tests
SOURCES=$(MAIN_SOURCE) $(DEMOS) $(TESTS)
install-req:
- $(PIP) install --upgrade 'setuptools<45.0.0'
$(PIP) install .
install-test:
From 891228ed247c19c22ced8c6cbe7c0dd7f4392fbd Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Mon, 31 Jan 2022 11:33:18 +0200
Subject: [PATCH 252/331] CI: run on Python 3.10
Refs #294
---
.github/workflows/python-package.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 6330e7f7..0f1e37f2 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -24,6 +24,7 @@ jobs:
- "3.7"
- "3.8"
- "3.9"
+ - "3.10"
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -51,7 +52,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
- python-version: 3.9
+ python-version: "3.10"
- uses: actions/cache@v2
with:
path: ~/.cache/pip
From 66f367de4b0462d5d3011bbc21253554162bf07a Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Mon, 31 Jan 2022 11:33:27 +0200
Subject: [PATCH 253/331] Set Python 3.10 trove specifier
---
setup.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/setup.py b/setup.py
index c309285b..b49ab57f 100644
--- a/setup.py
+++ b/setup.py
@@ -23,6 +23,7 @@
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
],
author='OneLogin',
author_email='support@onelogin.com',
From f10edf68feaddeb0472819d220f28014c8ac8834 Mon Sep 17 00:00:00 2001
From: Aarni Koskela
Date: Mon, 31 Jan 2022 12:43:43 +0200
Subject: [PATCH 254/331] Remove coveralls mentions
The stats haven't been updated since 2017
---
.travis.yml | 2 --
README.md | 1 -
setup.py | 1 -
3 files changed, 4 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index b690a1d0..820f9499 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,5 +23,3 @@ script:
- 'coverage report -m --rcfile=tests/coverage.rc'
# - 'pylint src/onelogin/saml2 --rcfile=tests/pylint.rc'
- 'flake8 .'
-
-after_success: 'coveralls'
diff --git a/README.md b/README.md
index 43fd5322..8894edaf 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
# OneLogin's SAML Python Toolkit (compatible with Python3)
[](http://travis-ci.org/onelogin/python3-saml)
-[](https://coveralls.io/github/onelogin/python3-saml?branch=master)
[](https://pypi.python.org/pypi/python3-saml)

diff --git a/setup.py b/setup.py
index c309285b..04c23b40 100644
--- a/setup.py
+++ b/setup.py
@@ -49,7 +49,6 @@
'freezegun>=0.3.11, <=1.1.0',
'pylint==1.9.4',
'flake8>=3.6.0',
- 'coveralls>=1.11.1',
'pytest>=4.6',
),
},
From 3dad0d3fff75636a70840c55ff430801227246b1 Mon Sep 17 00:00:00 2001
From: eriktalvi
Date: Tue, 15 Feb 2022 16:25:49 -0800
Subject: [PATCH 255/331] Update setup.py
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index b794f921..a148b49a 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@
},
test_suite='tests',
install_requires=[
- 'lxml~=4.7.1',
+ 'lxml>4.7.1',
'isodate>=0.6.1',
'xmlsec>=1.3.9'
],
From 34098971c8bb4ed810849db65a78e5066f2725e7 Mon Sep 17 00:00:00 2001
From: eriktalvi
Date: Tue, 15 Feb 2022 16:27:47 -0800
Subject: [PATCH 256/331] Update setup.py
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index a148b49a..691f86d5 100644
--- a/setup.py
+++ b/setup.py
@@ -38,7 +38,7 @@
},
test_suite='tests',
install_requires=[
- 'lxml>4.7.1',
+ 'lxml<4.7.1',
'isodate>=0.6.1',
'xmlsec>=1.3.9'
],
From 809912de0862dd0e44fcbb11774d8da7b64e3418 Mon Sep 17 00:00:00 2001
From: Bryan Vestey
Date: Fri, 18 Feb 2022 14:50:12 -0800
Subject: [PATCH 257/331] Release 1.14.0
---
changelog.md | 5 +++++
setup.py | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/changelog.md b/changelog.md
index cb4a5024..93037656 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,9 @@
# python3-saml changelog
+### 1.14.0 (Feb 18, 2022)
+- [#297](https://github.com/onelogin/python3-saml/pull/297) Don't require yanked version of lxml.
+- [#298](https://github.com/onelogin/python3-saml/pull/298) Add support for python 3.10 and cleanup the GHA.
+- [#299](https://github.com/onelogin/python3-saml/pull/299) Remove stats from coveralls removed as they are no longer maintained.
+
### 1.13.0 (Jan 28, 2022)
- [#296](https://github.com/onelogin/python3-saml/pull/296) Add rejectDeprecatedAlgorithm settings in order to be able reject messages signed with deprecated algorithms.
- Set sha256 and rsa-sha256 as default algorithms
diff --git a/setup.py b/setup.py
index 6df390ec..21105b36 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
setup(
name='python3-saml',
- version='1.13.0',
+ version='1.14.0',
description='Onelogin Python Toolkit. Add SAML support to your Python software using this library',
classifiers=[
'Development Status :: 5 - Production/Stable',
From 220a3359afd7db10fbbd11f6c359d91960b0ef9d Mon Sep 17 00:00:00 2001
From: Noam <69756316+noamsan@users.noreply.github.com>
Date: Mon, 18 Jul 2022 13:54:30 +0300
Subject: [PATCH 258/331] Typo fix: reply -> replay
"Reply attacks" should be "Replay attacks".
---
README.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 8894edaf..d22347a2 100644
--- a/README.md
+++ b/README.md
@@ -139,9 +139,9 @@ a trusted and expected URL.
Read more about Open Redirect [CWE-601](https://cwe.mitre.org/data/definitions/601.html).
-### Avoiding Reply attacks ###
+### Avoiding Replay attacks ###
-A reply attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).
+A replay attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).
SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that
make harder this kind of attacks, but they are still possible.
@@ -152,8 +152,7 @@ we don't need to store all processed message/assertion Ids, but the most recent
The OneLogin_Saml2_Auth class contains the [get_last_request_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L357), [get_last_message_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L364) and [get_last_assertion_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L371) methods to retrieve the IDs
-Checking that the ID of the current Message/Assertion does not exists in the lis of the ones already processed will prevent reply
-attacks.
+Checking that the ID of the current Message/Assertion does not exists in the lis of the ones already processed will prevent replay attacks.
Getting Started
From 0a79044f0c429132ceb79f527677a4ca5caa3124 Mon Sep 17 00:00:00 2001
From: Anand Nanduri
Date: Wed, 3 Aug 2022 17:58:00 -0400
Subject: [PATCH 259/331] handle unicode characters gracefully in python 2
---
src/onelogin/saml2/compat.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/onelogin/saml2/compat.py b/src/onelogin/saml2/compat.py
index 90bcfac7..804d4d46 100644
--- a/src/onelogin/saml2/compat.py
+++ b/src/onelogin/saml2/compat.py
@@ -39,6 +39,8 @@ def to_string(data):
def to_bytes(data):
""" return bytes """
+ if isinstance(data, unicode):
+ return data.encode("utf8")
return str(data)
else: # py 3.x
From ff2b31c0083605f032f3bd3b6d350d87b3ca94aa Mon Sep 17 00:00:00 2001
From: Bryan Vestey
Date: Fri, 12 Aug 2022 09:46:11 -0700
Subject: [PATCH 260/331] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index d22347a2..6c2a9127 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
[](https://pypi.python.org/pypi/python3-saml)

+**Notice:** This project is currently not under active development, please see [#320](https://github.com/onelogin/python3-saml/issues/320) for more information.
Add SAML support to your Python software using this library.
Forget those complicated libraries and use the open source library provided
From ba572e24fd3028c0e38c8f9dcd02af46ddcc0870 Mon Sep 17 00:00:00 2001
From: Bryan Vestey
Date: Fri, 12 Aug 2022 09:47:11 -0700
Subject: [PATCH 261/331] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6c2a9127..9986ff5e 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://pypi.python.org/pypi/python3-saml)

-**Notice:** This project is currently not under active development, please see [#320](https://github.com/onelogin/python3-saml/issues/320) for more information.
+## **Notice:** This project is currently not under active development, please see [#320](https://github.com/onelogin/python3-saml/issues/320) for more information.
Add SAML support to your Python software using this library.
Forget those complicated libraries and use the open source library provided
From 344c28640f8a718aeffd72f1ff56835488965eb0 Mon Sep 17 00:00:00 2001
From: George Khaburzaniya
Date: Fri, 18 Nov 2022 13:31:08 -0800
Subject: [PATCH 262/331] Remove references to onelogin provided support.
---
LICENSE | 2 +-
README.md | 52 ++-
demo-django/saml/certs/README | 2 +-
demo-django/templates/base.html | 4 +-
demo-flask/saml/certs/README | 2 +-
demo-flask/templates/base.html | 4 +-
demo-tornado/saml/certs/README | 2 +-
demo-tornado/templates/base.html | 4 +-
demo_pyramid/demo_pyramid/saml/certs/README | 2 +-
docs/saml2/_modules/index.html | 21 +-
docs/saml2/_modules/saml2/auth.html | 29 +-
docs/saml2/_modules/saml2/authn_request.html | 30 +-
docs/saml2/_modules/saml2/constants.html | 30 +-
docs/saml2/_modules/saml2/errors.html | 30 +-
docs/saml2/_modules/saml2/logout_request.html | 30 +-
.../saml2/_modules/saml2/logout_response.html | 29 +-
docs/saml2/_modules/saml2/metadata.html | 29 +-
docs/saml2/_modules/saml2/response.html | 29 +-
docs/saml2/_modules/saml2/settings.html | 29 +-
docs/saml2/_modules/saml2/utils.html | 29 +-
docs/saml2/_sources/index.txt | 2 +-
docs/saml2/genindex.html | 359 +++++++++---------
docs/saml2/index.html | 27 +-
docs/saml2/py-modindex.html | 23 +-
docs/saml2/saml2.html | 29 +-
docs/saml2/search.html | 29 +-
setup.py | 9 +-
src/onelogin/__init__.py | 6 +-
src/onelogin/saml2/__init__.py | 6 +-
src/onelogin/saml2/auth.py | 4 +-
src/onelogin/saml2/authn_request.py | 4 +-
src/onelogin/saml2/compat.py | 2 -
src/onelogin/saml2/constants.py | 6 +-
src/onelogin/saml2/errors.py | 4 +-
src/onelogin/saml2/idp_metadata_parser.py | 4 +-
src/onelogin/saml2/logout_request.py | 4 +-
src/onelogin/saml2/logout_response.py | 4 +-
src/onelogin/saml2/metadata.py | 4 +-
src/onelogin/saml2/response.py | 4 +-
src/onelogin/saml2/utils.py | 4 +-
src/onelogin/saml2/xml_templates.py | 4 +-
src/onelogin/saml2/xml_utils.py | 4 +-
tests/data/metadata/idp_metadata.xml | 4 +-
...tadata_different_sign_and_encrypt_cert.xml | 4 +-
...dp_metadata_same_sign_and_encrypt_cert.xml | 4 +-
tests/src/OneLogin/saml2_tests/auth_test.py | 2 -
.../saml2_tests/authn_request_test.py | 2 -
tests/src/OneLogin/saml2_tests/error_test.py | 2 -
.../saml2_tests/idp_metadata_parser_test.py | 2 -
.../saml2_tests/logout_request_test.py | 2 -
.../saml2_tests/logout_response_test.py | 2 -
.../src/OneLogin/saml2_tests/metadata_test.py | 2 -
.../src/OneLogin/saml2_tests/response_test.py | 8 +-
.../src/OneLogin/saml2_tests/settings_test.py | 2 -
.../saml2_tests/signed_response_test.py | 2 -
tests/src/OneLogin/saml2_tests/utils_test.py | 2 -
.../OneLogin/saml2_tests/xml_utils_test.py | 2 -
57 files changed, 433 insertions(+), 540 deletions(-)
diff --git a/LICENSE b/LICENSE
index 734c18ba..0fd253e9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010-2021 OneLogin, Inc.
+Copyright (c) 2010-2022 OneLogin, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
diff --git a/README.md b/README.md
index 9986ff5e..08f15fde 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,13 @@
-# OneLogin's SAML Python Toolkit (compatible with Python3)
+# SAML Python Toolkit (compatible with Python3)
[](http://travis-ci.org/onelogin/python3-saml)
[](https://pypi.python.org/pypi/python3-saml)

-## **Notice:** This project is currently not under active development, please see [#320](https://github.com/onelogin/python3-saml/issues/320) for more information.
-
Add SAML support to your Python software using this library.
-Forget those complicated libraries and use the open source library provided
-and supported by OneLogin Inc.
+Forget those complicated libraries and use the open source library provided by the SAML tool community.
-This version supports Python3. There is a separate version that only support Python2: [python-saml](https://github.com/onelogin/python-saml)
+This version supports Python3. Python 2 support was deprecated on Jan 1st, 2020: [python-saml](https://github.com/onelogin/python-saml)
#### Warning ####
@@ -34,7 +31,7 @@ Update ``python3-saml`` to ``>= 1.2.1``, ``1.2.0`` had a bug on signature valida
#### Security Guidelines ####
-If you believe you have discovered a security vulnerability in this toolkit, please report it at https://www.onelogin.com/security with a description. We follow responsible disclosure guidelines, and will work with you to quickly find a resolution.
+If you believe you have discovered a security vulnerability in this toolkit, please report it in an issue with a description. We follow responsible disclosure guidelines, and will work with you to quickly find a resolution.
Why add SAML support to my software?
------------------------------------
@@ -62,7 +59,7 @@ since 2002, but lately it is becoming popular due its advantages:
General Description
-------------------
-OneLogin's SAML Python toolkit lets you turn your Python application into a SP
+SAML Python toolkit lets you turn your Python application into a SP
(Service Provider) that can be connected to an IdP (Identity Provider).
**Supports:**
@@ -83,7 +80,6 @@ OneLogin's SAML Python toolkit lets you turn your Python application into a SP
* **Easy to use** - Programmer will be allowed to code high-level and
low-level programming, 2 easy to use APIs are available.
* **Tested** - Thoroughly tested.
- * **Popular** - OneLogin's customers use it. Add easy support to your Django/Flask web projects.
Installation
------------
@@ -103,8 +99,8 @@ Review the ``setup.py`` file to know the version of the library that ``python3-s
The toolkit is hosted on GitHub. You can download it from:
- * Latest release: https://github.com/onelogin/python3-saml/releases/latest
- * Master repo: https://github.com/onelogin/python3-saml/tree/master
+ * Latest release: https://github.com/saml-toolkits/python3-saml/releases/latest
+ * Master repo: https://github.com/saml-toolkits/python3-saml/tree/master
Copy the core of the library ``(src/onelogin/saml2 folder)`` and merge the ``setup.py`` inside the Python application. (Each application has its structure so take your time to locate the Python SAML toolkit in the best place).
@@ -148,10 +144,10 @@ SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that
make harder this kind of attacks, but they are still possible.
In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs alredy valdidated and processed. Those values only need
-to be stored the amount of time of the SAML Message life time, so
+to be stored the amount of time of the SAML Message life time, so
we don't need to store all processed message/assertion Ids, but the most recent ones.
-The OneLogin_Saml2_Auth class contains the [get_last_request_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L357), [get_last_message_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L364) and [get_last_assertion_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L371) methods to retrieve the IDs
+The OneLogin_Saml2_Auth class contains the [get_last_request_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L357), [get_last_message_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L364) and [get_last_assertion_id](https://github.com/onelogin/python3-saml/blob/ab62b0d6f3e5ac2ae8e95ce3ed2f85389252a32d/src/onelogin/saml2/auth.py#L371) methods to retrieve the IDs
Checking that the ID of the current Message/Assertion does not exists in the lis of the ones already processed will prevent replay attacks.
@@ -161,7 +157,7 @@ Getting Started
### Knowing the toolkit ###
-The new OneLogin SAML Toolkit contains different folders (``certs``, ``lib``, ``demo-django``, ``demo-flask`` and ``tests``) and some files.
+The new SAML Toolkit contains different folders (``certs``, ``lib``, ``demo-django``, ``demo-flask`` and ``tests``) and some files.
Let's start describing them:
@@ -267,7 +263,7 @@ This is the ``settings.json`` file:
// URL Location where the from the IdP will be returned
"url": "https:///?acs",
// SAML protocol binding to be used when returning the
- // message. OneLogin Toolkit supports this endpoint for the
+ // message. SAML Toolkit supports this endpoint for the
// HTTP-POST binding only.
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
@@ -279,7 +275,7 @@ This is the ``settings.json`` file:
// OPTIONAL: only specify if different from url parameter
//"responseUrl": "https:///?sls",
// SAML protocol binding to be used when returning the
- // message. OneLogin Toolkit supports the HTTP-Redirect binding
+ // message. SAML Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
@@ -289,7 +285,7 @@ This is the ``settings.json`` file:
"attributeConsumingService": {
// OPTIONAL: only specifiy if SP requires this.
// index is an integer which identifies the attributeConsumingService used
- // to the SP. OneLogin toolkit supports configuring only one attributeConsumingService
+ // to the SP. SAML toolkit supports configuring only one attributeConsumingService
// but in certain cases the SP requires a different value. Defaults to '1'.
// "index": '1',
"serviceName": "SP test",
@@ -333,7 +329,7 @@ This is the ``settings.json`` file:
// will be sent.
"url": "https://app.onelogin.com/trust/saml2/http-post/sso/",
// SAML protocol binding to be used when returning the
- // message. OneLogin Toolkit supports the HTTP-Redirect binding
+ // message. SAML Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
@@ -342,10 +338,10 @@ This is the ``settings.json`` file:
// URL Location where the from the IdP will be sent (IdP-initiated logout)
"url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/",
// URL Location where the from the IdP will sent (SP-initiated logout, reply)
- // OPTIONAL: only specify if different from url parameter
+ // OPTIONAL: only specify if different from url parameter
"responseUrl": "https://app.onelogin.com/trust/saml2/http-redirect/slo_return/",
// SAML protocol binding to be used when returning the
- // message. OneLogin Toolkit supports the HTTP-Redirect binding
+ // message. SAML Toolkit supports the HTTP-Redirect binding
// only for this endpoint.
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
@@ -483,7 +479,7 @@ In addition to the required settings data (idp, sp), extra settings can be defin
// 'http://www.w3.org/2001/04/xmldsig-more#sha384'
// 'http://www.w3.org/2001/04/xmlenc#sha512'
'digestAlgorithm': "http://www.w3.org/2001/04/xmlenc#sha256",
-
+
// Specify if you want the SP to view assertions with duplicated Name or FriendlyName attributes to be valid
// Defaults to false if not specified
'allowRepeatAttributeName': false,
@@ -562,7 +558,7 @@ There's an easier method -- use a metadata exchange. Metadata is just an XML fi
Using ````parse_remote```` IdP metadata can be obtained and added to the settings without further ado.
-Take in mind that the OneLogin_Saml2_IdPMetadataParser class does not validate in any way the URL that is introduced in order to be parsed.
+Take in mind that the OneLogin_Saml2_IdPMetadataParser class does not validate in any way the URL that is introduced in order to be parsed.
Usually the same administrator that handles the Service Provider also sets the URL to the IdP, which should be a trusted resource.
@@ -985,7 +981,7 @@ Described below are the main classes and methods that can be invoked from the SA
#### OneLogin_Saml2_Auth - auth.py ####
-Main class of OneLogin Python Toolkit
+Main class of SAML Python Toolkit
* `__init__` Initializes the SP SAML instance.
* ***login*** Initiates the SSO process.
@@ -1078,7 +1074,7 @@ SAML 2 Logout Response class
#### OneLogin_Saml2_Settings - settings.py ####
-Configuration of the OneLogin Python Toolkit
+Configuration of the SAML Python Toolkit
* `__init__` Initializes the settings: Sets the paths of the different folders and Loads settings info from settings file or array/object provided.
* ***check_settings*** Checks the settings info.
@@ -1246,7 +1242,7 @@ The flask project contains:
#### SP setup ####
-The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-flask``, it uses the first method.
+The SAML Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-flask``, it uses the first method.
In the ``index.py`` file we define the ``app.config['SAML_PATH']``, that will target to the ``saml`` folder. We require it in order to load the settings files.
@@ -1319,7 +1315,7 @@ The tornado project contains:
#### SP setup ####
-The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-tornado``, it uses the first method.
+The SAML Python Toolkit allows you to provide the settings info in 2 ways: Settings files or define a setting dict. In the ``demo-tornado``, it uses the first method.
In the ``settings.py`` file we define the ``SAML_PATH``, that will target to the ``saml`` folder. We require it in order to load the settings files.
@@ -1392,7 +1388,7 @@ The django project contains:
#### SP setup ####
-The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In the demo-django it used the first method.
+The SAML Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In the demo-django it used the first method.
After set the ``SAML_FOLDER`` in the ``demo/settings.py``, the settings of the Python toolkit will be loaded on the Django web.
@@ -1472,7 +1468,7 @@ The Pyramid project contains:
#### SP setup ####
-The Onelogin's Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In ``demo_pyramid`` the first method is used.
+The SAML Python Toolkit allows you to provide the settings info in 2 ways: settings files or define a setting dict. In ``demo_pyramid`` the first method is used.
In the ``views.py`` file we define the ``SAML_PATH``, which will target the ``saml`` folder. We require it in order to load the settings files.
diff --git a/demo-django/saml/certs/README b/demo-django/saml/certs/README
index 7e837fb9..ed973e05 100644
--- a/demo-django/saml/certs/README
+++ b/demo-django/saml/certs/README
@@ -1,6 +1,6 @@
Take care of this folder that could contain private key. Be sure that this folder never is published.
-Onelogin Python Toolkit expects that certs for the SP could be stored in this folder as:
+SAML Python Toolkit expects that certs for the SP could be stored in this folder as:
* sp.key Private Key
* sp.crt Public cert
diff --git a/demo-django/templates/base.html b/demo-django/templates/base.html
index a55dbf0b..960ca0bc 100644
--- a/demo-django/templates/base.html
+++ b/demo-django/templates/base.html
@@ -5,7 +5,7 @@
- A Python SAML Toolkit by OneLogin demo
+ A Python SAML Toolkit demo
@@ -18,7 +18,7 @@
- A Python SAML Toolkit by OneLogin demo
+ A Python SAML Toolkit demo
{% block content %}{% endblock %}
diff --git a/demo-flask/saml/certs/README b/demo-flask/saml/certs/README
index 7e837fb9..ed973e05 100644
--- a/demo-flask/saml/certs/README
+++ b/demo-flask/saml/certs/README
@@ -1,6 +1,6 @@
Take care of this folder that could contain private key. Be sure that this folder never is published.
-Onelogin Python Toolkit expects that certs for the SP could be stored in this folder as:
+SAML Python Toolkit expects that certs for the SP could be stored in this folder as:
* sp.key Private Key
* sp.crt Public cert
diff --git a/demo-flask/templates/base.html b/demo-flask/templates/base.html
index a55dbf0b..960ca0bc 100644
--- a/demo-flask/templates/base.html
+++ b/demo-flask/templates/base.html
@@ -5,7 +5,7 @@
- A Python SAML Toolkit by OneLogin demo
+ A Python SAML Toolkit demo
@@ -18,7 +18,7 @@
- A Python SAML Toolkit by OneLogin demo
+ A Python SAML Toolkit demo
{% block content %}{% endblock %}
diff --git a/demo-tornado/saml/certs/README b/demo-tornado/saml/certs/README
index 7e837fb9..ed973e05 100644
--- a/demo-tornado/saml/certs/README
+++ b/demo-tornado/saml/certs/README
@@ -1,6 +1,6 @@
Take care of this folder that could contain private key. Be sure that this folder never is published.
-Onelogin Python Toolkit expects that certs for the SP could be stored in this folder as:
+SAML Python Toolkit expects that certs for the SP could be stored in this folder as:
* sp.key Private Key
* sp.crt Public cert
diff --git a/demo-tornado/templates/base.html b/demo-tornado/templates/base.html
index e403a8ef..e71bb136 100644
--- a/demo-tornado/templates/base.html
+++ b/demo-tornado/templates/base.html
@@ -5,7 +5,7 @@
- A Python SAML Toolkit by OneLogin demo
+ A Python SAML Toolkit demo
@@ -18,7 +18,7 @@
- A Python SAML Toolkit by OneLogin demo
+ A Python SAML Toolkit demo
{% block content %}{% end %}
diff --git a/demo_pyramid/demo_pyramid/saml/certs/README b/demo_pyramid/demo_pyramid/saml/certs/README
index 7e837fb9..ed973e05 100644
--- a/demo_pyramid/demo_pyramid/saml/certs/README
+++ b/demo_pyramid/demo_pyramid/saml/certs/README
@@ -1,6 +1,6 @@
Take care of this folder that could contain private key. Be sure that this folder never is published.
-Onelogin Python Toolkit expects that certs for the SP could be stored in this folder as:
+SAML Python Toolkit expects that certs for the SP could be stored in this folder as:
* sp.key Private Key
* sp.crt Public cert
diff --git a/docs/saml2/_modules/index.html b/docs/saml2/_modules/index.html
index d672e465..12fa0756 100644
--- a/docs/saml2/_modules/index.html
+++ b/docs/saml2/_modules/index.html
@@ -7,12 +7,12 @@
-
- Overview: module code — OneLogin SAML Python library classes and methods
-
+
+ Overview: module code — SAML Python library classes and methods
+
-
+
-
+
+
-
+
All modules for which code is available
- saml2.auth
- saml2.authn_request
@@ -90,12 +90,11 @@ Navigation
-
modules |
- - OneLogin SAML Python library classes and methods »
+ - SAML Python library classes and methods »
-
\ No newline at end of file
+
diff --git a/docs/saml2/_modules/saml2/auth.html b/docs/saml2/_modules/saml2/auth.html
index f70e70b2..494ef85c 100644
--- a/docs/saml2/_modules/saml2/auth.html
+++ b/docs/saml2/_modules/saml2/auth.html
@@ -7,12 +7,12 @@
-
- saml2.auth — OneLogin SAML Python library classes and methods
-
+
+ saml2.auth — SAML Python library classes and methods
+
-
+
-
-
+
+
+
-
+
Source code for saml2.auth
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
from base64 import b64encode
from urllib import urlencode, quote
@@ -363,7 +361,7 @@ Source code for saml2.auth
dsig_ctx = xmlsec.DSigCtx()
dsig_ctx.signKey = xmlsec.Key.load(file_key.name, xmlsec.KeyDataFormatPem, None)
file_key.close()
-
+
data = {
'SAMLRequest': quote(saml_request),
'RelayState': quote(relay_state),
@@ -448,12 +446,11 @@ Navigation
modules |
- OneLogin SAML Python library classes and methods »
- Class code »
+ SAML Python library classes and methods »
+ Class code »
diff --git a/docs/saml2/_modules/saml2/authn_request.html b/docs/saml2/_modules/saml2/authn_request.html
index 207ee7a6..2bd22b2f 100644
--- a/docs/saml2/_modules/saml2/authn_request.html
+++ b/docs/saml2/_modules/saml2/authn_request.html
@@ -7,12 +7,12 @@
-
- saml2.authn_request — OneLogin SAML Python library classes and methods
-
+
+ saml2.authn_request — SAML Python library classes and methods
+
-
+
-
-
+
+
+
-
+
Source code for saml2.authn_request
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
from base64 import b64encode
from datetime import datetime
from zlib import compress
@@ -164,13 +161,12 @@ Navigation
modules |
- OneLogin SAML Python library classes and methods »
- Class code »
+ SAML Python library classes and methods »
+ Class code »
-
\ No newline at end of file
+
diff --git a/docs/saml2/_modules/saml2/constants.html b/docs/saml2/_modules/saml2/constants.html
index 306ec1b3..1b0af52f 100644
--- a/docs/saml2/_modules/saml2/constants.html
+++ b/docs/saml2/_modules/saml2/constants.html
@@ -7,12 +7,12 @@
-
- saml2.constants — OneLogin SAML Python library classes and methods
-
+
+ saml2.constants — SAML Python library classes and methods
+
-
+
-
-
+
+
+
-
+
Source code for saml2.constants
# -*- coding: utf-8 -*-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
[docs]class OneLogin_Saml2_Constants:
# Value added to the current time in time condition validations
@@ -154,13 +151,12 @@ Navigation
modules |
- OneLogin SAML Python library classes and methods »
- Class code »
+ SAML Python library classes and methods »
+ Class code »
-
\ No newline at end of file
+