summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kanakarakis <ivan.kanak@gmail.com>2021-10-20 12:18:23 +0300
committerGitHub <noreply@github.com>2021-10-20 12:18:23 +0300
commit9f30f2ffd56b8e188e6f396ad090b3248c2a70a4 (patch)
tree8440a42fefb2dacc924224ebfb286516b7ca6388
parent5583f16c161f10f6ef39818657fec4d4eca5ff2e (diff)
parentd1a11dbfe6032c2bc8f1e96435b48c7faa6a203e (diff)
downloadpysaml2-9f30f2ffd56b8e188e6f396ad090b3248c2a70a4.tar.gz
Merge pull request #807 from pandafy/issues/806-requested-authn-context
Adds option to configure RequestedAuthnContext
-rw-r--r--docs/howto/config.rst30
-rw-r--r--src/saml2/client_base.py28
-rw-r--r--src/saml2/config.py1
-rw-r--r--tests/servera_conf.py15
-rw-r--r--tests/test_31_config.py41
-rw-r--r--tests/test_71_authn_request.py25
6 files changed, 116 insertions, 24 deletions
diff --git a/docs/howto/config.rst b/docs/howto/config.rst
index 9060ad2c..0cbfcbf1 100644
--- a/docs/howto/config.rst
+++ b/docs/howto/config.rst
@@ -342,7 +342,7 @@ ca_certs
This is the path to a file containing root CA certificates for SSL server certificate validation.
Example::
-
+
"ca_certs": full_path("cacerts.txt"),
@@ -1222,6 +1222,34 @@ Example::
"requested_attribute_name_format": NAME_FORMAT_BASIC
+requested_authn_context
+"""""""""""""""""""""""
+
+This configuration option defines the ``<RequestedAuthnContext>`` for an AuthnRequest by
+a client. The value is a dictionary with two fields
+
+- ``authn_context_class_ref`` a list of string values representing
+ ``<AuthnContextClassRef>`` elements.
+
+- ``comparison`` a string representing the Comparison xml-attribute value of the
+ ``<RequestedAuthnContext>`` element. Per the SAML core specificiation the value should
+ be one of "exact", "minimum", "maximum", or "better". The default is "exact".
+
+Example::
+
+ "service": {
+ "sp": {
+ "requested_authn_context": {
+ "authn_context_class_ref": [
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient",
+ ],
+ "comparison": "minimum",
+ }
+ }
+ }
+
+
idp/aa/sp
^^^^^^^^^
diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py
index 90a4fbd1..4546ef07 100644
--- a/src/saml2/client_base.py
+++ b/src/saml2/client_base.py
@@ -17,7 +17,9 @@ from saml2.mdstore import locations
from saml2.profile import paos, ecp
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.saml import NAMEID_FORMAT_TRANSIENT
-from saml2.samlp import AuthnQuery, RequestedAuthnContext
+from saml2.saml import AuthnContextClassRef
+from saml2.samlp import AuthnQuery
+from saml2.samlp import RequestedAuthnContext
from saml2.samlp import NameIDMappingRequest
from saml2.samlp import AttributeQuery
from saml2.samlp import AuthzDecisionQuery
@@ -358,18 +360,36 @@ class Base(Entity):
provider_name = self._my_name()
args["provider_name"] = provider_name
+ requested_authn_context = (
+ kwargs.pop("requested_authn_context", None)
+ or self.config.getattr("requested_authn_context", "sp")
+ or {}
+ )
+ requested_authn_context_accrs = requested_authn_context.get(
+ "authn_context_class_ref", []
+ )
+ requested_authn_context_comparison = requested_authn_context.get(
+ "comparison", "exact"
+ )
+ if requested_authn_context_accrs:
+ args["requested_authn_context"] = RequestedAuthnContext(
+ authn_context_class_ref=[
+ AuthnContextClassRef(accr)
+ for accr in requested_authn_context_accrs
+ ],
+ comparison=requested_authn_context_comparison,
+ )
+
# Allow argument values either as class instances or as dictionaries
# all of these have cardinality 0..1
_msg = AuthnRequest()
- for param in ["scoping", "requested_authn_context", "conditions", "subject"]:
+ for param in ["scoping", "conditions", "subject"]:
_item = kwargs.pop(param, None)
if not _item:
continue
if isinstance(_item, _msg.child_class(param)):
args[param] = _item
- elif isinstance(_item, dict):
- args[param] = RequestedAuthnContext(**_item)
else:
raise ValueError("Wrong type for param {name}".format(name=param))
diff --git a/src/saml2/config.py b/src/saml2/config.py
index 6687a60f..f441f337 100644
--- a/src/saml2/config.py
+++ b/src/saml2/config.py
@@ -104,6 +104,7 @@ SP_ARGS = [
"sp_type",
"sp_type_in_metadata",
"requested_attributes",
+ "requested_authn_context",
]
AA_IDP_ARGS = [
diff --git a/tests/servera_conf.py b/tests/servera_conf.py
index 3ab8741b..5b837b34 100644
--- a/tests/servera_conf.py
+++ b/tests/servera_conf.py
@@ -4,6 +4,8 @@ from saml2 import BINDING_PAOS
from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_ARTIFACT
+from saml2.authn_context import PASSWORDPROTECTEDTRANSPORT as AUTHN_PASSWORD_PROTECTED
+from saml2.authn_context import TIMESYNCTOKEN as AUTHN_TIME_SYNC_TOKEN
from saml2.saml import NAMEID_FORMAT_TRANSIENT
from saml2.saml import NAMEID_FORMAT_PERSISTENT
@@ -42,8 +44,17 @@ CONFIG = {
"required_attributes": ["surName", "givenName", "mail"],
"optional_attributes": ["title", "eduPersonAffiliation"],
"idp": ["urn:mace:example.com:saml:roland:idp"],
- "name_id_format": [NAMEID_FORMAT_TRANSIENT,
- NAMEID_FORMAT_PERSISTENT]
+ "name_id_format": [
+ NAMEID_FORMAT_TRANSIENT,
+ NAMEID_FORMAT_PERSISTENT,
+ ],
+ "requested_authn_context": {
+ "authn_context_class_ref": [
+ AUTHN_PASSWORD_PROTECTED,
+ AUTHN_TIME_SYNC_TOKEN,
+ ],
+ "comparison": "exact",
+ },
}
},
"debug": 1,
diff --git a/tests/test_31_config.py b/tests/test_31_config.py
index bb19d85c..d58b9a01 100644
--- a/tests/test_31_config.py
+++ b/tests/test_31_config.py
@@ -5,14 +5,19 @@ import sys
import logging
from saml2.mdstore import MetadataStore, name
-from saml2 import BINDING_HTTP_REDIRECT, BINDING_SOAP, BINDING_HTTP_POST
-from saml2.config import SPConfig, IdPConfig, Config
-
+from saml2 import BINDING_HTTP_REDIRECT
+from saml2 import BINDING_SOAP
+from saml2.config import Config
+from saml2.config import IdPConfig
+from saml2.config import SPConfig
+from saml2.authn_context import PASSWORDPROTECTEDTRANSPORT as AUTHN_PASSWORD_PROTECTED
+from saml2.authn_context import TIMESYNCTOKEN as AUTHN_TIME_SYNC_TOKEN
from saml2 import logger
from pathutils import dotname, full_path
from saml2.sigver import security_context, CryptoBackendXMLSecurity
+
sp1 = {
"entityid": "urn:mace:umu.se:saml:roland:sp",
"service": {
@@ -26,8 +31,15 @@ sp1 = {
"urn:mace:example.com:saml:roland:idp": {
'single_sign_on_service':
{'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect':
- 'http://localhost:8088/sso/'}},
- }
+ 'http://localhost:8088/sso/'}},
+ },
+ "requested_authn_context": {
+ "authn_context_class_ref": [
+ AUTHN_PASSWORD_PROTECTED,
+ AUTHN_TIME_SYNC_TOKEN,
+ ],
+ "comparison": "exact",
+ },
}
},
"key_file": full_path("test.key"),
@@ -211,12 +223,23 @@ def test_1():
assert len(c._sp_idp) == 1
assert list(c._sp_idp.keys()) == ["urn:mace:example.com:saml:roland:idp"]
- assert list(c._sp_idp.values()) == [{'single_sign_on_service':
- {
- 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect':
- 'http://localhost:8088/sso/'}}]
+ assert list(c._sp_idp.values()) == [
+ {
+ 'single_sign_on_service': {
+ 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': (
+ 'http://localhost:8088/sso/'
+ )
+ }
+ }
+ ]
assert c.only_use_keys_in_metadata
+ assert type(c.getattr("requested_authn_context")) is dict
+ assert c.getattr("requested_authn_context").get("authn_context_class_ref") == [
+ AUTHN_PASSWORD_PROTECTED,
+ AUTHN_TIME_SYNC_TOKEN,
+ ]
+ assert c.getattr("requested_authn_context").get("comparison") == "exact"
def test_2():
diff --git a/tests/test_71_authn_request.py b/tests/test_71_authn_request.py
index ee970923..6ee609e3 100644
--- a/tests/test_71_authn_request.py
+++ b/tests/test_71_authn_request.py
@@ -1,6 +1,7 @@
from contextlib import closing
from saml2.client import Saml2Client
from saml2.server import Server
+from saml2.saml import AuthnContextClassRef
def test_authn_request_with_acs_by_index():
@@ -15,22 +16,30 @@ def test_authn_request_with_acs_by_index():
# instead of AssertionConsumerServiceURL. The index with label ACS_INDEX
# exists in the SP metadata in servera.xml.
request_id, authn_request = sp.create_authn_request(
- sp.config.entityid,
- assertion_consumer_service_index=ACS_INDEX)
+ sp.config.entityid, assertion_consumer_service_index=ACS_INDEX
+ )
- # Make sure the authn_request contains AssertionConsumerServiceIndex.
- acs_index = getattr(authn_request,
- 'assertion_consumer_service_index', None)
+ assert authn_request.requested_authn_context.authn_context_class_ref == [
+ AuthnContextClassRef(accr)
+ for accr in sp.config.getattr("requested_authn_context").get("authn_context_class_ref")
+ ]
+ assert authn_request.requested_authn_context.comparison == (
+ sp.config.getattr("requested_authn_context").get("comparison")
+ )
+ # Make sure the authn_request contains AssertionConsumerServiceIndex.
+ acs_index = getattr(
+ authn_request, 'assertion_consumer_service_index', None
+ )
assert acs_index == ACS_INDEX
# Create IdP.
with closing(Server(config_file="idp_all_conf")) as idp:
-
# Ask the IdP to pick out the binding and destination from the
# authn_request.
- binding, destination = idp.pick_binding("assertion_consumer_service",
- request=authn_request)
+ binding, destination = idp.pick_binding(
+ "assertion_consumer_service", request=authn_request
+ )
# Make sure the IdP pick_binding method picks the correct location
# or destination based on the ACS index in the authn request.