What
Core 2.5.1 says there MUST be at most one <OneTimeUse> and at most one <ProxyRestriction> in a <Conditions>. The bundled XSD cannot enforce it (ConditionsType is choice maxOccurs="unbounded"), and the reader does not either: since #53 <saml:OneTimeUse/><saml:OneTimeUse/> is accepted like one, and duplicate ProxyRestriction has been accepted since #42.
Before #53 the duplicates could not get through, as a side effect of any OneTimeUse being refused.
What to do
The MUST NOT binds the issuer, so the spec does not order a refusal, but a duplicate is a malformed assertion from any conformant IdP and refusing is fail-closed; Keycloak's broker checks exactly this. count_assertion_el(conditions, "OneTimeUse"), already used by the other readers, gives presence and count in one call. Route a count above one down the refusal path with a message naming the duplicate, for both elements, with a test each.
Raised by @jarvis9443 reviewing #53.
What
Core 2.5.1 says there MUST be at most one
<OneTimeUse>and at most one<ProxyRestriction>in a<Conditions>. The bundled XSD cannot enforce it (ConditionsTypeischoice maxOccurs="unbounded"), and the reader does not either: since #53<saml:OneTimeUse/><saml:OneTimeUse/>is accepted like one, and duplicateProxyRestrictionhas been accepted since #42.Before #53 the duplicates could not get through, as a side effect of any
OneTimeUsebeing refused.What to do
The MUST NOT binds the issuer, so the spec does not order a refusal, but a duplicate is a malformed assertion from any conformant IdP and refusing is fail-closed; Keycloak's broker checks exactly this.
count_assertion_el(conditions, "OneTimeUse"), already used by the other readers, gives presence and count in one call. Route a count above one down the refusal path with a message naming the duplicate, for both elements, with a test each.Raised by @jarvis9443 reviewing #53.