Skip to content

Commit 1d03b43

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 571e634 commit 1d03b43

File tree

4 files changed

+209
-0
lines changed

4 files changed

+209
-0
lines changed

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

+42
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.List;
1010

1111
import com.onelogin.saml2.model.hsm.HSM;
12+
13+
import org.apache.commons.lang3.StringUtils;
1214
import org.slf4j.Logger;
1315
import org.slf4j.LoggerFactory;
1416
import org.w3c.dom.Document;
@@ -1005,6 +1007,44 @@ private boolean checkIdpx509certRequired () {
10051007

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

10091049
/**
10101050
* Checks the SP settings .
@@ -1026,6 +1066,8 @@ public List<String> checkSPSettings() {
10261066
errors.add(errorMsg);
10271067
LOGGER.error(errorMsg);
10281068
}
1069+
1070+
checkAttributeConsumingServices(errors);
10291071

10301072
if (this.getHsm() == null && (this.getAuthnRequestsSigned() || this.getLogoutRequestSigned()
10311073
|| 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
@@ -116,6 +116,18 @@ public void testCheckSPSettingsAllErrors() throws IOException, Error {
116116
assertThat(settingsErrors, hasItem("sp_cert_not_found_and_required"));
117117
assertThat(settingsErrors, hasItem("contact_not_enough_data"));
118118
assertThat(settingsErrors, hasItem("organization_not_enough_data"));
119+
120+
Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.sperrors_multi_attribute_consuming_services.properties").build();
121+
List<String> settings2Errors = settings2.checkSPSettings();
122+
assertFalse(settings2Errors.isEmpty());
123+
assertThat(settings2Errors, hasItem("sp_entityId_not_found"));
124+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_data"));
125+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_no_requested_attribute"));
126+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_multiple_defaults"));
127+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_requested_attribute_data"));
128+
assertThat(settings2Errors, hasItem("sp_cert_not_found_and_required"));
129+
assertThat(settings2Errors, hasItem("contact_not_enough_data"));
130+
assertThat(settings2Errors, hasItem("organization_not_enough_data"));
119131
}
120132

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

137158
/**
@@ -157,6 +178,22 @@ public void testCheckSettingsAllErrors() throws IOException, Error {
157178
assertThat(settingsErrors, hasItem("idp_sso_url_invalid"));
158179
assertThat(settingsErrors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
159180
assertThat(settingsErrors, hasItem("idp_cert_not_found_and_required"));
181+
182+
Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.allerrors_multi_attribute_consuming_services.properties").build();
183+
List<String> settings2Errors = settings2.checkSettings();
184+
assertFalse(settings2Errors.isEmpty());
185+
assertThat(settings2Errors, hasItem("sp_entityId_not_found"));
186+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_data"));
187+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_no_requested_attribute"));
188+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_multiple_defaults"));
189+
assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_requested_attribute_data"));
190+
assertThat(settings2Errors, hasItem("sp_cert_not_found_and_required"));
191+
assertThat(settings2Errors, hasItem("contact_not_enough_data"));
192+
assertThat(settings2Errors, hasItem("organization_not_enough_data"));
193+
assertThat(settings2Errors, hasItem("idp_entityId_not_found"));
194+
assertThat(settings2Errors, hasItem("idp_sso_url_invalid"));
195+
assertThat(settings2Errors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
196+
assertThat(settings2Errors, hasItem("idp_cert_not_found_and_required"));
160197
}
161198

162199
/**
@@ -285,6 +322,49 @@ public void testGetSPMetadataUnsigned() throws Exception {
285322
assertThat(metadataStr, containsString("<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>"));
286323
}
287324

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