Skip to content

Commit a17dfb8

Browse files
amergeyjzheaux
authored andcommitted
Add SP NameIDFormat Support
closes gh-9115
1 parent 7e55c84 commit a17dfb8

File tree

6 files changed

+82
-5
lines changed

6 files changed

+82
-5
lines changed

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolver.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
3232
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
3333
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
34+
import org.opensaml.saml.saml2.metadata.NameIDFormat;
3435
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
3536
import org.opensaml.saml.saml2.metadata.SingleLogoutService;
3637
import org.opensaml.saml.saml2.metadata.impl.EntityDescriptorMarshaller;
@@ -87,6 +88,9 @@ private SPSSODescriptor buildSpSsoDescriptor(RelyingPartyRegistration registrati
8788
.addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION));
8889
spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration));
8990
spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration));
91+
if (registration.getNameIdFormat() != null) {
92+
spSsoDescriptor.getNameIDFormats().add(buildNameIDFormat(registration));
93+
}
9094
return spSsoDescriptor;
9195
}
9296

@@ -133,6 +137,12 @@ private SingleLogoutService buildSingleLogoutService(RelyingPartyRegistration re
133137
return singleLogoutService;
134138
}
135139

140+
private NameIDFormat buildNameIDFormat(RelyingPartyRegistration registration) {
141+
NameIDFormat nameIdFormat = build(NameIDFormat.DEFAULT_ELEMENT_NAME);
142+
nameIdFormat.setFormat(registration.getNameIdFormat());
143+
return nameIdFormat;
144+
}
145+
136146
@SuppressWarnings("unchecked")
137147
private <T> T build(QName elementName) {
138148
XMLObjectBuilder<?> builder = XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(elementName);

saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java

+28-2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ public final class RelyingPartyRegistration {
8787

8888
private final Saml2MessageBinding singleLogoutServiceBinding;
8989

90+
private final String nameIdFormat;
91+
9092
private final ProviderDetails providerDetails;
9193

9294
private final List<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials;
@@ -98,7 +100,7 @@ public final class RelyingPartyRegistration {
98100
private RelyingPartyRegistration(String registrationId, String entityId, String assertionConsumerServiceLocation,
99101
Saml2MessageBinding assertionConsumerServiceBinding, String singleLogoutServiceLocation,
100102
String singleLogoutServiceResponseLocation, Saml2MessageBinding singleLogoutServiceBinding,
101-
ProviderDetails providerDetails,
103+
ProviderDetails providerDetails, String nameIdFormat,
102104
Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials,
103105
Collection<Saml2X509Credential> decryptionX509Credentials,
104106
Collection<Saml2X509Credential> signingX509Credentials) {
@@ -129,6 +131,7 @@ private RelyingPartyRegistration(String registrationId, String entityId, String
129131
this.singleLogoutServiceLocation = singleLogoutServiceLocation;
130132
this.singleLogoutServiceResponseLocation = singleLogoutServiceResponseLocation;
131133
this.singleLogoutServiceBinding = singleLogoutServiceBinding;
134+
this.nameIdFormat = nameIdFormat;
132135
this.providerDetails = providerDetails;
133136
this.credentials = Collections.unmodifiableList(new LinkedList<>(credentials));
134137
this.decryptionX509Credentials = Collections.unmodifiableList(new LinkedList<>(decryptionX509Credentials));
@@ -234,6 +237,15 @@ public String getSingleLogoutServiceResponseLocation() {
234237
return this.singleLogoutServiceResponseLocation;
235238
}
236239

240+
/**
241+
* Get the NameID format.
242+
* @return the NameID format
243+
* @since 5.7
244+
*/
245+
public String getNameIdFormat() {
246+
return this.nameIdFormat;
247+
}
248+
237249
/**
238250
* Get the {@link Collection} of decryption {@link Saml2X509Credential}s associated
239251
* with this relying party
@@ -424,6 +436,7 @@ public static Builder withRelyingPartyRegistration(RelyingPartyRegistration regi
424436
.singleLogoutServiceLocation(registration.getSingleLogoutServiceLocation())
425437
.singleLogoutServiceResponseLocation(registration.getSingleLogoutServiceResponseLocation())
426438
.singleLogoutServiceBinding(registration.getSingleLogoutServiceBinding())
439+
.nameIdFormat(registration.getNameIdFormat())
427440
.assertingPartyDetails((assertingParty) -> assertingParty
428441
.entityId(registration.getAssertingPartyDetails().getEntityId())
429442
.wantAuthnRequestsSigned(registration.getAssertingPartyDetails().getWantAuthnRequestsSigned())
@@ -1018,6 +1031,8 @@ public static final class Builder {
10181031

10191032
private Saml2MessageBinding singleLogoutServiceBinding = Saml2MessageBinding.POST;
10201033

1034+
private String nameIdFormat = null;
1035+
10211036
private ProviderDetails.Builder providerDetails = new ProviderDetails.Builder();
10221037

10231038
private Collection<org.springframework.security.saml2.credentials.Saml2X509Credential> credentials = new HashSet<>();
@@ -1173,6 +1188,17 @@ public Builder singleLogoutServiceResponseLocation(String singleLogoutServiceRes
11731188
return this;
11741189
}
11751190

1191+
/**
1192+
* Set the NameID format
1193+
* @param nameIdFormat
1194+
* @return the {@link Builder} for further configuration
1195+
* @since 5.7
1196+
*/
1197+
public Builder nameIdFormat(String nameIdFormat) {
1198+
this.nameIdFormat = nameIdFormat;
1199+
return this;
1200+
}
1201+
11761202
/**
11771203
* Apply this {@link Consumer} to further configure the Asserting Party details
11781204
* @param assertingPartyDetails The {@link Consumer} to apply
@@ -1321,7 +1347,7 @@ public RelyingPartyRegistration build() {
13211347
return new RelyingPartyRegistration(this.registrationId, this.entityId,
13221348
this.assertionConsumerServiceLocation, this.assertionConsumerServiceBinding,
13231349
this.singleLogoutServiceLocation, this.singleLogoutServiceResponseLocation,
1324-
this.singleLogoutServiceBinding, this.providerDetails.build(), this.credentials,
1350+
this.singleLogoutServiceBinding, this.providerDetails.build(), this.nameIdFormat, this.credentials,
13251351
this.decryptionX509Credentials, this.signingX509Credentials);
13261352
}
13271353

saml2/saml2-service-provider/src/opensaml4Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactory.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,8 +27,10 @@
2727
import org.opensaml.saml.common.xml.SAMLConstants;
2828
import org.opensaml.saml.saml2.core.AuthnRequest;
2929
import org.opensaml.saml.saml2.core.Issuer;
30+
import org.opensaml.saml.saml2.core.NameIDPolicy;
3031
import org.opensaml.saml.saml2.core.impl.AuthnRequestBuilder;
3132
import org.opensaml.saml.saml2.core.impl.IssuerBuilder;
33+
import org.opensaml.saml.saml2.core.impl.NameIDPolicyBuilder;
3234

3335
import org.springframework.core.convert.converter.Converter;
3436
import org.springframework.security.saml2.core.OpenSamlInitializationService;
@@ -56,6 +58,8 @@ public final class OpenSaml4AuthenticationRequestFactory implements Saml2Authent
5658

5759
private final IssuerBuilder issuerBuilder;
5860

61+
private final NameIDPolicyBuilder nameIdPolicyBuilder;
62+
5963
private Clock clock = Clock.systemUTC();
6064

6165
private Converter<Saml2AuthenticationRequestContext, AuthnRequest> authenticationRequestContextConverter;
@@ -69,6 +73,8 @@ public OpenSaml4AuthenticationRequestFactory() {
6973
this.authnRequestBuilder = (AuthnRequestBuilder) registry.getBuilderFactory()
7074
.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
7175
this.issuerBuilder = (IssuerBuilder) registry.getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
76+
this.nameIdPolicyBuilder = (NameIDPolicyBuilder) registry.getBuilderFactory()
77+
.getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME);
7278
}
7379

7480
/**
@@ -152,6 +158,9 @@ private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext contex
152158
auth.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
153159
}
154160
auth.setProtocolBinding(protocolBinding);
161+
if (auth.getNameIDPolicy() == null) {
162+
setNameIdPolicy(auth, context.getRelyingPartyRegistration());
163+
}
155164
Issuer iss = this.issuerBuilder.buildObject();
156165
iss.setValue(issuer);
157166
auth.setIssuer(iss);
@@ -160,6 +169,15 @@ private AuthnRequest createAuthnRequest(Saml2AuthenticationRequestContext contex
160169
return auth;
161170
}
162171

172+
private void setNameIdPolicy(AuthnRequest authnRequest, RelyingPartyRegistration registration) {
173+
if (!StringUtils.hasText(registration.getNameIdFormat())) {
174+
return;
175+
}
176+
NameIDPolicy nameIdPolicy = this.nameIdPolicyBuilder.buildObject();
177+
nameIdPolicy.setFormat(registration.getNameIdFormat());
178+
authnRequest.setNameIDPolicy(nameIdPolicy);
179+
}
180+
163181
/**
164182
* Set the strategy for building an {@link AuthnRequest} from a given context
165183
* @param authenticationRequestContextConverter the conversion strategy to use

saml2/saml2-service-provider/src/opensaml4Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml4AuthenticationRequestFactoryTests.java

+12
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,18 @@ public void createRedirectAuthenticationRequestWhenSHA1SignRequestThenSignatureI
242242
assertThat(result.getBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
243243
}
244244

245+
@Test
246+
public void createAuthenticationRequestWhenSetNameIDPolicyThenReturnsCorrectNameIDPolicy() {
247+
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().nameIdFormat("format").build();
248+
this.context = this.contextBuilder.relayState("Relay State Value").relyingPartyRegistration(registration)
249+
.build();
250+
AuthnRequest authn = getAuthNRequest(Saml2MessageBinding.POST);
251+
assertThat(authn.getNameIDPolicy()).isNotNull();
252+
assertThat(authn.getNameIDPolicy().getAllowCreate()).isFalse();
253+
assertThat(authn.getNameIDPolicy().getFormat()).isEqualTo("format");
254+
assertThat(authn.getNameIDPolicy().getSPNameQualifier()).isNull();
255+
}
256+
245257
private AuthnRequest authnRequest() {
246258
AuthnRequest authnRequest = TestOpenSamlObjects.authnRequest();
247259
authnRequest.setIssueInstant(Instant.now());

saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/metadata/OpenSamlMetadataResolverTests.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -61,4 +61,13 @@ public void resolveWhenRelyingPartyNoCredentialsThenMetadataMatches() {
6161
.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"");
6262
}
6363

64+
@Test
65+
public void resolveWhenRelyingPartyNameIDFormatThenMetadataMatches() {
66+
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full().nameIdFormat("format")
67+
.build();
68+
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
69+
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
70+
assertThat(metadata).contains("<md:NameIDFormat>format</md:NameIDFormat>");
71+
}
72+
6473
}

saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class RelyingPartyRegistrationTests {
2828
@Test
2929
public void withRelyingPartyRegistrationWorks() {
3030
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
31+
.nameIdFormat("format")
3132
.assertingPartyDetails((a) -> a.singleSignOnServiceBinding(Saml2MessageBinding.POST))
3233
.assertingPartyDetails((a) -> a.wantAuthnRequestsSigned(false))
3334
.assertingPartyDetails((a) -> a.signingAlgorithms((algs) -> algs.add("alg")))
@@ -74,6 +75,7 @@ private void compareRegistrations(RelyingPartyRegistration registration, Relying
7475
.isEqualTo(registration.getAssertingPartyDetails().getVerificationX509Credentials());
7576
assertThat(copy.getAssertingPartyDetails().getSigningAlgorithms())
7677
.isEqualTo(registration.getAssertingPartyDetails().getSigningAlgorithms());
78+
assertThat(copy.getNameIdFormat()).isEqualTo(registration.getNameIdFormat());
7779
}
7880

7981
@Test

0 commit comments

Comments
 (0)