Skip to content

Commit d77ff88

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 d77ff88

File tree

4 files changed

+207
-0
lines changed

4 files changed

+207
-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

+80
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@ 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_not_enough_data"));
131+
assertThat(settings2Errors, hasItem("organization_not_enough_data"));
120132
}
121133

122134
/**
@@ -133,6 +145,15 @@ public void testCheckSPSettingsOk() throws IOException, Error {
133145
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.all.properties").build();
134146
List<String> settingsErrors = settings.checkSPSettings();
135147
assertTrue(settingsErrors.isEmpty());
148+
149+
Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.all_multi_attribute_consuming_services.properties").build();
150+
List<String> settings2Errors = settings2.checkSPSettings();
151+
assertTrue(settings2Errors.isEmpty());
152+
153+
// no attribute consuming services at all
154+
Saml2Settings settings3 = new SettingsBuilder().fromFile("config/config.min.properties").build();
155+
List<String> settings3Errors = settings3.checkSPSettings();
156+
assertTrue(settings3Errors.isEmpty());
136157
}
137158

138159
/**
@@ -159,6 +180,22 @@ public void testCheckSettingsAllErrors() throws IOException, Error {
159180
assertThat(settingsErrors, hasItem("idp_sso_url_invalid"));
160181
assertThat(settingsErrors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
161182
assertThat(settingsErrors, hasItem("idp_cert_not_found_and_required"));
183+
184+
Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.allerrors_multi_attribute_consuming_services.properties").build();
185+
List<String> settings2Errors = settings2.checkSettings();
186+
assertFalse(settings2Errors.isEmpty());
187+
assertThat(settings2Errors, hasItem("sp_entityId_not_found"));
188+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_data"));
189+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_no_requested_attribute"));
190+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_multiple_defaults"));
191+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_requested_attribute_data"));
192+
assertThat(settings2Errors, hasItem("sp_cert_not_found_and_required"));
193+
assertThat(settings2Errors, hasItem("contact_not_enough_data"));
194+
assertThat(settings2Errors, hasItem("organization_not_enough_data"));
195+
assertThat(settings2Errors, hasItem("idp_entityId_not_found"));
196+
assertThat(settings2Errors, hasItem("idp_sso_url_invalid"));
197+
assertThat(settings2Errors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
198+
assertThat(settings2Errors, hasItem("idp_cert_not_found_and_required"));
162199
}
163200

164201
/**
@@ -287,6 +324,49 @@ public void testGetSPMetadataUnsigned() throws Exception {
287324
assertThat(metadataStr, containsString("<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>"));
288325
}
289326

327+
/**
328+
* Tests the getSPMetadata method of the Saml2Settings
329+
* <p>
330+
* Case Unsigned metadata with multiple Attribute Consuming Services
331+
*
332+
* @throws Exception
333+
*
334+
* @see com.onelogin.saml2.settings.Saml2Settings#getSPMetadata
335+
*/
336+
@Test
337+
public void testGetSPMetadataUnsignedMultiAttributeConsumingServices() throws Exception {
338+
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min_multi_attribute_consuming_services.properties").build();
339+
340+
String metadataStr = settings.getSPMetadata();
341+
342+
Document metadataDoc = Util.loadXML(metadataStr);
343+
assertTrue(metadataDoc instanceof Document);
344+
345+
assertEquals("md:EntityDescriptor", metadataDoc.getDocumentElement().getNodeName());
346+
assertEquals("md:SPSSODescriptor", metadataDoc.getDocumentElement().getFirstChild().getNodeName());
347+
348+
assertTrue(Util.validateXML(metadataDoc, SchemaFactory.SAML_SCHEMA_METADATA_2_0));
349+
350+
assertThat(metadataStr, containsString("<md:SPSSODescriptor"));
351+
assertThat(metadataStr, containsString("entityID=\"http://localhost:8080/java-saml-jspsample/metadata.jsp\""));
352+
assertThat(metadataStr, containsString("AuthnRequestsSigned=\"false\""));
353+
assertThat(metadataStr, containsString("WantAssertionsSigned=\"false\""));
354+
assertThat(metadataStr, not(containsString("<md:KeyDescriptor use=\"signing\">")));
355+
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\"/>"));
356+
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\"/>"));
357+
assertThat(metadataStr, containsString("<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>"));
358+
assertThat(metadataStr, containsString("<md:AttributeConsumingService index=\"0\">"));
359+
assertThat(metadataStr, containsString("<md:ServiceName xml:lang=\"en\">Just e-mail</md:ServiceName>"));
360+
assertThat(metadataStr, containsString("<md:RequestedAttribute Name=\"Email\" NameFormat=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\" FriendlyName=\"E-mail address\" isRequired=\"true\">"));
361+
assertThat(metadataStr, containsString("<saml:AttributeValue xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">[email protected]</saml:AttributeValue>"));
362+
assertThat(metadataStr, containsString("<saml:AttributeValue xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">[email protected]</saml:AttributeValue>"));
363+
assertThat(metadataStr, containsString("<md:AttributeConsumingService index=\"1\" isDefault=\"true\">"));
364+
assertThat(metadataStr, containsString("<md:ServiceName xml:lang=\"it\">Anagrafica</md:ServiceName>"));
365+
assertThat(metadataStr, containsString("<md:ServiceDescription xml:lang=\"it\">Servizio completo</md:ServiceDescription>"));
366+
assertThat(metadataStr, containsString("<md:RequestedAttribute Name=\"FirstName\" />"));
367+
assertThat(metadataStr, containsString("<md:RequestedAttribute Name=\"LastName\" isRequired=\"true\" />"));
368+
}
369+
290370
/**
291371
* Tests the getSPMetadata method of the Saml2Settings
292372
* * Case Unsigned metadata No SLS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.contacts.support.email_address = [email protected]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.contacts.support.given_name = Support Guy

0 commit comments

Comments
 (0)