Skip to content

Commit 0c1873d

Browse files
committed
Differentiate between metadata NameIDFormat and AuthnRequest NameIDPolicy Format
The `name_id_format` configuration option is used to define 1. the value of the `<NameIDFormat>` metadata element 2. and the value of the `<NameIDPolicy>` `Format` attribute in an `AuthnRequest` The configuration option to set what the value of `<NameIDFormat>` element is in the metadata should be different from the configuration option to specify what should be requested in an `AuthnRequest` through the `<NameIDPolicy Format="...">` attribute. Introduce a new option (`name_id_policy_format`), or use the same name but scoped in a specific section for metadata and AuthnRequest. On the side of this, pysaml2 defaults to _transient_ as the `<NameIDPolicy Format="...">` attribute value. To omit requesting a value for the `<NameIDPolicy Format="">` attribute the value `"None"` (a string) must be set in the configuration. This is unintuitive. It is better to be explicit and set transient to request a transient NameID, than not setting a value and requesting transient by default. If no value is set, no specific `<NameIDPolicy Format="...">` should be requested. - Refactor the name_id_format usage - Add name_id_policy_format configuration option - Remove the "None" convention value Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 1d7d4f8 commit 0c1873d

File tree

7 files changed

+89
-46
lines changed

7 files changed

+89
-46
lines changed

docs/howto/config.rst

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,10 +536,26 @@ Example::
536536
}
537537

538538

539+
name_id_policy_format
540+
"""""""""""""""""""""
541+
542+
A string value that will be used to set the ``Format`` attribute of the
543+
``<NameIDPolicy>`` element of an ``<AuthnRequest>``.
544+
545+
Example::
546+
547+
"service": {
548+
"sp": {
549+
"name_id_policy_format": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
550+
}
551+
}
552+
553+
539554
name_id_format_allow_create
540555
"""""""""""""""""""""""""""
541556

542-
Enable AllowCreate in NameIDPolicy.
557+
A boolean value (``True`` or ``False``) that will be used to set the ``AllowCreate``
558+
attribute of the ``<NameIDPolicy>`` element of an ``<AuthnRequest>``.
543559

544560
Example::
545561

@@ -550,6 +566,24 @@ Example::
550566
}
551567

552568

569+
name_id_format
570+
""""""""""""""
571+
572+
A list of string values that will be used to set the ``<NameIDFormat>`` element of the
573+
metadata of an entity.
574+
575+
Example::
576+
577+
"service": {
578+
"idp": {
579+
"name_id_format": [
580+
"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
581+
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
582+
]
583+
}
584+
}
585+
586+
553587
allow_unsolicited
554588
"""""""""""""""""
555589

src/saml2/client_base.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def create_authn_request(self, destination, vorg="", scoping=None,
288288
:param vorg: The virtual organization the service belongs to.
289289
:param scoping: The scope of the request
290290
:param binding: The protocol to use for the Response !!
291-
:param nameid_format: Format of the NameID
291+
:param nameid_format: Format of the NameIDPolicy
292292
:param service_url_binding: Where the reply should be sent dependent
293293
on reply binding.
294294
:param message_id: The identifier for this request
@@ -351,43 +351,36 @@ def create_authn_request(self, destination, vorg="", scoping=None,
351351
raise ValueError("Wrong type for param {name}".format(name=param))
352352

353353
# NameIDPolicy
354-
nameid_format_config = self.config.getattr("name_id_format", "sp")
355-
nameid_format_config = (
356-
nameid_format_config[0]
357-
if isinstance(nameid_format_config, list)
358-
else nameid_format_config
359-
)
360-
nameid_format = (
354+
nameid_policy_format_config = self.config.getattr("name_id_policy_format", "sp")
355+
nameid_policy_format = (
361356
nameid_format
362-
if nameid_format is not None
363-
else NAMEID_FORMAT_TRANSIENT
364-
if nameid_format_config is None
365-
else None
366-
if nameid_format_config == 'None'
367-
else nameid_format_config
357+
or nameid_policy_format_config
358+
or None
368359
)
369360

370361
allow_create_config = self.config.getattr("name_id_format_allow_create", "sp")
371362
allow_create = (
372363
None
373364
# SAML 2.0 errata says AllowCreate MUST NOT be used for transient ids
374-
if nameid_format == NAMEID_FORMAT_TRANSIENT
365+
if nameid_policy_format == NAMEID_FORMAT_TRANSIENT
375366
else allow_create
376-
if allow_create is not None
367+
if allow_create
377368
else str(bool(allow_create_config)).lower()
378369
)
379370

380371
name_id_policy = (
381372
kwargs.pop("name_id_policy", None)
382373
if "name_id_policy" in kwargs
383374
else None
384-
if nameid_format == ""
385-
else samlp.NameIDPolicy(allow_create=allow_create, format=nameid_format)
375+
if not nameid_policy_format
376+
else samlp.NameIDPolicy(
377+
allow_create=allow_create, format=nameid_policy_format
378+
)
386379
)
387380

388381
if name_id_policy and vorg:
389382
name_id_policy.sp_name_qualifier = vorg
390-
name_id_policy.format = nameid_format or NAMEID_FORMAT_PERSISTENT
383+
name_id_policy.format = nameid_policy_format or NAMEID_FORMAT_PERSISTENT
391384

392385
args["name_id_policy"] = name_id_policy
393386

src/saml2/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"allow_unsolicited",
9090
"ecp",
9191
"name_id_format",
92+
"name_id_policy_format",
9293
"name_id_format_allow_create",
9394
"logout_requests_signed",
9495
"requested_attribute_name_format",
@@ -209,6 +210,7 @@ def __init__(self, homedir="."):
209210
self.contact_person = None
210211
self.name_form = None
211212
self.name_id_format = None
213+
self.name_id_policy_format = None
212214
self.name_id_format_allow_create = None
213215
self.virtual_organization = None
214216
self.only_use_keys_in_metadata = True

src/saml2/metadata.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,15 @@ def do_extensions(mname, item):
379379

380380

381381
def _do_nameid_format(cls, conf, typ):
382-
namef = conf.getattr("name_id_format", typ)
383-
if namef:
384-
if isinstance(namef, six.string_types):
385-
ids = [md.NameIDFormat(namef)]
386-
else:
387-
ids = [md.NameIDFormat(text=form) for form in namef]
388-
setattr(cls, "name_id_format", ids)
382+
name_id_format = conf.getattr("name_id_format", typ)
383+
if not name_id_format:
384+
return
385+
386+
if isinstance(name_id_format, six.string_types):
387+
name_id_format = [name_id_format]
388+
389+
formats = [md.NameIDFormat(text=format) for format in name_id_format]
390+
setattr(cls, "name_id_format", formats)
389391

390392

391393
def do_endpoints(conf, endpoints):

tests/sp_conf_nameidpolicy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"required_attributes": ["surName", "givenName", "mail"],
1515
"optional_attributes": ["title"],
1616
"idp": ["urn:mace:example.com:saml:roland:idp"],
17-
"name_id_format": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
17+
"name_id_policy_format": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
1818
"name_id_format_allow_create": "true"
1919
}
2020
},

tests/test_50_server.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,10 @@ def test_parse_faulty_request_to_err_status(self):
251251

252252
def test_parse_ok_request(self):
253253
req_id, authn_request = self.client.create_authn_request(
254-
message_id="id1", destination="http://localhost:8088/sso")
254+
message_id="id1",
255+
destination="http://localhost:8088/sso",
256+
nameid_format=saml.NAMEID_FORMAT_TRANSIENT,
257+
)
255258

256259
print(authn_request)
257260
binding = BINDING_HTTP_REDIRECT
@@ -1308,7 +1311,10 @@ def test_parse_faulty_request_to_err_status(self):
13081311

13091312
def test_parse_ok_request(self):
13101313
req_id, authn_request = self.client.create_authn_request(
1311-
message_id="id1", destination="http://localhost:8088/sso")
1314+
message_id="id1",
1315+
destination="http://localhost:8088/sso",
1316+
nameid_format=saml.NAMEID_FORMAT_TRANSIENT,
1317+
)
13121318

13131319
print(authn_request)
13141320
binding = BINDING_HTTP_REDIRECT

tests/test_51_client.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def test_create_attribute_query_3(self):
242242
req_id, req = self.client.create_attribute_query(
243243
"https://aai-demo-idp.switch.ch/idp/shibboleth",
244244
"_e7b68a04488f715cda642fbdd90099f5",
245-
format=saml.NAMEID_FORMAT_TRANSIENT,
245+
format=NAMEID_FORMAT_TRANSIENT,
246246
message_id="id1")
247247

248248
assert isinstance(req, samlp.AttributeQuery)
@@ -253,12 +253,15 @@ def test_create_attribute_query_3(self):
253253
assert req.issue_instant
254254
assert req.issuer.text == "urn:mace:example.com:saml:roland:sp"
255255
nameid = req.subject.name_id
256-
assert nameid.format == saml.NAMEID_FORMAT_TRANSIENT
256+
assert nameid.format == NAMEID_FORMAT_TRANSIENT
257257
assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5"
258258

259259
def test_create_auth_request_0(self):
260260
ar_str = "%s" % self.client.create_authn_request(
261-
"http://www.example.com/sso", message_id="id1")[1]
261+
"http://www.example.com/sso",
262+
message_id="id1",
263+
nameid_format=NAMEID_FORMAT_TRANSIENT,
264+
)[1]
262265

263266
ar = samlp.authn_request_from_string(ar_str)
264267
assert ar.assertion_consumer_service_url == ("http://lingon.catalogix"
@@ -270,7 +273,7 @@ def test_create_auth_request_0(self):
270273
assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp"
271274
nid_policy = ar.name_id_policy
272275
assert nid_policy.allow_create is None
273-
assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT
276+
assert nid_policy.format == NAMEID_FORMAT_TRANSIENT
274277

275278
node_requested_attributes = None
276279
for e in ar.extensions.extension_elements:
@@ -892,7 +895,7 @@ def test_sign_then_encrypt_assertion(self):
892895
subject=factory(saml.Subject, text="_aaa",
893896
name_id=factory(
894897
saml.NameID,
895-
format=saml.NAMEID_FORMAT_TRANSIENT)),
898+
format=NAMEID_FORMAT_TRANSIENT)),
896899
attribute_statement=do_attribute_statement(
897900
{
898901
("", "", "sn"): ("Jeter", ""),
@@ -976,7 +979,7 @@ def test_sign_then_encrypt_assertion2(self):
976979
self.client.config.entityid,
977980
self.server.config.attribute_converters,
978981
self.server.config.getattr("policy", "idp"),
979-
name_id=factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT),
982+
name_id=factory(saml.NameID, format=NAMEID_FORMAT_TRANSIENT),
980983
issuer=self.server._issuer(),
981984
authn_class=INTERNETPROTOCOLPASSWORD,
982985
authn_auth="http://www.example.com/login",
@@ -1037,7 +1040,7 @@ def test_sign_then_encrypt_assertion_advice_1(self):
10371040
'in_response_to': "_012345",
10381041
'subject_confirmation_method': saml.SCM_BEARER
10391042
}
1040-
name_id = factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT)
1043+
name_id = factory(saml.NameID, format=NAMEID_FORMAT_TRANSIENT)
10411044

10421045
farg = add_path(
10431046
{},
@@ -1149,7 +1152,7 @@ def test_sign_then_encrypt_assertion_advice_2(self):
11491152
farg['assertion']['subject']['subject_confirmation'],
11501153
['subject_confirmation_data', 'recipient',
11511154
"http://lingon.catalogix.se:8087/"])
1152-
name_id = factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT)
1155+
name_id = factory(saml.NameID, format=NAMEID_FORMAT_TRANSIENT)
11531156

11541157
assertion_1 = asser_1.construct(
11551158
self.client.config.entityid,
@@ -1796,7 +1799,7 @@ def test_create_attribute_query_3(self):
17961799
req_id, req = self.client.create_attribute_query(
17971800
"https://aai-demo-idp.switch.ch/idp/shibboleth",
17981801
"_e7b68a04488f715cda642fbdd90099f5",
1799-
format=saml.NAMEID_FORMAT_TRANSIENT,
1802+
format=NAMEID_FORMAT_TRANSIENT,
18001803
message_id="id1")
18011804

18021805
assert isinstance(req, samlp.AttributeQuery)
@@ -1807,12 +1810,15 @@ def test_create_attribute_query_3(self):
18071810
assert req.issue_instant
18081811
assert req.issuer.text == "urn:mace:example.com:saml:roland:sp"
18091812
nameid = req.subject.name_id
1810-
assert nameid.format == saml.NAMEID_FORMAT_TRANSIENT
1813+
assert nameid.format == NAMEID_FORMAT_TRANSIENT
18111814
assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5"
18121815

18131816
def test_create_auth_request_0(self):
18141817
ar_str = "%s" % self.client.create_authn_request(
1815-
"http://www.example.com/sso", message_id="id1")[1]
1818+
"http://www.example.com/sso",
1819+
message_id="id1",
1820+
nameid_format=NAMEID_FORMAT_TRANSIENT,
1821+
)[1]
18161822

18171823
ar = samlp.authn_request_from_string(ar_str)
18181824
assert ar.assertion_consumer_service_url == ("http://lingon.catalogix"
@@ -1824,7 +1830,7 @@ def test_create_auth_request_0(self):
18241830
assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp"
18251831
nid_policy = ar.name_id_policy
18261832
assert nid_policy.allow_create is None
1827-
assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT
1833+
assert nid_policy.format == NAMEID_FORMAT_TRANSIENT
18281834

18291835
node_requested_attributes = None
18301836
for e in ar.extensions.extension_elements:
@@ -2464,7 +2470,7 @@ def test_sign_then_encrypt_assertion(self):
24642470
subject=factory(saml.Subject, text="_aaa",
24652471
name_id=factory(
24662472
saml.NameID,
2467-
format=saml.NAMEID_FORMAT_TRANSIENT)),
2473+
format=NAMEID_FORMAT_TRANSIENT)),
24682474
attribute_statement=do_attribute_statement(
24692475
{
24702476
("", "", "sn"): ("Jeter", ""),
@@ -2548,7 +2554,7 @@ def test_sign_then_encrypt_assertion2(self):
25482554
self.client.config.entityid,
25492555
self.server.config.attribute_converters,
25502556
self.server.config.getattr("policy", "idp"),
2551-
name_id=factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT),
2557+
name_id=factory(saml.NameID, format=NAMEID_FORMAT_TRANSIENT),
25522558
issuer=self.server._issuer(),
25532559
authn_class=INTERNETPROTOCOLPASSWORD,
25542560
authn_auth="http://www.example.com/login",
@@ -2609,7 +2615,7 @@ def test_sign_then_encrypt_assertion_advice_1(self):
26092615
'in_response_to': "_012345",
26102616
'subject_confirmation_method': saml.SCM_BEARER
26112617
}
2612-
name_id = factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT)
2618+
name_id = factory(saml.NameID, format=NAMEID_FORMAT_TRANSIENT)
26132619

26142620
farg = add_path(
26152621
{},
@@ -2722,7 +2728,7 @@ def test_sign_then_encrypt_assertion_advice_2(self):
27222728
farg['assertion']['subject']['subject_confirmation'],
27232729
['subject_confirmation_data', 'recipient',
27242730
"http://lingon.catalogix.se:8087/"])
2725-
name_id = factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT)
2731+
name_id = factory(saml.NameID, format=NAMEID_FORMAT_TRANSIENT)
27262732

27272733
assertion_1 = asser_1.construct(
27282734
self.client.config.entityid,

0 commit comments

Comments
 (0)