Skip to content

Commit 4978a18

Browse files
committed
Add Attribute Consuming Services settings check
Now Saml2Settings.checkSPSettings() also checks that, if any Attribute Consuming Service are declared, their configuration is consistent.
1 parent 3bb0d9e commit 4978a18

File tree

4 files changed

+213
-0
lines changed

4 files changed

+213
-0
lines changed

core/src/main/java/com/onelogin/saml2/settings/Saml2Settings.java

+40
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,44 @@ private boolean checkIdpx509certRequired () {
10091009

10101010
return this.getIdpx509certMulti() != null && !this.getIdpx509certMulti().isEmpty();
10111011
}
1012+
1013+
/*
1014+
* Auxiliary method to check Attribute Consuming Services are properly
1015+
* configured.
1016+
*
1017+
* @param errors the list to add to when an error is encountered
1018+
*/
1019+
private void checkAttributeConsumingServices(List<String> errors) {
1020+
List<AttributeConsumingService> attributeConsumingServices = getSpAttributeConsumingServices();
1021+
if(!attributeConsumingServices.isEmpty()) {
1022+
String errorMsg;
1023+
// all Attribute Consuming Services must have a service name
1024+
if(attributeConsumingServices.stream().anyMatch(service -> StringUtils.isEmpty(service.getServiceName()))) {
1025+
errorMsg = "sp_attribute_consuming_service_not_enough_data";
1026+
errors.add(errorMsg);
1027+
LOGGER.error(errorMsg);
1028+
}
1029+
// all Attribute Consuming Services must have at least one requested attribute
1030+
if(attributeConsumingServices.stream().anyMatch(service -> service.getRequestedAttributes().isEmpty())) {
1031+
errorMsg = "sp_attribute_consuming_service_no_requested_attribute";
1032+
errors.add(errorMsg);
1033+
LOGGER.error(errorMsg);
1034+
}
1035+
// there must be at most one with default = true
1036+
if(attributeConsumingServices.stream().filter(service -> Boolean.TRUE.equals(service.isDefault())).count() > 1) {
1037+
errorMsg = "sp_attribute_consuming_service_multiple_defaults";
1038+
errors.add(errorMsg);
1039+
LOGGER.error(errorMsg);
1040+
}
1041+
// all requested attributes must have a name
1042+
if(attributeConsumingServices.stream().flatMap(service -> service.getRequestedAttributes().stream())
1043+
.anyMatch(attribute -> StringUtils.isEmpty(attribute.getName()))) {
1044+
errorMsg = "sp_attribute_consuming_service_not_enough_requested_attribute_data";
1045+
errors.add(errorMsg);
1046+
LOGGER.error(errorMsg);
1047+
}
1048+
}
1049+
}
10121050

10131051
/**
10141052
* Checks the SP settings .
@@ -1030,6 +1068,8 @@ public List<String> checkSPSettings() {
10301068
errors.add(errorMsg);
10311069
LOGGER.error(errorMsg);
10321070
}
1071+
1072+
checkAttributeConsumingServices(errors);
10331073

10341074
if (this.getHsm() == null && (this.getAuthnRequestsSigned() || this.getLogoutRequestSigned()
10351075
|| this.getLogoutResponseSigned() || this.getWantAssertionsEncrypted() || this.getWantNameIdEncrypted()) && !this.checkSPCerts()) {

core/src/test/java/com/onelogin/saml2/test/settings/Saml2SettingsTest.java

+82
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ public void testCheckSPSettingsAllErrors() throws IOException, Error {
117117
assertThat(settingsErrors, hasItem("contact_type_invalid"));
118118
assertThat(settingsErrors, hasItem("contact_not_enough_data"));
119119
assertThat(settingsErrors, hasItem("organization_not_enough_data"));
120+
121+
Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.sperrors_multi_attribute_consuming_services.properties").build();
122+
List<String> settings2Errors = settings2.checkSPSettings();
123+
assertFalse(settings2Errors.isEmpty());
124+
assertThat(settings2Errors, hasItem("sp_entityId_not_found"));
125+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_data"));
126+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_no_requested_attribute"));
127+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_multiple_defaults"));
128+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_requested_attribute_data"));
129+
assertThat(settings2Errors, hasItem("sp_cert_not_found_and_required"));
130+
assertThat(settings2Errors, hasItem("contact_type_invalid"));
131+
assertThat(settings2Errors, hasItem("contact_not_enough_data"));
132+
assertThat(settings2Errors, hasItem("organization_not_enough_data"));
120133
}
121134

122135
/**
@@ -133,6 +146,15 @@ public void testCheckSPSettingsOk() throws IOException, Error {
133146
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.all.properties").build();
134147
List<String> settingsErrors = settings.checkSPSettings();
135148
assertTrue(settingsErrors.isEmpty());
149+
150+
Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.all_multi_attribute_consuming_services.properties").build();
151+
List<String> settings2Errors = settings2.checkSPSettings();
152+
assertTrue(settings2Errors.isEmpty());
153+
154+
// no attribute consuming services at all
155+
Saml2Settings settings3 = new SettingsBuilder().fromFile("config/config.min.properties").build();
156+
List<String> settings3Errors = settings3.checkSPSettings();
157+
assertTrue(settings3Errors.isEmpty());
136158
}
137159

138160
/**
@@ -159,6 +181,23 @@ public void testCheckSettingsAllErrors() throws IOException, Error {
159181
assertThat(settingsErrors, hasItem("idp_sso_url_invalid"));
160182
assertThat(settingsErrors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
161183
assertThat(settingsErrors, hasItem("idp_cert_not_found_and_required"));
184+
185+
Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.allerrors_multi_attribute_consuming_services.properties").build();
186+
List<String> settings2Errors = settings2.checkSettings();
187+
assertFalse(settings2Errors.isEmpty());
188+
assertThat(settings2Errors, hasItem("sp_entityId_not_found"));
189+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_data"));
190+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_no_requested_attribute"));
191+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_multiple_defaults"));
192+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_requested_attribute_data"));
193+
assertThat(settings2Errors, hasItem("sp_cert_not_found_and_required"));
194+
assertThat(settings2Errors, hasItem("contact_type_invalid"));
195+
assertThat(settings2Errors, hasItem("contact_not_enough_data"));
196+
assertThat(settings2Errors, hasItem("organization_not_enough_data"));
197+
assertThat(settings2Errors, hasItem("idp_entityId_not_found"));
198+
assertThat(settings2Errors, hasItem("idp_sso_url_invalid"));
199+
assertThat(settings2Errors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
200+
assertThat(settings2Errors, hasItem("idp_cert_not_found_and_required"));
162201
}
163202

164203
/**
@@ -287,6 +326,49 @@ public void testGetSPMetadataUnsigned() throws Exception {
287326
assertThat(metadataStr, containsString("<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>"));
288327
}
289328

329+
/**
330+
* Tests the getSPMetadata method of the Saml2Settings
331+
* <p>
332+
* Case Unsigned metadata with multiple Attribute Consuming Services
333+
*
334+
* @throws Exception
335+
*
336+
* @see com.onelogin.saml2.settings.Saml2Settings#getSPMetadata
337+
*/
338+
@Test
339+
public void testGetSPMetadataUnsignedMultiAttributeConsumingServices() throws Exception {
340+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min_multi_attribute_consuming_services.properties").build();
341+
342+
String metadataStr = settings.getSPMetadata();
343+
344+
Document metadataDoc = Util.loadXML(metadataStr);
345+
assertTrue(metadataDoc instanceof Document);
346+
347+
assertEquals("md:EntityDescriptor", metadataDoc.getDocumentElement().getNodeName());
348+
assertEquals("md:SPSSODescriptor", metadataDoc.getDocumentElement().getFirstChild().getNodeName());
349+
350+
assertTrue(Util.validateXML(metadataDoc, SchemaFactory.SAML_SCHEMA_METADATA_2_0));
351+
352+
assertThat(metadataStr, containsString("<md:SPSSODescriptor"));
353+
assertThat(metadataStr, containsString("entityID=\"http://localhost:8080/java-saml-jspsample/metadata.jsp\""));
354+
assertThat(metadataStr, containsString("AuthnRequestsSigned=\"false\""));
355+
assertThat(metadataStr, containsString("WantAssertionsSigned=\"false\""));
356+
assertThat(metadataStr, not(containsString("<md:KeyDescriptor use=\"signing\">")));
357+
assertThat(metadataStr, containsString("<md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8080/java-saml-jspsample/acs.jsp\" index=\"1\"/>"));
358+
assertThat(metadataStr, containsString("<md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8080/java-saml-jspsample/sls.jsp\"/>"));
359+
assertThat(metadataStr, containsString("<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>"));
360+
assertThat(metadataStr, containsString("<md:AttributeConsumingService index=\"0\">"));
361+
assertThat(metadataStr, containsString("<md:ServiceName xml:lang=\"en\">Just e-mail</md:ServiceName>"));
362+
assertThat(metadataStr, containsString("<md:RequestedAttribute Name=\"Email\" NameFormat=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\" FriendlyName=\"E-mail address\" isRequired=\"true\">"));
363+
assertThat(metadataStr, containsString("<saml:AttributeValue xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">[email protected]</saml:AttributeValue>"));
364+
assertThat(metadataStr, containsString("<saml:AttributeValue xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">[email protected]</saml:AttributeValue>"));
365+
assertThat(metadataStr, containsString("<md:AttributeConsumingService index=\"1\" isDefault=\"true\">"));
366+
assertThat(metadataStr, containsString("<md:ServiceName xml:lang=\"it\">Anagrafica</md:ServiceName>"));
367+
assertThat(metadataStr, containsString("<md:ServiceDescription xml:lang=\"it\">Servizio completo</md:ServiceDescription>"));
368+
assertThat(metadataStr, containsString("<md:RequestedAttribute Name=\"FirstName\" />"));
369+
assertThat(metadataStr, containsString("<md:RequestedAttribute Name=\"LastName\" isRequired=\"true\" />"));
370+
}
371+
290372
/**
291373
* Tests the getSPMetadata method of the Saml2Settings
292374
* * Case Unsigned metadata No SLS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# we have some Attribute Consuming Services with missing information and multiple defaults
2+
onelogin.saml2.sp.attribute_consuming_service[0].name = Just e-mail
3+
onelogin.saml2.sp.attribute_consuming_service[0].default = true
4+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name =
5+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
6+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].friendly_name = E-mail address
7+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].required = true
8+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[0] = [email protected]
9+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[1] = [email protected]
10+
onelogin.saml2.sp.attribute_consuming_service[1].name =
11+
onelogin.saml2.sp.attribute_consuming_service[1].default = true
12+
onelogin.saml2.sp.attribute_consuming_service[1].attribute[0].name = FirstName
13+
onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].name = LastName
14+
onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].required = true
15+
onelogin.saml2.sp.attribute_consuming_service[2].name = No requested attributes
16+
17+
# Usually x509cert and privateKey of the SP are provided by files placed at
18+
# the certs folder. But we can also provide them with the following parameters
19+
onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----
20+
21+
# Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
22+
# <samlp:LogoutResponse> elements received by this SP to be signed.
23+
onelogin.saml2.security.want_messages_signed = true
24+
25+
# Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
26+
# will be encrypted.
27+
onelogin.saml2.security.nameid_encrypted = true
28+
29+
# Indicates whether the <samlp:AuthnRequest> messages sent by this SP
30+
# will be signed. [The Metadata of the SP will offer this info]
31+
onelogin.saml2.security.authnrequest_signed = true
32+
33+
# Organization
34+
onelogin.saml2.organization.name = SP Java
35+
onelogin.saml2.organization.url = http://sp.example.com
36+
37+
# Contacts
38+
onelogin.saml2.sp.contact[0].contactType=administrative
39+
onelogin.saml2.sp.contact[1].contactType=nonexistent
40+
onelogin.saml2.sp.contact[1].company=ACME
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Identity Provider Data that we want connect with our SP
2+
# Identifier of the IdP entity (must be a URI)
3+
onelogin.saml2.idp.entityid = http://idp.example.com/
4+
5+
# SSO endpoint info of the IdP. (Authentication Request protocol)
6+
# URL Target of the IdP where the SP will send the Authentication Request Message
7+
onelogin.saml2.idp.single_sign_on_service.url = http://idp.example.com/simplesaml/saml2/idp/SSOService.php
8+
9+
# SLO endpoint info of the IdP.
10+
# URL Location of the IdP where the SP will send the SLO Request
11+
onelogin.saml2.idp.single_logout_service.url = http://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php
12+
13+
# we have some Attribute Consuming Services with missing information and multiple defaults
14+
onelogin.saml2.sp.attribute_consuming_service[0].name = Just e-mail
15+
onelogin.saml2.sp.attribute_consuming_service[0].default = true
16+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name =
17+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
18+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].friendly_name = E-mail address
19+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].required = true
20+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[0] = [email protected]
21+
onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[1] = [email protected]
22+
onelogin.saml2.sp.attribute_consuming_service[1].name =
23+
onelogin.saml2.sp.attribute_consuming_service[1].default = true
24+
onelogin.saml2.sp.attribute_consuming_service[1].attribute[0].name = FirstName
25+
onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].name = LastName
26+
onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].required = true
27+
onelogin.saml2.sp.attribute_consuming_service[2].name = No requested attributes
28+
29+
# Public x509 certificate of the IdP
30+
onelogin.saml2.idp.x509cert = -----BEGIN CERTIFICATE-----\nMIIBrTCCAaGgAwIBAgIBATA
31+
32+
# Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
33+
# <samlp:LogoutResponse> elements received by this SP to be signed.
34+
onelogin.saml2.security.want_messages_signed = true
35+
36+
# Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
37+
# will be encrypted.
38+
onelogin.saml2.security.nameid_encrypted = true
39+
40+
# Indicates whether the <samlp:AuthnRequest> messages sent by this SP
41+
# will be signed. [The Metadata of the SP will offer this info]
42+
onelogin.saml2.security.authnrequest_signed = true
43+
44+
# Organization
45+
onelogin.saml2.organization.name = SP Java
46+
onelogin.saml2.organization.displayname = SP Java Example
47+
48+
# Contacts
49+
onelogin.saml2.sp.contact[0].contactType=administrative
50+
onelogin.saml2.sp.contact[1].contactType=nonexistent
51+
onelogin.saml2.sp.contact[1].company=ACME

0 commit comments

Comments
 (0)