diff --git a/README.md b/README.md
index df0c3bf0..043d5bc0 100644
--- a/README.md
+++ b/README.md
@@ -275,6 +275,9 @@ onelogin.saml2.contacts.technical.email_address = technical@example.com
onelogin.saml2.contacts.support.given_name = Support Guy
onelogin.saml2.contacts.support.email_address = support@example.com
+# Attribute Consuming Services
+# SEE BELOW
+
## Identity Provider Data that we want connect with our SP ##
# Identifier of the IdP entity (must be a URI)
@@ -510,8 +513,90 @@ The getSPMetadata will return the metadata signed or not based on the security p
Before the XML metadata is exposed, a check takes place to ensure that the info to be provided is valid.
-##### Attribute Consumer Service(ACS)
-This code handles the SAML response that the IdP forwards to the SP through the user's client.
+##### Attribute Consuming Services
+The SP may optionally specify one or more Attribute Consuming Services in its metadata. These can be configured in the settings.
+
+If just one Attribute Consuming Service is required:
+
+```properties
+# Attribute Consuming Service name when just one such service should be declared by the SP.
+# Comment out or set to empty if no Attribute Consuming Service should be declared, or if multiple ones should (see below).
+# The service name is mandatory.
+onelogin.saml2.sp.attribute_consuming_service.name = My service
+
+# Attribute Consuming Service description when just one such service should be declared by the SP.
+# Ignored if the previous property is commented or empty.
+# The service description is optional.
+onelogin.saml2.sp.attribute_consuming_service.description = My service description
+
+# Language used for Attribute Consuming Service name and description when just one such service should be declared by the SP.
+# Ignored if the name property is commented or empty.
+# The language is optional and defaults to "en" (English).
+onelogin.saml2.sp.attribute_consuming_service.lang = en
+
+# Requested attributes to be included in the Attribute Consuming Service when just one such service should be declared by the SP.
+# At least one requested attribute must be specified, otherwise schema validation will fail.
+# Attribute properties are indexed properties, starting from 0. The index is used only to enumerate and sort attributes, but it's required.
+# The following properties allow to define each requested attribute:
+# - name: mandatory
+# - name_format: optional; if omitted, defaults to urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified
+# - friendly_name: optional; if omitted, it won't appear in SP metadata
+# - required: optional; if omitted or empty, defaults to false
+# - value[x]: an attribute value; the [x] index is used only to enumerate and sort values, but it's required
+# Please note that only simple values are currently supported and treated internally as strings. Hence no structured values
+# and no ability to specify an xsi:type attribute.
+# Attribute values are optional and most often they are simply omitted.
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name = Email
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[0] = foo@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[1] = bar@example.org
+```
+
+If multiple Attribute Consuming Services are required, they can be specified in a similar way, but using indexes: these indexes
+are used to enumerate and identify attribute consuming services within the SP metadata and can be subsequently used in the auth
+process to specify which attribute set should be requested to the IdP. The "default" property can also be set to designate the
+default Attribute Consuming Service. Here is an example:
+
+```properties
+onelogin.saml2.sp.attribute_consuming_service[0].name = Just e-mail
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name = Email
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[0] = foo@example.org
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[1] = bar@example.org
+onelogin.saml2.sp.attribute_consuming_service[1].name = Anagrafica
+onelogin.saml2.sp.attribute_consuming_service[1].description = Set completo
+onelogin.saml2.sp.attribute_consuming_service[1].lang = it
+onelogin.saml2.sp.attribute_consuming_service[1].default = true
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[0].name = FirstName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].name = LastName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].required = true
+```
+
+Please note that if you specify (multiple) indexed Attribute Consuming Services, the non-indexed properties will be ignored.
+
+As said, to request a specific attribute set when initiating SSO, a selection mechanism is available:
+
+```java
+import static com.onelogin.saml2.authn.AttributeConsumingServiceSelector.*;
+Auth auth = new Auth(request, response);
+// select by index 1
+auth.login(new AuthnRequestParams(false, false, true, byIndex(1));
+// or select by service name
+auth.login(new AuthnRequestParams(false, false, true, byServiceName(auth.getSettings(), "Anagrafica"));
+// or see AttributeConsumingServiceSelector interface implementations for more options
+```
+
+If no selector is specified, `AttributeConsumingServiceSelector.useDefault()` will be used, which will simply omit any
+`AttributeConsumingServiceIndex` from the request, hence leaving the IdP choose the default attribute set agreed upon.
+
+
+##### Assertion Consumer Service (ACS)
+This code handles the SAML response that the IdP forwards to the SP through the user's client:
+
```java
Auth auth = new Auth(request, response);
auth.processResponse();
diff --git a/core/src/main/java/com/onelogin/saml2/authn/AttributeConsumingServiceSelector.java b/core/src/main/java/com/onelogin/saml2/authn/AttributeConsumingServiceSelector.java
new file mode 100644
index 00000000..1a478ed3
--- /dev/null
+++ b/core/src/main/java/com/onelogin/saml2/authn/AttributeConsumingServiceSelector.java
@@ -0,0 +1,75 @@
+package com.onelogin.saml2.authn;
+
+import java.util.List;
+
+import com.onelogin.saml2.model.AttributeConsumingService;
+import com.onelogin.saml2.settings.Saml2Settings;
+
+/**
+ * Interfaced used to select the Attribute Consuming Service to be specified in
+ * an authentication request. An instance of this interface can be passed as an
+ * input parameter in a {@link AuthnRequestParams} to be used when initiating a
+ * login operation.
+ *
+ * A set of predefined implementations are provided: they should cover the most
+ * common cases.
+ */
+@FunctionalInterface
+public interface AttributeConsumingServiceSelector {
+
+ /**
+ * @return a selector of the default Attribute Consuming Service
+ */
+ static AttributeConsumingServiceSelector useDefault() {
+ return () -> null;
+ }
+
+ /**
+ * @param attributeConsumingService
+ * the Attribute Consuming Service to select
+ * @return a selector the chooses the specified Attribute Consuming Service;
+ * indeed, its index is used
+ */
+ static AttributeConsumingServiceSelector use(final AttributeConsumingService attributeConsumingService) {
+ return byIndex(attributeConsumingService.getIndex());
+ }
+
+ /**
+ * @param index
+ * the index of the Attribute Consuming Service to select
+ * @return a selector that chooses the Attribute Consuming Service with the
+ * given index
+ */
+ static AttributeConsumingServiceSelector byIndex(final int index) {
+ return () -> index;
+ }
+
+ /**
+ * @param settings
+ * the SAML settings, containing the list of the available
+ * Attribute Consuming Services (see
+ * {@link Saml2Settings#getSpAttributeConsumingServices()})
+ * @param serviceName
+ * the name of the Attribute Consuming Service to select
+ * @return a selector that chooses the Attribute Consuming Service with the
+ * given name; please note that this selector will select the default
+ * service if no one is found with the given name
+ */
+ static AttributeConsumingServiceSelector byServiceName(final Saml2Settings settings, final String serviceName) {
+ return () -> {
+ final List services = settings.getSpAttributeConsumingServices();
+ if (services != null)
+ return services.stream().filter(service -> service.getServiceName().equals(serviceName))
+ .findFirst().map(AttributeConsumingService::getIndex).orElse(null);
+ else
+ return null;
+ };
+ }
+
+ /**
+ * Returns the index of the selected Attribute Consuming Service.
+ *
+ * @return the service index, or null
if the default one should be selected
+ */
+ Integer getAttributeConsumingServiceIndex();
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/onelogin/saml2/authn/AuthnRequest.java b/core/src/main/java/com/onelogin/saml2/authn/AuthnRequest.java
index b5f3811f..594e62ad 100644
--- a/core/src/main/java/com/onelogin/saml2/authn/AuthnRequest.java
+++ b/core/src/main/java/com/onelogin/saml2/authn/AuthnRequest.java
@@ -266,6 +266,12 @@ private StrSubstitutor generateSubstitutor(AuthnRequestParams params, Saml2Setti
}
valueMap.put("requestedAuthnContextStr", requestedAuthnContextStr);
+
+ String attributeConsumingServiceIndexStr = "";
+ final Integer acsIndex = params.getAttributeConsumingServiceSelector().getAttributeConsumingServiceIndex();
+ if (acsIndex != null)
+ attributeConsumingServiceIndexStr = " AttributeConsumingServiceIndex=\"" + acsIndex + "\"";
+ valueMap.put("attributeConsumingServiceIndexStr", attributeConsumingServiceIndexStr);
return new StrSubstitutor(valueMap);
}
@@ -275,7 +281,7 @@ private StrSubstitutor generateSubstitutor(AuthnRequestParams params, Saml2Setti
*/
private static StringBuilder getAuthnRequestTemplate() {
StringBuilder template = new StringBuilder();
- template.append("");
+ template.append("");
template.append("${spEntityid}");
template.append("${subjectStr}${nameIDPolicyStr}${requestedAuthnContextStr}");
return template;
diff --git a/core/src/main/java/com/onelogin/saml2/authn/AuthnRequestParams.java b/core/src/main/java/com/onelogin/saml2/authn/AuthnRequestParams.java
index 69954254..f361942c 100644
--- a/core/src/main/java/com/onelogin/saml2/authn/AuthnRequestParams.java
+++ b/core/src/main/java/com/onelogin/saml2/authn/AuthnRequestParams.java
@@ -27,8 +27,15 @@ public class AuthnRequestParams {
*/
private final String nameIdValueReq;
+ /*
+ * / Selector to use to specify the Attribute Consuming Service index
+ */
+ private AttributeConsumingServiceSelector attributeConsumingServiceSelector;
+
/**
- * Create a set of authentication request input parameters.
+ * Create a set of authentication request input parameters. The
+ * {@link AttributeConsumingServiceSelector#useDefault()} selector is used to
+ * select the Attribute Consuming Service.
*
* @param forceAuthn
* whether the ForceAuthn
attribute should be set to
@@ -71,6 +78,29 @@ public AuthnRequestParams(boolean forceAuthn, boolean isPassive, boolean setName
* whether the ForceAuthn
attribute should be set to
* true
* @param isPassive
+ * whether the isPassive
attribute should be set to
+ * true
+ * @param setNameIdPolicy
+ * whether a NameIDPolicy
should be set
+ * @param attributeConsumingServiceSelector
+ * the selector to use to specify the Attribute Consuming Service
+ * index; if null
,
+ * {@link AttributeConsumingServiceSelector#useDefault()} is used
+ */
+ public AuthnRequestParams(boolean forceAuthn, boolean isPassive, boolean setNameIdPolicy,
+ AttributeConsumingServiceSelector attributeConsumingServiceSelector) {
+ this(forceAuthn, isPassive, setNameIdPolicy, true, null, attributeConsumingServiceSelector);
+ }
+
+ /**
+ * Create a set of authentication request input parameters. The
+ * {@link AttributeConsumingServiceSelector#useDefault()} selector is used to
+ * select the Attribute Consuming Service.
+ *
+ * @param forceAuthn
+ * whether the ForceAuthn
attribute should be set to
+ * true
+ * @param isPassive
* whether the IsPassive
attribute should be set to
* true
* @param setNameIdPolicy
@@ -103,13 +133,44 @@ public AuthnRequestParams(boolean forceAuthn, boolean isPassive, boolean setName
*/
public AuthnRequestParams(boolean forceAuthn, boolean isPassive, boolean setNameIdPolicy, boolean allowCreate,
String nameIdValueReq) {
+ this(forceAuthn, isPassive, setNameIdPolicy, allowCreate, nameIdValueReq, null);
+ }
+
+ /**
+ * Create a set of authentication request input parameters.
+ *
+ * @param forceAuthn
+ * whether the ForceAuthn
attribute should be set to
+ * true
+ * @param isPassive
+ * whether the isPassive
attribute should be set to
+ * true
+ * @param setNameIdPolicy
+ * whether a NameIDPolicy
should be set
+ * @param allowCreate
+ * the value to set for the allowCreate
attribute of
+ * NameIDPolicy
element; null
means it's
+ * not set at all; only meaningful when
+ * setNameIdPolicy
is true
+ * @param nameIdValueReq
+ * the subject that should be authenticated
+ * @param attributeConsumingServiceSelector
+ * the selector to use to specify the Attribute Consuming Service
+ * index; if null
,
+ * {@link AttributeConsumingServiceSelector#useDefault()} is used
+ */
+ public AuthnRequestParams(boolean forceAuthn, boolean isPassive, boolean setNameIdPolicy, boolean allowCreate,
+ String nameIdValueReq, AttributeConsumingServiceSelector attributeConsumingServiceSelector) {
this.forceAuthn = forceAuthn;
this.isPassive = isPassive;
this.setNameIdPolicy = setNameIdPolicy;
this.allowCreate = allowCreate;
this.nameIdValueReq = nameIdValueReq;
+ this.attributeConsumingServiceSelector = attributeConsumingServiceSelector != null
+ ? attributeConsumingServiceSelector
+ : AttributeConsumingServiceSelector.useDefault();
}
-
+
/**
* Create a set of authentication request input parameters, by copying them from
* another set.
@@ -123,6 +184,7 @@ protected AuthnRequestParams(AuthnRequestParams source) {
this.setNameIdPolicy = source.isSetNameIdPolicy();
this.allowCreate = source.isAllowCreate();
this.nameIdValueReq = source.getNameIdValueReq();
+ this.attributeConsumingServiceSelector = source.getAttributeConsumingServiceSelector();
}
/**
@@ -163,4 +225,11 @@ public boolean isAllowCreate() {
public String getNameIdValueReq() {
return nameIdValueReq;
}
+
+ /**
+ * @return the selector to use to specify the Attribute Consuming Service index
+ */
+ public AttributeConsumingServiceSelector getAttributeConsumingServiceSelector() {
+ return attributeConsumingServiceSelector;
+ }
}
\ No newline at end of file
diff --git a/core/src/main/java/com/onelogin/saml2/model/AttributeConsumingService.java b/core/src/main/java/com/onelogin/saml2/model/AttributeConsumingService.java
index 2086686d..1ed53201 100644
--- a/core/src/main/java/com/onelogin/saml2/model/AttributeConsumingService.java
+++ b/core/src/main/java/com/onelogin/saml2/model/AttributeConsumingService.java
@@ -11,6 +11,14 @@
*/
public class AttributeConsumingService {
/**
+ * Service Index
+ */
+ private final int index;
+ /**
+ * Whether this service is the default one
+ */
+ private final Boolean isDefault;
+ /**
* Service Name
*/
private final String serviceName;
@@ -20,6 +28,11 @@ public class AttributeConsumingService {
*/
private final String serviceDescription;
+ /**
+ * Language used for service name and description
+ */
+ private final String lang;
+
/**
* Requested Attributes
*/
@@ -27,18 +40,62 @@ public class AttributeConsumingService {
/**
* Constructor
- *
+ *
+ * @param index
+ * int. Service index
+ * @param isDefault
+ * boolean. Whether it's the default attribute consuming service
* @param serviceName
* String. Service Name
* @param serviceDescription
* String. Service Description
+ * @param lang
+ * String. Language in which service name and description are
+ * written; defaults to en
if null
is specified
*/
- public AttributeConsumingService(String serviceName, String serviceDescription) {
+ public AttributeConsumingService(int index, Boolean isDefault, String serviceName, String serviceDescription, String lang) {
+ this.index = index;
+ this.isDefault = isDefault;
this.serviceName = serviceName != null? serviceName : "";
- this.serviceDescription = serviceDescription != null? serviceDescription : "";
+ this.serviceDescription = serviceDescription;
+ this.lang = lang != null? lang: "en";
this.requestedAttributes = new ArrayList();
}
+ /**
+ * Constructor. Service name and description are assumed to be in English.
+ *
+ * @param index
+ * int. Service index
+ * @param isDefault
+ * boolean. Whether it's the default attribute consuming service
+ * @param serviceName
+ * String. Service Name
+ * @param serviceDescription
+ * String. Service Description
+ */
+ public AttributeConsumingService(int index, Boolean isDefault, String serviceName, String serviceDescription) {
+ this(index, isDefault, serviceName, serviceDescription, null);
+ }
+
+ /**
+ * Constructor for a non-default attribute consuming service with index
+ * 1
and service name and descriptions in English.
+ *
+ * Mainly kept for backward compatibility, this constructor can be used when an
+ * only attribute consuming service is required. Please also note that, to
+ * maintain full backward compatibility, if the service description is
+ * null
this constructor will set is as an empty string.
+ *
+ * @param serviceName
+ * String. Service Name
+ * @param serviceDescription
+ * String. Service Description; if null
, an empty string will be set
+ */
+ public AttributeConsumingService(String serviceName, String serviceDescription) {
+ this(1, null, serviceName, serviceDescription != null? serviceDescription : "", null);
+ }
+
/**
* @param attr
* RequestedAttribute. The requested attribute to be included
@@ -47,6 +104,20 @@ public final void addRequestedAttribute(RequestedAttribute attr) {
this.requestedAttributes.add(attr);
}
+ /**
+ * @return int the service index
+ */
+ public final int getIndex() {
+ return index;
+ }
+
+ /**
+ * @return boolean whether this is the default attribute consuming service
+ */
+ public final Boolean isDefault() {
+ return isDefault;
+ }
+
/**
* @return string the service name
*/
@@ -61,6 +132,13 @@ public final String getServiceDescription() {
return serviceDescription;
}
+ /**
+ * @return string the language in which service name and description are written
+ */
+ public String getLang() {
+ return lang;
+ }
+
/**
* @return List the requested attributes
*/
diff --git a/core/src/main/java/com/onelogin/saml2/settings/Metadata.java b/core/src/main/java/com/onelogin/saml2/settings/Metadata.java
index 30a83184..d8f4405f 100644
--- a/core/src/main/java/com/onelogin/saml2/settings/Metadata.java
+++ b/core/src/main/java/com/onelogin/saml2/settings/Metadata.java
@@ -43,8 +43,11 @@ public class Metadata {
private static final int SECONDS_CACHED = 604800; // 1 week
/**
- * AttributeConsumingService
- */
+ * AttributeConsumingService
+ *
+ * @deprecated Attribute Consuming Services should be specified in settings
+ */
+ @Deprecated
private AttributeConsumingService attributeConsumingService = null;
/**
@@ -70,7 +73,12 @@ public class Metadata {
* @param cacheDuration Duration of the cache in seconds
* @param attributeConsumingService AttributeConsumingService of service provider
* @throws CertificateEncodingException
+ * @deprecated Attribute Consuming Services should be specified in settings; if
+ * a non-null
service is specified here, it will be
+ * used in place of the ones specified in settings to generate
+ * metadata
*/
+ @Deprecated
public Metadata(Saml2Settings settings, Calendar validUntilTime, Integer cacheDuration, AttributeConsumingService attributeConsumingService) throws CertificateEncodingException {
this.validUntilTime = validUntilTime;
this.attributeConsumingService = attributeConsumingService;
@@ -169,7 +177,12 @@ private StrSubstitutor generateSubstitutor(Saml2Settings settings) throws Certif
valueMap.put("spAssertionConsumerServiceUrl", Util.toXml(settings.getSpAssertionConsumerServiceUrl().toString()));
valueMap.put("sls", toSLSXml(settings.getSpSingleLogoutServiceUrl(), settings.getSpSingleLogoutServiceBinding()));
- valueMap.put("strAttributeConsumingService", getAttributeConsumingServiceXml());
+ // if an Attribute Consuming Service was specified at construction time, use it in place of the ones specified in settings
+ // this is for backward compatibility
+ valueMap.put("strAttributeConsumingService",
+ toAttributeConsumingServicesXml(attributeConsumingService != null
+ ? Arrays.asList(attributeConsumingService)
+ : settings.getSpAttributeConsumingServices()));
valueMap.put("strKeyDescriptor", toX509KeyDescriptorsXML(settings.getSPcert(), settings.getSPcertNew(), wantsEncrypted));
@@ -206,63 +219,86 @@ private static StringBuilder getMetadataTemplate() {
/**
* Generates the AttributeConsumingService section of the metadata's template
+ *
+ * @param attributeConsumingServices
+ * a list containing the Attribute Consuming Services to generate
+ * the metadata for
*
* @return the AttributeConsumingService section of the metadata's template
*/
- private String getAttributeConsumingServiceXml() {
+ private String toAttributeConsumingServicesXml(List attributeConsumingServices) {
+ final StringBuilder acssXml = new StringBuilder();
+ if (attributeConsumingServices != null)
+ attributeConsumingServices.stream().forEach(service -> acssXml.append(toAttributeConsumingServiceXml(service)));
+ return acssXml.toString();
+ }
+
+ /**
+ * Generates a single Attribute Consuming Service metadata fragment
+ *
+ * @param service
+ * the Attribute Consuming Service for which the XML fragment
+ * should be generated
+ * @return the generated XML fragment
+ */
+ private String toAttributeConsumingServiceXml(AttributeConsumingService service) {
+ int index = service.getIndex();
+ Boolean isDefault = service.isDefault();
+ String serviceName = service.getServiceName();
+ String serviceDescription = service.getServiceDescription();
+ String lang = service.getLang();
+ List requestedAttributes = service.getRequestedAttributes();
StringBuilder attributeConsumingServiceXML = new StringBuilder();
- if (attributeConsumingService != null) {
- String serviceName = attributeConsumingService.getServiceName();
- String serviceDescription = attributeConsumingService.getServiceDescription();
- List requestedAttributes = attributeConsumingService.getRequestedAttributes();
-
- attributeConsumingServiceXML.append("");
- if (serviceName != null && !serviceName.isEmpty()) {
- attributeConsumingServiceXML.append("" + Util.toXml(serviceName) + "");
- }
- if (serviceDescription != null && !serviceDescription.isEmpty()) {
- attributeConsumingServiceXML.append("" + Util.toXml(serviceDescription) + "");
- }
- if (requestedAttributes != null && !requestedAttributes.isEmpty()) {
- for (RequestedAttribute requestedAttribute : requestedAttributes) {
- String name = requestedAttribute.getName();
- String friendlyName = requestedAttribute.getFriendlyName();
- String nameFormat = requestedAttribute.getNameFormat();
- Boolean isRequired = requestedAttribute.isRequired();
- List attrValues = requestedAttribute.getAttributeValues();
-
- String contentStr = "");
+ if (serviceName != null && !serviceName.isEmpty()) {
+ attributeConsumingServiceXML.append("")
+ .append(Util.toXml(serviceName)).append("");
+ }
+ if (serviceDescription != null && !serviceDescription.isEmpty()) {
+ attributeConsumingServiceXML.append("")
+ .append(Util.toXml(serviceDescription)).append("");
+ }
+ if (requestedAttributes != null && !requestedAttributes.isEmpty()) {
+ for (RequestedAttribute requestedAttribute : requestedAttributes) {
+ String name = requestedAttribute.getName();
+ String friendlyName = requestedAttribute.getFriendlyName();
+ String nameFormat = requestedAttribute.getNameFormat();
+ Boolean isRequired = requestedAttribute.isRequired();
+ List attrValues = requestedAttribute.getAttributeValues();
+
+ StringBuilder contentStr = new StringBuilder("";
- for (String attrValue : attrValues) {
- contentStr += "" + Util.toXml(attrValue) + "";
- }
- attributeConsumingServiceXML.append(contentStr + "");
- } else {
- attributeConsumingServiceXML.append(contentStr + " />");
+ if (attrValues != null && !attrValues.isEmpty()) {
+ contentStr.append(">");
+ for (String attrValue : attrValues) {
+ contentStr.append("").append(Util.toXml(attrValue)).append("");
}
+ attributeConsumingServiceXML.append(contentStr).append("");
+ } else {
+ attributeConsumingServiceXML.append(contentStr).append(" />");
}
}
- attributeConsumingServiceXML.append("");
}
-
+ attributeConsumingServiceXML.append("");
return attributeConsumingServiceXML.toString();
}
diff --git a/core/src/main/java/com/onelogin/saml2/settings/Saml2Settings.java b/core/src/main/java/com/onelogin/saml2/settings/Saml2Settings.java
index 9ee29465..50210f63 100644
--- a/core/src/main/java/com/onelogin/saml2/settings/Saml2Settings.java
+++ b/core/src/main/java/com/onelogin/saml2/settings/Saml2Settings.java
@@ -17,6 +17,8 @@
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+
+import com.onelogin.saml2.model.AttributeConsumingService;
import com.onelogin.saml2.model.Contact;
import com.onelogin.saml2.model.Organization;
import com.onelogin.saml2.util.Constants;
@@ -49,6 +51,7 @@ public class Saml2Settings {
private X509Certificate spX509certNew = null;
private PrivateKey spPrivateKey = null;
private HSM hsm = null;
+ private List spAttributeConsumingServices = new ArrayList<>();
// IdP
private String idpEntityId = "";
@@ -125,6 +128,13 @@ public final String getSpAssertionConsumerServiceBinding() {
return spAssertionConsumerServiceBinding;
}
+ /**
+ * @return the SP Attribute Consuming Services
+ */
+ public final List getSpAttributeConsumingServices() {
+ return spAttributeConsumingServices;
+ }
+
/**
* @return the spSingleLogoutServiceUrl setting value
*/
@@ -905,6 +915,17 @@ public void setTrimAttributeValues(boolean trimAttributeValues) {
public boolean isTrimAttributeValues() {
return trimAttributeValues;
}
+
+ /**
+ * Set the Attribute Consuming Services to be declared in the Service Provider
+ * metadata
+ *
+ * @param spAttributeConsumingServices
+ * the Attribute Consuming Services to set
+ */
+ protected final void setSpAttributeConsumingServices(List spAttributeConsumingServices) {
+ this.spAttributeConsumingServices = spAttributeConsumingServices;
+ }
/**
* Set contacts info that will be listed on the Service Provider metadata
@@ -988,6 +1009,44 @@ private boolean checkIdpx509certRequired () {
return this.getIdpx509certMulti() != null && !this.getIdpx509certMulti().isEmpty();
}
+
+ /*
+ * Auxiliary method to check Attribute Consuming Services are properly
+ * configured.
+ *
+ * @param errors the list to add to when an error is encountered
+ */
+ private void checkAttributeConsumingServices(List errors) {
+ List attributeConsumingServices = getSpAttributeConsumingServices();
+ if(!attributeConsumingServices.isEmpty()) {
+ String errorMsg;
+ // all Attribute Consuming Services must have a service name
+ if(attributeConsumingServices.stream().anyMatch(service -> StringUtils.isEmpty(service.getServiceName()))) {
+ errorMsg = "sp_attribute_consuming_service_not_enough_data";
+ errors.add(errorMsg);
+ LOGGER.error(errorMsg);
+ }
+ // all Attribute Consuming Services must have at least one requested attribute
+ if(attributeConsumingServices.stream().anyMatch(service -> service.getRequestedAttributes().isEmpty())) {
+ errorMsg = "sp_attribute_consuming_service_no_requested_attribute";
+ errors.add(errorMsg);
+ LOGGER.error(errorMsg);
+ }
+ // there must be at most one with default = true
+ if(attributeConsumingServices.stream().filter(service -> Boolean.TRUE.equals(service.isDefault())).count() > 1) {
+ errorMsg = "sp_attribute_consuming_service_multiple_defaults";
+ errors.add(errorMsg);
+ LOGGER.error(errorMsg);
+ }
+ // all requested attributes must have a name
+ if(attributeConsumingServices.stream().flatMap(service -> service.getRequestedAttributes().stream())
+ .anyMatch(attribute -> StringUtils.isEmpty(attribute.getName()))) {
+ errorMsg = "sp_attribute_consuming_service_not_enough_requested_attribute_data";
+ errors.add(errorMsg);
+ LOGGER.error(errorMsg);
+ }
+ }
+ }
/**
* Checks the SP settings .
@@ -1009,6 +1068,8 @@ public List checkSPSettings() {
errors.add(errorMsg);
LOGGER.error(errorMsg);
}
+
+ checkAttributeConsumingServices(errors);
if (this.getHsm() == null && (this.getAuthnRequestsSigned() || this.getLogoutRequestSigned()
|| this.getLogoutResponseSigned() || this.getWantAssertionsEncrypted() || this.getWantNameIdEncrypted()) && !this.checkSPCerts()) {
diff --git a/core/src/main/java/com/onelogin/saml2/settings/SettingsBuilder.java b/core/src/main/java/com/onelogin/saml2/settings/SettingsBuilder.java
index 6232044e..3a6ff501 100644
--- a/core/src/main/java/com/onelogin/saml2/settings/SettingsBuilder.java
+++ b/core/src/main/java/com/onelogin/saml2/settings/SettingsBuilder.java
@@ -31,10 +31,13 @@
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
import com.onelogin.saml2.exception.Error;
+import com.onelogin.saml2.model.AttributeConsumingService;
import com.onelogin.saml2.model.Contact;
import com.onelogin.saml2.model.KeyStoreSettings;
import com.onelogin.saml2.model.Organization;
+import com.onelogin.saml2.model.RequestedAttribute;
import com.onelogin.saml2.util.Constants;
import com.onelogin.saml2.util.Util;
@@ -82,6 +85,19 @@ public class SettingsBuilder {
public final static String SP_CONTACT_EMAIL_ADDRESS_PROPERTY_KEY_PREFIX = "email_address";
public final static String SP_CONTACT_TELEPHONE_NUMBER_PROPERTY_KEY_PREFIX = "telephone_number";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX = "onelogin.saml2.sp.attribute_consuming_service";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_NAME_PROPERTY_KEY_SUFFIX = "name";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_DESCRIPTION_PROPERTY_KEY_SUFFIX = "description";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_LANG_PROPERTY_KEY_SUFFIX = "lang";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_DEFAULT_PROPERTY_KEY_SUFFIX = "default";
+
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX = "attribute";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX = "name";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_FORMAT_PROPERTY_KEY_SUFFIX = "name_format";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_FRIENDLY_NAME_PROPERTY_KEY_SUFFIX = "friendly_name";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_REQUIRED_PROPERTY_KEY_SUFFIX = "required";
+ public final static String SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX = "value";
+
// KeyStore
public final static String KEYSTORE_KEY = "onelogin.saml2.keystore.store";
public final static String KEYSTORE_ALIAS = "onelogin.saml2.keystore.alias";
@@ -518,6 +534,81 @@ private List loadContacts() {
return contacts;
}
+
+ /**
+ * Loads the Attribute Consuming Services from settings.
+ *
+ * @return a list containing the loaded Attribute Consuming Services
+ */
+ private List loadAttributeConsumingServices() {
+ // first split properties into a map of properties
+ // key = service index; value = service properties
+ final SortedMap> acsProps =
+ extractIndexedProperties(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX, samlData);
+ // then build each Attribute Consuming Service
+ if(acsProps.containsKey(-1) && acsProps.size() == 1)
+ // single service specified; use index 1 for backward compatibility
+ return Arrays.asList(loadAttributeConsumingService(acsProps.get(-1), 1));
+ else
+ // multiple indexed services specified
+ return acsProps.entrySet().stream()
+ // ignore non-indexed service
+ .filter(entry -> {
+ final boolean indexed = entry.getKey() != -1;
+ if(!indexed) {
+ LOGGER.warn("non indexed Attribute Consuming Service found along with other indexed Services; the non-indexed one will be ignored");
+ }
+ return indexed;
+ })
+ .map(entry -> loadAttributeConsumingService(entry.getValue(), entry.getKey()))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Loads a single Attribute Consuming Service from settings.
+ *
+ * @param acsProps
+ * a map containing the Attribute Consuming Service settings
+ * @param index
+ * the index to be set on the returned Attribute Consuming Service
+ * @return the loaded Attribute Consuming Service
+ */
+ private AttributeConsumingService loadAttributeConsumingService(Map acsProps, int index) {
+ final String serviceName = loadStringProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_NAME_PROPERTY_KEY_SUFFIX, acsProps);
+ final String serviceDescription = loadStringProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_DESCRIPTION_PROPERTY_KEY_SUFFIX, acsProps);
+ final String lang = loadStringProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_LANG_PROPERTY_KEY_SUFFIX, acsProps);
+ final Boolean isDefault = loadBooleanProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_DEFAULT_PROPERTY_KEY_SUFFIX, acsProps);
+ final AttributeConsumingService acs = new AttributeConsumingService(index, isDefault, serviceName, serviceDescription, lang);
+ // split properties into a map of properties
+ // key = attribute index; value = attribute properties
+ final SortedMap> attributeProps = extractIndexedProperties(SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX, acsProps);
+ // build attributes
+ attributeProps.forEach((attributeIndex, attributeData) -> {
+ acs.addRequestedAttribute(loadRequestedAttribute(attributeData));
+ });
+ return acs;
+ }
+
+ /**
+ * Loads a requested attribute from settings.
+ *
+ * @param attributeProps
+ * a map containing the attribute settings
+ * @return the loaded attribute
+ */
+ private RequestedAttribute loadRequestedAttribute(Map attributeProps) {
+ final String name = loadStringProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, attributeProps);
+ final String nameFormat = loadStringProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_FORMAT_PROPERTY_KEY_SUFFIX, attributeProps);
+ final String friendlyName = loadStringProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_FRIENDLY_NAME_PROPERTY_KEY_SUFFIX, attributeProps);
+ final Boolean required = loadBooleanProperty(SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_REQUIRED_PROPERTY_KEY_SUFFIX, attributeProps);
+ // split properties into a map of properties
+ // key = value index; value = the actual value
+ final SortedMap values = extractIndexedValues(SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX, attributeProps);
+ final List stringValues = values.values().stream()
+ .map(value -> isString(value) ? StringUtils.trimToNull((String) value) : null)
+ .filter(Objects::nonNull).collect(Collectors.toList());
+ return new RequestedAttribute(name, friendlyName, required, nameFormat, stringValues);
+ }
/**
* Loads a single contact from settings.
@@ -732,6 +823,8 @@ private void loadSpSetting() {
if (spNameIDFormat != null && !spNameIDFormat.isEmpty()) {
saml2Setting.setSpNameIDFormat(spNameIDFormat);
}
+
+ saml2Setting.setSpAttributeConsumingServices(loadAttributeConsumingServices());
boolean keyStoreEnabled = this.samlData.get(KEYSTORE_KEY) != null && this.samlData.get(KEYSTORE_ALIAS) != null
&& this.samlData.get(KEYSTORE_KEY_PASSWORD) != null;
@@ -799,7 +892,19 @@ private String loadStringProperty(String propertyKey, Map data)
* @return the value
*/
private Boolean loadBooleanProperty(String propertyKey) {
- Object propValue = samlData.get(propertyKey);
+ return loadBooleanProperty(propertyKey, samlData);
+ }
+
+ /**
+ * Loads a property of the type Boolean from the specified data
+ *
+ * @param propertyKey the property name
+ * @param data the input data
+ *
+ * @return the value
+ */
+ private Boolean loadBooleanProperty(String propertyKey, Map data) {
+ Object propValue = data.get(propertyKey);
if (isString(propValue)) {
return Boolean.parseBoolean(((String) propValue).trim());
}
diff --git a/core/src/test/java/com/onelogin/saml2/test/authn/AuthnRequestTest.java b/core/src/test/java/com/onelogin/saml2/test/authn/AuthnRequestTest.java
index 20886fb8..81582563 100644
--- a/core/src/test/java/com/onelogin/saml2/test/authn/AuthnRequestTest.java
+++ b/core/src/test/java/com/onelogin/saml2/test/authn/AuthnRequestTest.java
@@ -16,8 +16,10 @@
import org.junit.Assert;
import org.junit.Test;
+import com.onelogin.saml2.authn.AttributeConsumingServiceSelector;
import com.onelogin.saml2.authn.AuthnRequest;
import com.onelogin.saml2.authn.AuthnRequestParams;
+import com.onelogin.saml2.model.AttributeConsumingService;
import com.onelogin.saml2.settings.Saml2Settings;
import com.onelogin.saml2.settings.SettingsBuilder;
import com.onelogin.saml2.util.Util;
@@ -482,6 +484,58 @@ public void testSubjectSpecialChars() throws Exception {
assertThat(authnRequestStr, containsString(""));
}
+ /**
+ * Tests the AuthnRequest Constructor
+ * The creation of a deflated SAML Request with the index of the desired Attribute Consuming Service
+ *
+ * @throws Exception
+ *
+ * @see com.onelogin.saml2.authn.AuthnRequest
+ */
+ @Test
+ public void testAttributeConsumingServiceSelector() throws Exception {
+ Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min_multi_attribute_consuming_services.properties").build();
+
+ AuthnRequest authnRequest = new AuthnRequest(settings);
+ String authnRequestStringBase64 = authnRequest.getEncodedAuthnRequest();
+ String authnRequestStr = Util.base64decodedInflated(authnRequestStringBase64);
+ assertThat(authnRequestStr, containsString("";
+ String sNameStr = "Test Service";
+ String sDescStr = "Test Service Desc";
+ String reqAttr1Str = "";
+ String reqAttr2Str = "";
+ String reqAttr3Str = "";
+ String footerStr = "";
+
+ assertThat(metadataStr, containsString(headerStr));
+ assertThat(metadataStr, containsString(sNameStr));
+ assertThat(metadataStr, containsString(sDescStr));
+ assertThat(metadataStr, containsString(reqAttr1Str));
+ assertThat(metadataStr, containsString(reqAttr2Str));
+ assertThat(metadataStr, containsString(reqAttr3Str));
+ assertThat(metadataStr, containsString(footerStr));
+ }
+
+ /**
+ * Tests the toAttributeConsumingServicesXml method of Metadata when an Attribute Consuming Service
+ * is specified at construction time
+ *
+ * Case: Attribute Consuming Service definition contains special chars.
+ *
+ * @throws IOException
+ * @throws CertificateEncodingException
+ * @throws Error
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
+ */
+ @Test
+ public void testToAttributeConsumingServicesXmlLegacySpecialChars() throws IOException, CertificateEncodingException, Error {
+ Saml2Settings settings = getSettingFromAllProperties();
+
+ AttributeConsumingService attributeConsumingService = new AttributeConsumingService(0, true, "T&st Service", "T&st Service Desc");
+ RequestedAttribute requestedAttribute = new RequestedAttribute("Email", "Email \"address\"", true, "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", null);
+ RequestedAttribute requestedAttribute2 = new RequestedAttribute("FirstName&LastName", null, true, "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", null);
+
+ attributeConsumingService.addRequestedAttribute(requestedAttribute);
+ attributeConsumingService.addRequestedAttribute(requestedAttribute2);
+
+ Metadata metadataObj = new Metadata(settings, null, null, attributeConsumingService);
+ String metadataStr = metadataObj.getMetadataString();
+
+ String headerStr = "";
+ String sNameStr = "T&st Service";
+ String sDescStr = "T&st Service Desc";
+ String reqAttr1Str = "";
+ String reqAttr2Str = "";
+ String footerStr = "";
+
+ assertThat(metadataStr, containsString(headerStr));
+ assertThat(metadataStr, containsString(sNameStr));
+ assertThat(metadataStr, containsString(sDescStr));
+ assertThat(metadataStr, containsString(reqAttr1Str));
+ assertThat(metadataStr, containsString(reqAttr2Str));
+ assertThat(metadataStr, containsString(footerStr));
+ }
+
+ /**
+ * Tests the toAttributeConsumingServicesXml method of Metadata when an Attribute Consuming Service
+ * is specified at construction time
+ *
+ * Case: Single non-default AttributeConsumingService with no explicit index.
+ *
+ * @throws IOException
+ * @throws CertificateEncodingException
+ * @throws Error
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
+ */
+ @Test
+ public void testToAttributeConsumingServiceXmlNoIndexLegacy() throws IOException, CertificateEncodingException, Error {
Saml2Settings settings = getSettingFromAllProperties();
AttributeConsumingService attributeConsumingService = new AttributeConsumingService("Test Service", "Test Service Desc");
@@ -492,17 +577,18 @@ public void testGetAttributeConsumingServiceXml() throws IOException, Certificat
}
/**
- * Tests the getAttributeConsumingServiceXml method of Metadata
+ * Tests the toAttributeConsumingServicesXml method of Metadata when an Attribute Consuming Service
+ * is specified at construction time
*
- * Case: Attribute Consuming Service definition contains special chars.
+ * Case: Single non-default AttributeConsumingService with no explicit index and containing special chars.
*
* @throws IOException
* @throws CertificateEncodingException
* @throws Error
- * @see com.onelogin.saml2.settings.Metadata#getAttributeConsumingServiceXml
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
*/
@Test
- public void testGetAttributeConsumingServiceXmlSpecialChars() throws IOException, CertificateEncodingException, Error {
+ public void testToAttributeConsumingServiceXmlNoIndexLegacySpecialChars() throws IOException, CertificateEncodingException, Error {
Saml2Settings settings = getSettingFromAllProperties();
AttributeConsumingService attributeConsumingService = new AttributeConsumingService("T&st Service", "T&st Service Desc");
@@ -531,20 +617,21 @@ public void testGetAttributeConsumingServiceXmlSpecialChars() throws IOException
}
/**
- * Tests the getAttributeConsumingServiceXml method of Metadata
+ * Tests the toAttributeConsumingServicesXml method of Metadata when an Attribute Consuming Service
+ * is specified at construction time
*
* Case: AttributeConsumingService Multiple AttributeValue.
*
* @throws IOException
* @throws CertificateEncodingException
* @throws Error
- * @see com.onelogin.saml2.settings.Metadata#getAttributeConsumingServiceXml
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
*/
@Test
- public void testGetAttributeConsumingServiceXmlWithMultipleAttributeValue() throws IOException, CertificateEncodingException, Error {
+ public void testToAttributeConsumingServiceXmlWithMultipleAttributeValueLegacy() throws IOException, CertificateEncodingException, Error {
Saml2Settings settings = getSettingFromAllProperties();
- AttributeConsumingService attributeConsumingService = new AttributeConsumingService("Test Service", "Test Service Desc");
+ AttributeConsumingService attributeConsumingService = new AttributeConsumingService(0, true, "Test Service", "Test Service Desc", "en");
List attrValues = new ArrayList();
attrValues.add("userType");
attrValues.add("admin");
@@ -557,7 +644,7 @@ public void testGetAttributeConsumingServiceXmlWithMultipleAttributeValue() thro
Metadata metadataObj = new Metadata(settings, null, null, attributeConsumingService);
String metadataStr = metadataObj.getMetadataString();
- String headerStr = "";
+ String headerStr = "";
String sNameStr = "Test Service";
String sDescStr = "Test Service Desc";
String reqAttr1Str = "";
@@ -577,17 +664,18 @@ public void testGetAttributeConsumingServiceXmlWithMultipleAttributeValue() thro
}
/**
- * Tests the getAttributeConsumingServiceXml method of Metadata
+ * Tests the toAttributeConsumingServicesXml method of Metadata when an Attribute Consuming Service
+ * is specified at construction time
*
* Case: AttributeConsumingService Multiple AttributeValue with special chars.
*
* @throws IOException
* @throws CertificateEncodingException
* @throws Error
- * @see com.onelogin.saml2.settings.Metadata#getAttributeConsumingServiceXml
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
*/
@Test
- public void testGetAttributeConsumingServiceXmlWithMultipleAttributeValueSpecialChars() throws IOException, CertificateEncodingException, Error {
+ public void testToAttributeConsumingServiceXmlWithMultipleAttributeValueLegacySpecialChars() throws IOException, CertificateEncodingException, Error {
Saml2Settings settings = getSettingFromAllProperties();
AttributeConsumingService attributeConsumingService = new AttributeConsumingService("T&st Service", "T&st Service Desc");
@@ -622,6 +710,138 @@ public void testGetAttributeConsumingServiceXmlWithMultipleAttributeValueSpecial
assertThat(metadataStr, containsString(footerStr));
}
+ /**
+ * Tests the toAttributeConsumingServicesXml method of Metadata
+ *
+ * Case: single Attribute Consuming Service specified in settings.
+ *
+ * @throws IOException
+ * @throws CertificateEncodingException
+ * @throws Error
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
+ */
+ @Test
+ public void testToAttributeConsumingServiceXmlSingleService() throws IOException, CertificateEncodingException, Error {
+ Saml2Settings settings = getSettingFromAllProperties();
+
+ Metadata metadataObj = new Metadata(settings, null, null);
+ String metadataStr = metadataObj.getMetadataString();
+
+ String headerStr = "";
+ String sNameStr = "My service";
+ String sDescStr = "My service description";
+ String reqAttr1Str = "";
+ String reqAttr1Atr1Str = "foo@example.org";
+ String reqAttr1Attr2Str = "bar@example.org";
+ String reqAttr2Str = "";
+ String footerStr = "";
+
+ assertThat(metadataStr, containsString(headerStr));
+ assertThat(metadataStr, containsString(sNameStr));
+ assertThat(metadataStr, containsString(sDescStr));
+ assertThat(metadataStr, containsString(reqAttr1Str));
+ assertThat(metadataStr, containsString(reqAttr1Atr1Str));
+ assertThat(metadataStr, containsString(reqAttr1Attr2Str));
+ assertThat(metadataStr, containsString(reqAttr2Str));
+ assertThat(metadataStr, containsString(footerStr));
+ }
+
+ /**
+ * Tests the toAttributeConsumingServicesXml method of Metadata
+ *
+ * Case: single Attribute Consuming Service specified in settings.
+ *
+ * @throws IOException
+ * @throws CertificateEncodingException
+ * @throws Error
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
+ */
+ @Test
+ public void testToAttributeConsumingServiceXmlSingleACSSpecialChars() throws IOException, CertificateEncodingException, Error {
+ Saml2Settings settings = getSettingFromAllSpecialCharsProperties();
+
+ Metadata metadataObj = new Metadata(settings, null, null);
+ String metadataStr = metadataObj.getMetadataString();
+
+ String headerStr = "";
+ String sNameStr = "My s&rvice";
+ String sDescStr = "My s&rvice description";
+ String reqAttr1Str = "";
+ String reqAttr1Atr1Str = "foo&bar@example.org";
+ String reqAttr1Attr2Str = "bar@example.org";
+ String reqAttr2Str = "";
+ String footerStr = "";
+
+ assertThat(metadataStr, containsString(headerStr));
+ assertThat(metadataStr, containsString(sNameStr));
+ assertThat(metadataStr, containsString(sDescStr));
+ assertThat(metadataStr, containsString(reqAttr1Str));
+ assertThat(metadataStr, containsString(reqAttr1Atr1Str));
+ assertThat(metadataStr, containsString(reqAttr1Attr2Str));
+ assertThat(metadataStr, containsString(reqAttr2Str));
+ assertThat(metadataStr, containsString(footerStr));
+ }
+
+ /**
+ * Tests the toAttributeConsumingServicesXml method of Metadata
+ * Case: single Attribute Consuming Service specified in settings
+ *
+ * @throws IOException
+ * @throws CertificateEncodingException
+ * @throws Error
+ * @see com.onelogin.saml2.settings.Metadata#toAttributeConsumingServicesXml
+ */
+ @Test
+ public void testToAttributeConsumingServiceXmlMultiServices() throws IOException, CertificateEncodingException, Error {
+ Saml2Settings settings = getSettingFromAllPropertiesMultiAttributeConsumingServices();
+
+ Metadata metadataObj = new Metadata(settings, null, null);
+ String metadataStr = metadataObj.getMetadataString();
+
+ String header1Str = "";
+ String sName1Str = "Just e-mail";
+ String reqAttr11Str = "";
+ String reqAttr11Atr1Str = "foo@example.org";
+ String reqAttr11Attr2Str = "bar@example.org";
+ String footer1Str = "";
+
+ String header2Str = "";
+ String sName2Str = "Anagrafica "completa"";
+ String sDesc2Str = "Dati anagrafici "completi"";
+ String reqAttr21Str = "";
+ String reqAttr22Str = "";
+ String footer2Str = "";
+
+ assertThat(metadataStr, containsString(header1Str));
+ assertThat(metadataStr, containsString(sName1Str));
+ assertThat(metadataStr, containsString(reqAttr11Str));
+ assertThat(metadataStr, containsString(reqAttr11Atr1Str));
+ assertThat(metadataStr, containsString(reqAttr11Attr2Str));
+ assertThat(metadataStr, containsString(footer1Str));
+
+ assertThat(metadataStr, containsString(header2Str));
+ assertThat(metadataStr, containsString(sName2Str));
+ assertThat(metadataStr, containsString(sDesc2Str));
+ assertThat(metadataStr, containsString(reqAttr21Str));
+ assertThat(metadataStr, containsString(reqAttr22Str));
+ assertThat(metadataStr, containsString(footer2Str));
+
+ // properties for the single non-indexed ACS must NOT be present in this case
+ String sNameStr = "My service";
+ String sDescStr = "My service description";
+ String reqAttr1Str = "";
+ String reqAttr1Atr1Str = "foo_wrong@example.org";
+ String reqAttr1Attr2Str = "bar_wrong@example.org";
+ String reqAttr2Str = "";
+
+ assertThat(metadataStr, not(containsString(sNameStr)));
+ assertThat(metadataStr, not(containsString(sDescStr)));
+ assertThat(metadataStr, not(containsString(reqAttr1Str)));
+ assertThat(metadataStr, not(containsString(reqAttr1Atr1Str)));
+ assertThat(metadataStr, not(containsString(reqAttr1Attr2Str)));
+ assertThat(metadataStr, not(containsString(reqAttr2Str)));
+ }
+
/**
* Tests the signMetadata method of Metadata
* Case imported metadata
@@ -707,6 +927,10 @@ private Saml2Settings getSettingFromAllSpecialCharsProperties() throws Error, IO
return new SettingsBuilder().fromFile("config/config.all_specialchars.properties").build();
}
+ private Saml2Settings getSettingFromAllPropertiesMultiAttributeConsumingServices() throws Error, IOException {
+ return new SettingsBuilder().fromFile("config/config.all_multi_attribute_consuming_services.properties").build();
+ }
+
@Test
public void shouldIncludeValidUntilAndDuration() throws CertificateEncodingException, Error, IOException {
//given
diff --git a/core/src/test/java/com/onelogin/saml2/test/settings/Saml2SettingsTest.java b/core/src/test/java/com/onelogin/saml2/test/settings/Saml2SettingsTest.java
index 67bbf84b..c8f5ed11 100644
--- a/core/src/test/java/com/onelogin/saml2/test/settings/Saml2SettingsTest.java
+++ b/core/src/test/java/com/onelogin/saml2/test/settings/Saml2SettingsTest.java
@@ -117,6 +117,19 @@ public void testCheckSPSettingsAllErrors() throws IOException, Error {
assertThat(settingsErrors, hasItem("contact_type_invalid"));
assertThat(settingsErrors, hasItem("contact_not_enough_data"));
assertThat(settingsErrors, hasItem("organization_not_enough_data"));
+
+ Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.sperrors_multi_attribute_consuming_services.properties").build();
+ List settings2Errors = settings2.checkSPSettings();
+ assertFalse(settings2Errors.isEmpty());
+ assertThat(settings2Errors, hasItem("sp_entityId_not_found"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_data"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_no_requested_attribute"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_multiple_defaults"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_requested_attribute_data"));
+ assertThat(settings2Errors, hasItem("sp_cert_not_found_and_required"));
+ assertThat(settings2Errors, hasItem("contact_type_invalid"));
+ assertThat(settings2Errors, hasItem("contact_not_enough_data"));
+ assertThat(settings2Errors, hasItem("organization_not_enough_data"));
}
/**
@@ -133,6 +146,15 @@ public void testCheckSPSettingsOk() throws IOException, Error {
Saml2Settings settings = new SettingsBuilder().fromFile("config/config.all.properties").build();
List settingsErrors = settings.checkSPSettings();
assertTrue(settingsErrors.isEmpty());
+
+ Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.all_multi_attribute_consuming_services.properties").build();
+ List settings2Errors = settings2.checkSPSettings();
+ assertTrue(settings2Errors.isEmpty());
+
+ // no attribute consuming services at all
+ Saml2Settings settings3 = new SettingsBuilder().fromFile("config/config.min.properties").build();
+ List settings3Errors = settings3.checkSPSettings();
+ assertTrue(settings3Errors.isEmpty());
}
/**
@@ -159,6 +181,23 @@ public void testCheckSettingsAllErrors() throws IOException, Error {
assertThat(settingsErrors, hasItem("idp_sso_url_invalid"));
assertThat(settingsErrors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
assertThat(settingsErrors, hasItem("idp_cert_not_found_and_required"));
+
+ Saml2Settings settings2 = new SettingsBuilder().fromFile("config/config.allerrors_multi_attribute_consuming_services.properties").build();
+ List settings2Errors = settings2.checkSettings();
+ assertFalse(settings2Errors.isEmpty());
+ assertThat(settings2Errors, hasItem("sp_entityId_not_found"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_data"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_no_requested_attribute"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_multiple_defaults"));
+ assertThat(settings2Errors, hasItem("sp_attribute_consuming_service_not_enough_requested_attribute_data"));
+ assertThat(settings2Errors, hasItem("sp_cert_not_found_and_required"));
+ assertThat(settings2Errors, hasItem("contact_type_invalid"));
+ assertThat(settings2Errors, hasItem("contact_not_enough_data"));
+ assertThat(settings2Errors, hasItem("organization_not_enough_data"));
+ assertThat(settings2Errors, hasItem("idp_entityId_not_found"));
+ assertThat(settings2Errors, hasItem("idp_sso_url_invalid"));
+ assertThat(settings2Errors, hasItem("idp_cert_or_fingerprint_not_found_and_required"));
+ assertThat(settings2Errors, hasItem("idp_cert_not_found_and_required"));
}
/**
@@ -287,6 +326,49 @@ public void testGetSPMetadataUnsigned() throws Exception {
assertThat(metadataStr, containsString("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"));
}
+ /**
+ * Tests the getSPMetadata method of the Saml2Settings
+ *
+ * Case Unsigned metadata with multiple Attribute Consuming Services
+ *
+ * @throws Exception
+ *
+ * @see com.onelogin.saml2.settings.Saml2Settings#getSPMetadata
+ */
+ @Test
+ public void testGetSPMetadataUnsignedMultiAttributeConsumingServices() throws Exception {
+ Saml2Settings settings = new SettingsBuilder().fromFile("config/config.min_multi_attribute_consuming_services.properties").build();
+
+ String metadataStr = settings.getSPMetadata();
+
+ Document metadataDoc = Util.loadXML(metadataStr);
+ assertTrue(metadataDoc instanceof Document);
+
+ assertEquals("md:EntityDescriptor", metadataDoc.getDocumentElement().getNodeName());
+ assertEquals("md:SPSSODescriptor", metadataDoc.getDocumentElement().getFirstChild().getNodeName());
+
+ assertTrue(Util.validateXML(metadataDoc, SchemaFactory.SAML_SCHEMA_METADATA_2_0));
+
+ assertThat(metadataStr, containsString("")));
+ assertThat(metadataStr, containsString(""));
+ assertThat(metadataStr, containsString(""));
+ assertThat(metadataStr, containsString("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"));
+ assertThat(metadataStr, containsString(""));
+ assertThat(metadataStr, containsString("Just e-mail"));
+ assertThat(metadataStr, containsString(""));
+ assertThat(metadataStr, containsString("foo@example.org"));
+ assertThat(metadataStr, containsString("bar@example.org"));
+ assertThat(metadataStr, containsString(""));
+ assertThat(metadataStr, containsString("Anagrafica"));
+ assertThat(metadataStr, containsString("Servizio completo"));
+ assertThat(metadataStr, containsString(""));
+ assertThat(metadataStr, containsString(""));
+ }
+
/**
* Tests the getSPMetadata method of the Saml2Settings
* * Case Unsigned metadata No SLS
diff --git a/core/src/test/java/com/onelogin/saml2/test/settings/SettingBuilderTest.java b/core/src/test/java/com/onelogin/saml2/test/settings/SettingBuilderTest.java
index b9206d4f..057b5a31 100644
--- a/core/src/test/java/com/onelogin/saml2/test/settings/SettingBuilderTest.java
+++ b/core/src/test/java/com/onelogin/saml2/test/settings/SettingBuilderTest.java
@@ -33,9 +33,11 @@
import com.onelogin.saml2.exception.Error;
import com.onelogin.saml2.exception.SettingsException;
+import com.onelogin.saml2.model.AttributeConsumingService;
import com.onelogin.saml2.model.Contact;
import com.onelogin.saml2.model.KeyStoreSettings;
import com.onelogin.saml2.model.Organization;
+import com.onelogin.saml2.model.RequestedAttribute;
import com.onelogin.saml2.settings.Saml2Settings;
import com.onelogin.saml2.settings.SettingsBuilder;
import com.onelogin.saml2.util.Constants;
@@ -135,6 +137,7 @@ public void testLoadFromFileEmpty() throws IOException, CertificateException, UR
assertNull(setting.getSpSingleLogoutServiceUrl());
assertEquals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", setting.getSpSingleLogoutServiceBinding());
assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", setting.getSpNameIDFormat());
+ assertTrue(setting.getSpAttributeConsumingServices().isEmpty());
assertTrue(setting.getIdpEntityId().isEmpty());
assertNull(setting.getIdpSingleSignOnServiceUrl());
@@ -193,6 +196,7 @@ public void testLoadFromFileMinProp() throws IOException, CertificateException,
assertEquals("http://localhost:8080/java-saml-jspsample/sls.jsp", setting.getSpSingleLogoutServiceUrl().toString());
assertEquals(setting.getSpSingleLogoutServiceBinding(), "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
assertEquals(setting.getSpNameIDFormat(), "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
+ assertTrue(setting.getSpAttributeConsumingServices().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -254,6 +258,31 @@ public void testLoadFromFileAllProp() throws IOException, CertificateException,
assertEquals("http://localhost:8080/java-saml-jspsample/sls.jsp", setting.getSpSingleLogoutServiceUrl().toString());
assertEquals(setting.getSpSingleLogoutServiceBinding(), "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
assertEquals(setting.getSpNameIDFormat(), "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
+ final List attributeConsumingServices = setting.getSpAttributeConsumingServices();
+ assertEquals(1, attributeConsumingServices.size());
+ final AttributeConsumingService attributeConsumingService = attributeConsumingServices.get(0);
+ assertEquals(1, attributeConsumingService.getIndex());
+ assertNull(attributeConsumingService.isDefault());
+ assertEquals("My service", attributeConsumingService.getServiceName());
+ assertEquals("My service description", attributeConsumingService.getServiceDescription());
+ assertEquals("en", attributeConsumingService.getLang());
+ final List requestedAttributes = attributeConsumingService.getRequestedAttributes();
+ assertEquals(2, requestedAttributes.size());
+ final RequestedAttribute requestedAttribute1 = requestedAttributes.get(0);
+ assertEquals("Email", requestedAttribute1.getName());
+ assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", requestedAttribute1.getNameFormat());
+ assertEquals("E-mail address", requestedAttribute1.getFriendlyName());
+ assertTrue(requestedAttribute1.isRequired());
+ final List requestedAttribute1Values = requestedAttribute1.getAttributeValues();
+ assertEquals(2, requestedAttribute1Values.size());
+ assertEquals("foo@example.org", requestedAttribute1Values.get(0));
+ assertEquals("bar@example.org", requestedAttribute1Values.get(1));
+ final RequestedAttribute requestedAttribute2 = requestedAttributes.get(1);
+ assertEquals("FirstName", requestedAttribute2.getName());
+ assertNull(requestedAttribute2.getNameFormat());
+ assertNull(requestedAttribute2.getFriendlyName());
+ assertNull(requestedAttribute2.isRequired());
+ assertTrue(requestedAttribute2.getAttributeValues().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -336,6 +365,72 @@ public void testLoadFromFileAllProp() throws IOException, CertificateException,
assertEquals("EXAMPLE", setting.getUniqueIDPrefix());
}
+ /**
+ * Tests SettingsBuilder fromFile method
+ *
+ * Case: all settings config file with multiple Attribute Consuming Services
+ *
+ * @throws IOException
+ * @throws CertificateException
+ * @throws URISyntaxException
+ * @throws SettingsException
+ * @throws Error
+ *
+ * @see com.onelogin.saml2.settings.SettingsBuilder#fromFile
+ */
+ @Test
+ public void testLoadFromFileAllPropMultiAttributeConsumingServices() throws IOException, CertificateException, URISyntaxException, SettingsException, Error {
+ Saml2Settings setting = new SettingsBuilder().fromFile("config/config.all_multi_attribute_consuming_services.properties").build();
+
+ // let's test only the Attribute Consuming Service part - no need to test again all the rest
+ final List attributeConsumingServices = setting.getSpAttributeConsumingServices();
+ assertEquals(2, attributeConsumingServices.size());
+
+ {
+ final AttributeConsumingService attributeConsumingService1 = attributeConsumingServices.get(0);
+ assertEquals(0, attributeConsumingService1.getIndex());
+ assertNull(attributeConsumingService1.isDefault());
+ assertEquals("Just e-mail", attributeConsumingService1.getServiceName());
+ assertNull(attributeConsumingService1.getServiceDescription());
+ assertEquals("en", attributeConsumingService1.getLang());
+ final List requestedAttributes1 = attributeConsumingService1.getRequestedAttributes();
+ assertEquals(1, requestedAttributes1.size());
+ final RequestedAttribute requestedAttribute11 = requestedAttributes1.get(0);
+ assertEquals("Email", requestedAttribute11.getName());
+ assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", requestedAttribute11.getNameFormat());
+ assertEquals("E-mail address", requestedAttribute11.getFriendlyName());
+ assertTrue(requestedAttribute11.isRequired());
+ final List requestedAttribute11Values = requestedAttribute11.getAttributeValues();
+ assertEquals(2, requestedAttribute11Values.size());
+ assertEquals("foo@example.org", requestedAttribute11Values.get(0));
+ assertEquals("bar@example.org", requestedAttribute11Values.get(1));
+ }
+
+ {
+ final AttributeConsumingService attributeConsumingService2 = attributeConsumingServices.get(1);
+ assertEquals(1, attributeConsumingService2.getIndex());
+ assertTrue(attributeConsumingService2.isDefault());
+ assertEquals("Anagrafica \"completa\"", attributeConsumingService2.getServiceName());
+ assertEquals("Dati anagrafici \"completi\"", attributeConsumingService2.getServiceDescription());
+ assertEquals("it", attributeConsumingService2.getLang());
+ final List requestedAttributes2 = attributeConsumingService2.getRequestedAttributes();
+ assertEquals(2, requestedAttributes2.size());
+ final RequestedAttribute requestedAttribute21 = requestedAttributes2.get(0);
+ assertEquals("Birth date", requestedAttribute21.getName());
+ assertNull(requestedAttribute21.getNameFormat());
+ assertNull(requestedAttribute21.getFriendlyName());
+ assertNull(requestedAttribute21.isRequired());
+ final List requestedAttribute21Values = requestedAttribute21.getAttributeValues();
+ assertTrue(requestedAttribute21Values.isEmpty());
+ final RequestedAttribute requestedAttribute22 = requestedAttributes2.get(1);
+ assertEquals("First & Last Name", requestedAttribute22.getName());
+ assertNull(requestedAttribute22.getNameFormat());
+ assertNull(requestedAttribute22.getFriendlyName());
+ assertTrue(requestedAttribute22.isRequired());
+ assertTrue(requestedAttribute22.getAttributeValues().isEmpty());
+ }
+ }
+
/**
* Tests SettingsBuilder fromFile method
* Case: settings config file with certificate string
@@ -361,6 +456,7 @@ public void testLoadFromFileCertString() throws IOException, CertificateExceptio
assertEquals("http://localhost:8080/java-saml-jspsample/sls.jsp", setting.getSpSingleLogoutServiceUrl().toString());
assertEquals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", setting.getSpSingleLogoutServiceBinding());
assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", setting.getSpNameIDFormat());
+ assertTrue(setting.getSpAttributeConsumingServices().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -416,6 +512,7 @@ public void testLoadFromFileContactString() throws IOException, CertificateExcep
assertEquals("http://localhost:8080/java-saml-jspsample/sls.jsp", setting.getSpSingleLogoutServiceUrl().toString());
assertEquals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", setting.getSpSingleLogoutServiceBinding());
assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", setting.getSpNameIDFormat());
+ assertTrue(setting.getSpAttributeConsumingServices().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -527,6 +624,7 @@ public void testLoadFromFileSomeEmptyProp() throws IOException, CertificateExcep
assertEquals("http://localhost:8080/java-saml-jspsample/sls.jsp", setting.getSpSingleLogoutServiceUrl().toString());
assertEquals(setting.getSpSingleLogoutServiceBinding(), "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
assertEquals(setting.getSpNameIDFormat(), "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
+ assertTrue(setting.getSpAttributeConsumingServices().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -581,6 +679,7 @@ public void testLoadFromFileDifferentProp() throws IOException, CertificateExcep
assertEquals("http://localhost:8080/java-saml-jspsample/sls.jsp", setting.getSpSingleLogoutServiceUrl().toString());
assertEquals(setting.getSpSingleLogoutServiceBinding(), "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
assertEquals(setting.getSpNameIDFormat(), "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
+ assertTrue(setting.getSpAttributeConsumingServices().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -679,6 +778,7 @@ public void testFromProperties() throws IOException, Error, CertificateException
assertEquals("http://localhost:8080/java-saml-jspsample/sls.jsp", setting2.getSpSingleLogoutServiceUrl().toString());
assertEquals(setting2.getSpSingleLogoutServiceBinding(), "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
assertEquals(setting2.getSpNameIDFormat(), "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
+ assertTrue(setting.getSpAttributeConsumingServices().isEmpty());
assertEquals("http://idp.example.com/", setting2.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting2.getIdpSingleSignOnServiceUrl().toString());
@@ -739,6 +839,16 @@ public void testLoadFromValues() throws Exception {
samlData.put(SP_X509CERT_PROPERTY_KEY, "-----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----");
samlData.put(SP_X509CERTNEW_PROPERTY_KEY, "-----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----");
samlData.put(SP_PRIVATEKEY_PROPERTY_KEY, "-----BEGIN PRIVATE KEY-----MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAECgYA7VPVRl+/xoVeWdKdWY1F17HerSa23ynI2vQ8TkUY6kR3ucz6ElRxHJesY8fNCPoX+XuMfUly7IKyPZMkWyvEgDPo7J5mYqP5VsTK0Li4AwR/BA93Aw6gaX7/EYi3HjBh8QdNSt4fi9yOea/hv04yfR9Lx/a5fvQIyhqaDtT2QeQJBAOnCgnxnj70/sv9UsFPa8t1OGdAfXtOgEoklh1F2NR9jid6FPw5E98eCpdZ00MfRrmUavgqg6Y4swZISyzJIjGMCQQDN0YNsC4S+eJJM6aOCpupKluWE/cCWB01UQYekyXH7OdUtl49NlKEUPBSAvtaLMuMKlTNOjlPrx4Q+/c5i0vTPAkEA5H7CR9J/OZETaewhc8ZYkaRvLPYNHjWhCLhLXoB6itUkhgOfUFZwEXAOpOOI1VmL675JN2B1DAmJqTx/rQYnWwJBAMx3ztsAmnBq8dTM6y65ydouDHhRawjg2jbRHwNbSQvuyVSQ08Gb3WZvxWKdtB/3fsydqqnpBYAf5sZ5eJZ+wssCQAOiIKnhdYe+RBbBwykzjUqtzEmt4fwCFE8tD4feEx77D05j5f7u7KYh1mL0G2zIbnUryi7jwc4ye98VirRpZ1w=-----END PRIVATE KEY-----");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_NAME_PROPERTY_KEY_SUFFIX, "My service");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_DESCRIPTION_PROPERTY_KEY_SUFFIX, "My service description");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_LANG_PROPERTY_KEY_SUFFIX, "en");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "Email");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_FORMAT_PROPERTY_KEY_SUFFIX, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_FRIENDLY_NAME_PROPERTY_KEY_SUFFIX, "E-mail address");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_REQUIRED_PROPERTY_KEY_SUFFIX, "true");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[0]", "foo@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[1]", "bar@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "FirstName");
// Build IdP
samlData.put(IDP_ENTITYID_PROPERTY_KEY, "http://idp.example.com/");
@@ -817,6 +927,31 @@ public void testLoadFromValues() throws Exception {
assertEquals(setting.getSpNameIDFormat(), "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
assertNotNull(setting.getSPcert());
assertNotNull(setting.getSPcertNew());
+ final List attributeConsumingServices = setting.getSpAttributeConsumingServices();
+ assertEquals(1, attributeConsumingServices.size());
+ final AttributeConsumingService attributeConsumingService = attributeConsumingServices.get(0);
+ assertEquals(1, attributeConsumingService.getIndex());
+ assertNull(attributeConsumingService.isDefault());
+ assertEquals("My service", attributeConsumingService.getServiceName());
+ assertEquals("My service description", attributeConsumingService.getServiceDescription());
+ assertEquals("en", attributeConsumingService.getLang());
+ final List requestedAttributes = attributeConsumingService.getRequestedAttributes();
+ assertEquals(2, requestedAttributes.size());
+ final RequestedAttribute requestedAttribute1 = requestedAttributes.get(0);
+ assertEquals("Email", requestedAttribute1.getName());
+ assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", requestedAttribute1.getNameFormat());
+ assertEquals("E-mail address", requestedAttribute1.getFriendlyName());
+ assertTrue(requestedAttribute1.isRequired());
+ final List requestedAttribute1Values = requestedAttribute1.getAttributeValues();
+ assertEquals(2, requestedAttribute1Values.size());
+ assertEquals("foo@example.org", requestedAttribute1Values.get(0));
+ assertEquals("bar@example.org", requestedAttribute1Values.get(1));
+ final RequestedAttribute requestedAttribute2 = requestedAttributes.get(1);
+ assertEquals("FirstName", requestedAttribute2.getName());
+ assertNull(requestedAttribute2.getNameFormat());
+ assertNull(requestedAttribute2.getFriendlyName());
+ assertNull(requestedAttribute2.isRequired());
+ assertTrue(requestedAttribute2.getAttributeValues().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -951,6 +1086,16 @@ public void testLoadFromValuesWithObjects() throws Exception {
samlData.put(SP_X509CERT_PROPERTY_KEY, "-----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----");
samlData.put(SP_X509CERTNEW_PROPERTY_KEY, "-----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----");
samlData.put(SP_PRIVATEKEY_PROPERTY_KEY, "-----BEGIN PRIVATE KEY-----MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAECgYA7VPVRl+/xoVeWdKdWY1F17HerSa23ynI2vQ8TkUY6kR3ucz6ElRxHJesY8fNCPoX+XuMfUly7IKyPZMkWyvEgDPo7J5mYqP5VsTK0Li4AwR/BA93Aw6gaX7/EYi3HjBh8QdNSt4fi9yOea/hv04yfR9Lx/a5fvQIyhqaDtT2QeQJBAOnCgnxnj70/sv9UsFPa8t1OGdAfXtOgEoklh1F2NR9jid6FPw5E98eCpdZ00MfRrmUavgqg6Y4swZISyzJIjGMCQQDN0YNsC4S+eJJM6aOCpupKluWE/cCWB01UQYekyXH7OdUtl49NlKEUPBSAvtaLMuMKlTNOjlPrx4Q+/c5i0vTPAkEA5H7CR9J/OZETaewhc8ZYkaRvLPYNHjWhCLhLXoB6itUkhgOfUFZwEXAOpOOI1VmL675JN2B1DAmJqTx/rQYnWwJBAMx3ztsAmnBq8dTM6y65ydouDHhRawjg2jbRHwNbSQvuyVSQ08Gb3WZvxWKdtB/3fsydqqnpBYAf5sZ5eJZ+wssCQAOiIKnhdYe+RBbBwykzjUqtzEmt4fwCFE8tD4feEx77D05j5f7u7KYh1mL0G2zIbnUryi7jwc4ye98VirRpZ1w=-----END PRIVATE KEY-----");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_NAME_PROPERTY_KEY_SUFFIX, "My service");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_DESCRIPTION_PROPERTY_KEY_SUFFIX, "My service description");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_LANG_PROPERTY_KEY_SUFFIX, "en");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "Email");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_FORMAT_PROPERTY_KEY_SUFFIX, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_FRIENDLY_NAME_PROPERTY_KEY_SUFFIX, "E-mail address");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_REQUIRED_PROPERTY_KEY_SUFFIX, Boolean.TRUE);
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[0]", "foo@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[1]", "bar@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "FirstName");
// Build IdP
samlData.put(IDP_ENTITYID_PROPERTY_KEY, "http://idp.example.com/");
@@ -1025,6 +1170,31 @@ public void testLoadFromValuesWithObjects() throws Exception {
assertEquals(setting.getSpNameIDFormat(), "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
assertNotNull(setting.getSPcert());
assertNotNull(setting.getSPcertNew());
+ final List attributeConsumingServices = setting.getSpAttributeConsumingServices();
+ assertEquals(1, attributeConsumingServices.size());
+ final AttributeConsumingService attributeConsumingService = attributeConsumingServices.get(0);
+ assertEquals(1, attributeConsumingService.getIndex());
+ assertNull(attributeConsumingService.isDefault());
+ assertEquals("My service", attributeConsumingService.getServiceName());
+ assertEquals("My service description", attributeConsumingService.getServiceDescription());
+ assertEquals("en", attributeConsumingService.getLang());
+ final List requestedAttributes = attributeConsumingService.getRequestedAttributes();
+ assertEquals(2, requestedAttributes.size());
+ final RequestedAttribute requestedAttribute1 = requestedAttributes.get(0);
+ assertEquals("Email", requestedAttribute1.getName());
+ assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", requestedAttribute1.getNameFormat());
+ assertEquals("E-mail address", requestedAttribute1.getFriendlyName());
+ assertTrue(requestedAttribute1.isRequired());
+ final List requestedAttribute1Values = requestedAttribute1.getAttributeValues();
+ assertEquals(2, requestedAttribute1Values.size());
+ assertEquals("foo@example.org", requestedAttribute1Values.get(0));
+ assertEquals("bar@example.org", requestedAttribute1Values.get(1));
+ final RequestedAttribute requestedAttribute2 = requestedAttributes.get(1);
+ assertEquals("FirstName", requestedAttribute2.getName());
+ assertNull(requestedAttribute2.getNameFormat());
+ assertNull(requestedAttribute2.getFriendlyName());
+ assertNull(requestedAttribute2.isRequired());
+ assertTrue(requestedAttribute2.getAttributeValues().isEmpty());
assertEquals("http://idp.example.com/", setting.getIdpEntityId());
assertEquals("http://idp.example.com/simplesaml/saml2/idp/SSOService.php", setting.getIdpSingleSignOnServiceUrl().toString());
@@ -1112,6 +1282,95 @@ public void testLoadFromValuesWithObjects() throws Exception {
assertEquals("ONELOGIN_", setting.getUniqueIDPrefix());
}
+ /**
+ * Tests SettingsBuilder constructor
+ * Case: settings from values when multiple Attribute Consuming Services are defined
+ *
+ * @throws IOException
+ *
+ * @see com.onelogin.saml2.settings.SettingsBuilder
+ */
+ @Test
+ public void testLoadFromValuesMultiAttributeConsumingServices() throws Exception {
+ Map samlData = new LinkedHashMap<>();
+
+ // just build Attribute Consuming Services
+ // the following must be ignored, because indexed properties are present
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_NAME_PROPERTY_KEY_SUFFIX, "My service");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_DESCRIPTION_PROPERTY_KEY_SUFFIX, "My service description");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_LANG_PROPERTY_KEY_SUFFIX, "en");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "Email");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_FORMAT_PROPERTY_KEY_SUFFIX, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_FRIENDLY_NAME_PROPERTY_KEY_SUFFIX, "E-mail address");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_REQUIRED_PROPERTY_KEY_SUFFIX, Boolean.TRUE);
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[0]", "foo@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[1]", "bar@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "FirstName");
+ // the following, instead, must be processed
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_NAME_PROPERTY_KEY_SUFFIX, "Just e-mail");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "Email");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_FORMAT_PROPERTY_KEY_SUFFIX, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_FRIENDLY_NAME_PROPERTY_KEY_SUFFIX, "E-mail address");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_REQUIRED_PROPERTY_KEY_SUFFIX, "true");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[0]", "foo@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_VALUE_PROPERTY_KEY_PREFIX + "[1]", "bar@example.org");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_NAME_PROPERTY_KEY_SUFFIX, "Anagrafica");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_DESCRIPTION_PROPERTY_KEY_SUFFIX, "Servizio completo");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_LANG_PROPERTY_KEY_SUFFIX, "it");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_DEFAULT_PROPERTY_KEY_SUFFIX, "true");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[0]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "FirstName");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_NAME_PROPERTY_KEY_SUFFIX, "LastName");
+ samlData.put(SP_ATTRIBUTE_CONSUMING_SERVICE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_PROPERTY_KEY_PREFIX + "[1]." + SP_ATTRIBUTE_CONSUMING_SERVICE_ATTRIBUTE_REQUIRED_PROPERTY_KEY_SUFFIX, "true");
+
+ Saml2Settings setting = new SettingsBuilder().fromValues(samlData).build();
+
+ final List attributeConsumingServices = setting.getSpAttributeConsumingServices();
+ assertEquals(2, attributeConsumingServices.size());
+
+ {
+ final AttributeConsumingService attributeConsumingService1 = attributeConsumingServices.get(0);
+ assertEquals(0, attributeConsumingService1.getIndex());
+ assertNull(attributeConsumingService1.isDefault());
+ assertEquals("Just e-mail", attributeConsumingService1.getServiceName());
+ assertNull(attributeConsumingService1.getServiceDescription());
+ assertEquals("en", attributeConsumingService1.getLang());
+ final List requestedAttributes1 = attributeConsumingService1.getRequestedAttributes();
+ assertEquals(1, requestedAttributes1.size());
+ final RequestedAttribute requestedAttribute11 = requestedAttributes1.get(0);
+ assertEquals("Email", requestedAttribute11.getName());
+ assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", requestedAttribute11.getNameFormat());
+ assertEquals("E-mail address", requestedAttribute11.getFriendlyName());
+ assertTrue(requestedAttribute11.isRequired());
+ final List requestedAttribute11Values = requestedAttribute11.getAttributeValues();
+ assertEquals(2, requestedAttribute11Values.size());
+ assertEquals("foo@example.org", requestedAttribute11Values.get(0));
+ assertEquals("bar@example.org", requestedAttribute11Values.get(1));
+ }
+
+ {
+ final AttributeConsumingService attributeConsumingService2 = attributeConsumingServices.get(1);
+ assertEquals(1, attributeConsumingService2.getIndex());
+ assertTrue(attributeConsumingService2.isDefault());
+ assertEquals("Anagrafica", attributeConsumingService2.getServiceName());
+ assertEquals("Servizio completo", attributeConsumingService2.getServiceDescription());
+ assertEquals("it", attributeConsumingService2.getLang());
+ final List requestedAttributes2 = attributeConsumingService2.getRequestedAttributes();
+ assertEquals(2, requestedAttributes2.size());
+ final RequestedAttribute requestedAttribute21 = requestedAttributes2.get(0);
+ assertEquals("FirstName", requestedAttribute21.getName());
+ assertNull(requestedAttribute21.getNameFormat());
+ assertNull(requestedAttribute21.getFriendlyName());
+ assertNull(requestedAttribute21.isRequired());
+ final List requestedAttribute21Values = requestedAttribute21.getAttributeValues();
+ assertTrue(requestedAttribute21Values.isEmpty());
+ final RequestedAttribute requestedAttribute22 = requestedAttributes2.get(1);
+ assertEquals("LastName", requestedAttribute22.getName());
+ assertNull(requestedAttribute22.getNameFormat());
+ assertNull(requestedAttribute22.getFriendlyName());
+ assertTrue(requestedAttribute22.isRequired());
+ assertTrue(requestedAttribute22.getAttributeValues().isEmpty());
+ }
+ }
/**
* Tests SettingsBuilder constructor
* Case: settings config file with certificate loaded from file
diff --git a/core/src/test/resources/config/config.all.properties b/core/src/test/resources/config/config.all.properties
index fcdbf242..10a50bb2 100644
--- a/core/src/test/resources/config/config.all.properties
+++ b/core/src/test/resources/config/config.all.properties
@@ -31,6 +31,40 @@ onelogin.saml2.sp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bi
# Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
onelogin.saml2.sp.nameidformat = urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+# Attribute Consuming Service name when just one such service should be declared by the SP.
+# Comment out or set to empty if no Attribute Consuming Service should be declared, or if multiple ones should (see below).
+# The service name is mandatory.
+onelogin.saml2.sp.attribute_consuming_service.name = My service
+
+# Attribute Consuming Service description when just one such service should be declared by the SP.
+# Ignored if the previous property is commented or empty.
+# The service description is optional.
+onelogin.saml2.sp.attribute_consuming_service.description = My service description
+
+# Language used for Attribute Consuming Service name and description when just one such service should be declared by the SP.
+# Ignored if the name property is commented or empty.
+# The language is optional and default to "en" (English).
+onelogin.saml2.sp.attribute_consuming_service.lang = en
+
+# Attributes to be included in the Attribute Consuming Service when just one such service should be declared by the SP.
+# These are indexed properties, starting from 0. The index is used only to enumerate and sort attributes, but it's required.
+# The following properties allow to define each attribute:
+# - name: mandatory
+# - name_format: optional; if omitted, defaults to urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified
+# - friendly_name: optional; if omitted, it won't appear in SP metadata
+# - required: optional; if omitted or empty, defaults to false
+# - value[x]: an attribute value; the [x] is only used only to enumerate and sort values, but it's required
+# Please note that only simple values are currently supported and treated internally as strings. Hence no structured values
+# and no ability to specify an xsi:type attribute.
+# Attribute values are optional and most often they are simply omitted.
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name = Email
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[0] = foo@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[1] = bar@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[1].name = FirstName
+
# Usually x509cert and privateKey of the SP are provided by files placed at
# the certs folder. But we can also provide them with the following parameters
onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----
diff --git a/core/src/test/resources/config/config.all_multi_attribute_consuming_services.properties b/core/src/test/resources/config/config.all_multi_attribute_consuming_services.properties
new file mode 100644
index 00000000..cc3e8ed2
--- /dev/null
+++ b/core/src/test/resources/config/config.all_multi_attribute_consuming_services.properties
@@ -0,0 +1,208 @@
+# If 'strict' is True, then the Java Toolkit will reject unsigned
+# or unencrypted messages if it expects them signed or encrypted
+# Also will reject the messages if not strictly follow the SAML
+onelogin.saml2.strict = true
+
+# Enable debug mode (to print errors)
+onelogin.saml2.debug = true
+
+# Service Provider Data that we are deploying
+# Identifier of the SP entity (must be a URI)
+onelogin.saml2.sp.entityid = http://localhost:8080/java-saml-jspsample/metadata.jsp
+# Specifies info about where and how the message MUST be
+# returned to the requester, in this case our SP.
+# URL Location where the from the IdP will be returned
+onelogin.saml2.sp.assertion_consumer_service.url = http://localhost:8080/java-saml-jspsample/acs.jsp
+# SAML protocol binding to be used when returning the or sending the
+# message. Onelogin Toolkit supports for this endpoint the
+# HTTP-POST binding only
+onelogin.saml2.sp.assertion_consumer_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
+
+# Specifies info about Logout service
+# URL Location where the from the IdP will be returned or where to send the
+onelogin.saml2.sp.single_logout_service.url = http://localhost:8080/java-saml-jspsample/sls.jsp
+
+# SAML protocol binding for the Single Logout Service of the SP.
+# Onelogin Toolkit supports for this endpoint the HTTP-Redirect binding only
+onelogin.saml2.sp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
+
+# Specifies constraints on the name identifier to be used to
+# represent the requested subject.
+# Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
+onelogin.saml2.sp.nameidformat = urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+
+# THE FOLLOWING PROPERTIES FOR SINGLE ATTRIBUTE CONSUMING SERVICE MUST BE IGNORED - MULTIPLE SERVICES DEFINED LATER
+
+# Attribute Consuming Service name when just one such service should be declared by the SP.
+# Comment out or set to empty if no Attribute Consuming Service should be declared, or if multiple ones should (see below).
+# The service name is mandatory.
+onelogin.saml2.sp.attribute_consuming_service.name = My service
+
+# Attribute Consuming Service description when just one such service should be declared by the SP.
+# Ignored if the previous property is commented or empty.
+# The service description is optional.
+onelogin.saml2.sp.attribute_consuming_service.description = My service description
+
+# Language used for Attribute Consuming Service name and description when just one such service should be declared by the SP.
+# Ignored if the name property is commented or empty.
+# The language is optional and default to "en" (English).
+onelogin.saml2.sp.attribute_consuming_service.lang = en
+
+# Attributes to be included in the Attribute Consuming Service when just one such service should be declared by the SP.
+# These are indexed properties, starting from 0. The index is used only to enumerate and sort attributes, but it's required.
+# The following properties allow to define each attribute:
+# - name: mandatory
+# - name_format: optional; if omitted, defaults to urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified
+# - friendly_name: optional; if omitted, it won't appear in SP metadata
+# - required: optional; if omitted or empty, defaults to false
+# - value[x]: an attribute value; the [x] is only used only to enumerate and sort values, but it's required
+# Please note that only simple values are currently supported and treated internally as strings. Hence no structured values
+# and no ability to specify an xsi:type attribute.
+# Attribute values are optional and most often they are simply omitted.
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name = Email_Wrong
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[0] = foo_wrong@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[1] = bar_wrong@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[1].name = FirstName_Wrong
+
+# THE FOLLOWING PROPERTIES MUST BE PROCESSED INSTEAD
+
+onelogin.saml2.sp.attribute_consuming_service[0].name = Just e-mail
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name = Email
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[0] = foo@example.org
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[1] = bar@example.org
+onelogin.saml2.sp.attribute_consuming_service[1].name = Anagrafica "completa"
+onelogin.saml2.sp.attribute_consuming_service[1].description = Dati anagrafici "completi"
+onelogin.saml2.sp.attribute_consuming_service[1].lang = it
+onelogin.saml2.sp.attribute_consuming_service[1].default = true
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[0].name = Birth date
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].name = First & Last Name
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].required = true
+
+# Usually x509cert and privateKey of the SP are provided by files placed at
+# the certs folder. But we can also provide them with the following parameters
+onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----
+
+# To be used during SP Key roll over
+onelogin.saml2.sp.x509certNew = -----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----
+
+# Requires Format PKCS#8 BEGIN PRIVATE KEY
+# If you have PKCS#1 BEGIN RSA PRIVATE KEY convert it by openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem
+onelogin.saml2.sp.privatekey = -----BEGIN PRIVATE KEY-----MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAECgYA7VPVRl+/xoVeWdKdWY1F17HerSa23ynI2vQ8TkUY6kR3ucz6ElRxHJesY8fNCPoX+XuMfUly7IKyPZMkWyvEgDPo7J5mYqP5VsTK0Li4AwR/BA93Aw6gaX7/EYi3HjBh8QdNSt4fi9yOea/hv04yfR9Lx/a5fvQIyhqaDtT2QeQJBAOnCgnxnj70/sv9UsFPa8t1OGdAfXtOgEoklh1F2NR9jid6FPw5E98eCpdZ00MfRrmUavgqg6Y4swZISyzJIjGMCQQDN0YNsC4S+eJJM6aOCpupKluWE/cCWB01UQYekyXH7OdUtl49NlKEUPBSAvtaLMuMKlTNOjlPrx4Q+/c5i0vTPAkEA5H7CR9J/OZETaewhc8ZYkaRvLPYNHjWhCLhLXoB6itUkhgOfUFZwEXAOpOOI1VmL675JN2B1DAmJqTx/rQYnWwJBAMx3ztsAmnBq8dTM6y65ydouDHhRawjg2jbRHwNbSQvuyVSQ08Gb3WZvxWKdtB/3fsydqqnpBYAf5sZ5eJZ+wssCQAOiIKnhdYe+RBbBwykzjUqtzEmt4fwCFE8tD4feEx77D05j5f7u7KYh1mL0G2zIbnUryi7jwc4ye98VirRpZ1w=-----END PRIVATE KEY-----
+
+# Identity Provider Data that we want connect with our SP
+# Identifier of the IdP entity (must be a URI)
+onelogin.saml2.idp.entityid = http://idp.example.com/
+
+# SSO endpoint info of the IdP. (Authentication Request protocol)
+# URL Target of the IdP where the SP will send the Authentication Request Message
+onelogin.saml2.idp.single_sign_on_service.url = http://idp.example.com/simplesaml/saml2/idp/SSOService.php
+
+# SAML protocol binding to be used to deliver the message
+# to the IdP. Onelogin Toolkit supports for this endpoint the
+# HTTP-Redirect binding only
+onelogin.saml2.idp.single_sign_on_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
+
+# SLO endpoint info of the IdP.
+# URL Location of the IdP where the SP will send the SLO Request
+onelogin.saml2.idp.single_logout_service.url = http://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php
+
+# Optional SLO Response endpoint info of the IdP.
+# URL Location of the IdP where the SP will send the SLO Response. If left blank, same URL as onelogin.saml2.idp.single_logout_service.url will be used.
+# Some IdPs use a separate URL for sending a logout request and response, use this property to set the separate response url
+onelogin.saml2.idp.single_logout_service.response.url = http://idp.example.com/simplesaml/saml2/idp/SingleLogoutServiceResponse.php
+
+# SAML protocol binding to be used when returning the
+# message. Onelogin Toolkit supports for this endpoint the
+# HTTP-Redirect binding only
+onelogin.saml2.idp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
+
+# Public x509 certificate of the IdP
+onelogin.saml2.idp.x509cert = -----BEGIN CERTIFICATE-----\nMIIBrTCCAaGgAwIBAgIBATADBgEAMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMB4XDTEwMTAxMTIxMTUxMloXDTE1MTAxMTIxMTUxMlowZzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAcMDFNhbnRhIE1vbmljYTERMA8GA1UECgwIT25lTG9naW4xGTAXBgNVBAMMEGFwcC5vbmVsb2dpbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMPmjfjy7L35oDpeBXBoRVCgktPkLno9DOEWB7MgYMMVKs2B6ymWQLEWrDugMK1hkzWFhIb5fqWLGbWy0J0veGR9/gHOQG+rD/I36xAXnkdiXXhzoiAG/zQxM0edMOUf40n314FC8moErcUg6QabttzesO59HFz6shPuxcWaVAgxAgMBAAEwAwYBAAMBAA==\n-----END CERTIFICATE-----
+onelogin.saml2.idp.certfingerprint = 4b6f70bb2cab82c86a8270f71a880b62e25bc2b3
+onelogin.saml2.idp.certfingerprint_algorithm = sha1
+
+# Security settings
+#
+
+# Indicates that the nameID of the sent by this SP
+# will be encrypted.
+onelogin.saml2.security.nameid_encrypted = true
+
+# Indicates whether the messages sent by this SP
+# will be signed. [The Metadata of the SP will offer this info]
+onelogin.saml2.security.authnrequest_signed = true
+
+# Indicates whether the messages sent by this SP
+# will be signed.
+onelogin.saml2.security.logoutrequest_signed = true
+
+# Indicates whether the messages sent by this SP
+# will be signed.
+onelogin.saml2.security.logoutresponse_signed = true
+
+# Indicates a requirement for the , and
+# elements received by this SP to be signed.
+onelogin.saml2.security.want_messages_signed = true
+
+# Indicates a requirement for the of the to be signed
+onelogin.saml2.security.want_assertions_signed = true
+
+# Indicates a requirement for the Metadata of this SP to be signed.
+# Right now supported null/false (in order to not sign) or true (sign using SP private key)
+onelogin.saml2.security.sign_metadata = true
+
+# Indicates a requirement for the Assertions received by this SP to be encrypted
+onelogin.saml2.security.want_assertions_encrypted = true
+
+# Indicates a requirement for the NameID received by this SP to be encrypted
+onelogin.saml2.security.want_nameid_encrypted = true
+
+# Authentication context.
+# Set Empty and no AuthContext will be sent in the AuthNRequest,
+# Set comma separated values urn:oasis:names:tc:SAML:2.0:ac:classes:urn:oasis:names:tc:SAML:2.0:ac:classes:Password
+onelogin.saml2.security.requested_authncontext = urn:oasis:names:tc:SAML:2.0:ac:classes:urn:oasis:names:tc:SAML:2.0:ac:classes:Password
+
+# Allows the authn comparison parameter to be set, defaults to 'exact'
+onelogin.saml2.security.requested_authncontextcomparison = exact
+
+
+# Indicates if the SP will validate all received xmls.
+# (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true).
+onelogin.saml2.security.want_xml_validation = true
+
+# Algorithm that the toolkit will use on signing process. Options:
+# 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
+# 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
+# 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
+# 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
+onelogin.saml2.security.signature_algorithm = http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
+
+# Algorithm that the toolkit will use on digest process. Options:
+# 'http://www.w3.org/2000/09/xmldsig#sha1'
+# 'http://www.w3.org/2001/04/xmlenc#sha256'
+# 'http://www.w3.org/2001/04/xmldsig-more#sha384'
+# 'http://www.w3.org/2001/04/xmlenc#sha512'
+onelogin.saml2.security.digest_algorithm = http://www.w3.org/2001/04/xmlenc#sha512
+
+# Organization
+onelogin.saml2.organization.name = SP Java
+onelogin.saml2.organization.displayname = SP Java Example
+onelogin.saml2.organization.url = http://sp.example.com
+onelogin.saml2.organization.lang = en
+
+# Contacts
+onelogin.saml2.contacts.technical.given_name = Technical Guy
+onelogin.saml2.contacts.technical.email_address = technical@example.com
+onelogin.saml2.contacts.support.given_name = Support Guy
+onelogin.saml2.contacts.support.email_address = support@example.com
+
+# Prefix used in generated Unique IDs.
+# Optional, defaults to ONELOGIN_ or full ID is like ONELOGIN_ebb0badd-4f60-4b38-b20a-a8e01f0592b1.
+# At minimun, the prefix can be non-numeric character such as "_".
+onelogin.saml2.unique_id_prefix = EXAMPLE
diff --git a/core/src/test/resources/config/config.all_specialchars.properties b/core/src/test/resources/config/config.all_specialchars.properties
index 923297e0..b64bffd7 100644
--- a/core/src/test/resources/config/config.all_specialchars.properties
+++ b/core/src/test/resources/config/config.all_specialchars.properties
@@ -31,6 +31,40 @@ onelogin.saml2.sp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bi
# Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
onelogin.saml2.sp.nameidformat = urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
+# Attribute Consuming Service name when just one such service should be declared by the SP.
+# Comment out or set to empty if no Attribute Consuming Service should be declared, or if multiple ones should (see below).
+# The service name is mandatory.
+onelogin.saml2.sp.attribute_consuming_service.name = My s&rvice
+
+# Attribute Consuming Service description when just one such service should be declared by the SP.
+# Ignored if the previous property is commented or empty.
+# The service description is optional.
+onelogin.saml2.sp.attribute_consuming_service.description = My s&rvice description
+
+# Language used for Attribute Consuming Service name and description when just one such service should be declared by the SP.
+# Ignored if the name property is commented or empty.
+# The language is optional and default to "en" (English).
+onelogin.saml2.sp.attribute_consuming_service.lang = &n
+
+# Attributes to be included in the Attribute Consuming Service when just one such service should be declared by the SP.
+# These are indexed properties, starting from 0. The index is used only to enumerate and sort attributes, but it's required.
+# The following properties allow to define each attribute:
+# - name: mandatory
+# - name_format: optional; if omitted, defaults to urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified
+# - friendly_name: optional; if omitted, it won't appear in SP metadata
+# - required: optional; if omitted or empty, defaults to false
+# - value[x]: an attribute value; the [x] is only used only to enumerate and sort values, but it's required
+# Please note that only simple values are currently supported and treated internally as strings. Hence no structured values
+# and no ability to specify an xsi:type attribute.
+# Attribute values are optional and most often they are simply omitted.
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name = Email
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].friendly_name = E-mail "address"
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[0] = foo&bar@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[1] = bar@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[1].name = FirstName&LastName
+
# Usually x509cert and privateKey of the SP are provided by files placed at
# the certs folder. But we can also provide them with the following parameters
onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----
@@ -137,6 +171,15 @@ onelogin.saml2.security.signature_algorithm = http://www.w3.org/2001/04/xmldsig-
# 'http://www.w3.org/2001/04/xmlenc#sha512'
onelogin.saml2.security.digest_algorithm = http://www.w3.org/2001/04/xmlenc#sha512
+# Enable trimming of parsed Name IDs and attribute values
+# SAML specification states that no trimming for string elements should be performed, so no trimming will be
+# performed by default on extracted Name IDs and attribute values. However, some SAML implementations may add
+# undesirable surrounding whitespace when outputting XML (possibly due to formatting/pretty-printing).
+# These two options allow to optionally enable value trimming on extracted Name IDs (including issuers) and
+# attribute values.
+onelogin.saml2.parsing.trim_name_ids = false
+onelogin.saml2.parsing.trim_attribute_values = false
+
# Organization
onelogin.saml2.organization.name = S&P Java
onelogin.saml2.organization.displayname = S&P Java "Example"
diff --git a/core/src/test/resources/config/config.allerrors_multi_attribute_consuming_services.properties b/core/src/test/resources/config/config.allerrors_multi_attribute_consuming_services.properties
new file mode 100644
index 00000000..d4ff9352
--- /dev/null
+++ b/core/src/test/resources/config/config.allerrors_multi_attribute_consuming_services.properties
@@ -0,0 +1,40 @@
+# we have some Attribute Consuming Services with missing information and multiple defaults
+onelogin.saml2.sp.attribute_consuming_service[0].name = Just e-mail
+onelogin.saml2.sp.attribute_consuming_service[0].default = true
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name =
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[0] = foo@example.org
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[1] = bar@example.org
+onelogin.saml2.sp.attribute_consuming_service[1].name =
+onelogin.saml2.sp.attribute_consuming_service[1].default = true
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[0].name = FirstName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].name = LastName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].required = true
+onelogin.saml2.sp.attribute_consuming_service[2].name = No requested attributes
+
+# Usually x509cert and privateKey of the SP are provided by files placed at
+# the certs folder. But we can also provide them with the following parameters
+onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----MIICeDCCAeGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wHhcNMTUxMDE4MjAxMjM1WhcNMTgwNzE0MjAxMjM1WjBZMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lTG9naW4gSW5jMR4wHAYDVQQDDBVqYXZhLXNhbWwuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALvwEktX1+4y2AhEqxVwOO6HO7Wtzi3hr5becRkfLYGjNSyhzZCjI1DsNL61JSWDO3nviZd9fSkFnRC4akFUm0CS6GJ7TZe4T5o+9aowQ6N8e8cts9XPXyP6Inz7q4sD8pO2EInlfwHYPQCqFmz/SDW7cDgIC8vb0ygOsiXdreANAgMBAAGjUDBOMB0GA1UdDgQWBBTifMwN3CQ5ZOPkV5tDJsutU8teFDAfBgNVHSMEGDAWgBTifMwN3CQ5ZOPkV5tDJsutU8teFDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAG3nAEUjJaA75SkzID5FKLolsxG5TE/0HU0+yEUAVkXiqvqN4mPWq/JjoK5+uP4LEZIb4pRrCqI3iHp+vazLLYSeyV3kaGN7q35Afw8nk8WM0f7vImbQ69j1S8GQ+6E0PEI26qBLykGkMn3GUVtBBWSdpP093NuNLJiOomnHqhqj-----END CERTIFICATE-----
+
+# Indicates a requirement for the , and
+# elements received by this SP to be signed.
+onelogin.saml2.security.want_messages_signed = true
+
+# Indicates that the nameID of the sent by this SP
+# will be encrypted.
+onelogin.saml2.security.nameid_encrypted = true
+
+# Indicates whether the messages sent by this SP
+# will be signed. [The Metadata of the SP will offer this info]
+onelogin.saml2.security.authnrequest_signed = true
+
+# Organization
+onelogin.saml2.organization.name = SP Java
+onelogin.saml2.organization.url = http://sp.example.com
+
+# Contacts
+onelogin.saml2.sp.contact[0].contactType=administrative
+onelogin.saml2.sp.contact[1].contactType=nonexistent
+onelogin.saml2.sp.contact[1].company=ACME
\ No newline at end of file
diff --git a/core/src/test/resources/config/config.min_multi_attribute_consuming_services.properties b/core/src/test/resources/config/config.min_multi_attribute_consuming_services.properties
new file mode 100644
index 00000000..db6a0050
--- /dev/null
+++ b/core/src/test/resources/config/config.min_multi_attribute_consuming_services.properties
@@ -0,0 +1,62 @@
+# Service Provider Data that we are deploying
+# Identifier of the SP entity (must be a URI)
+onelogin.saml2.sp.entityid = http://localhost:8080/java-saml-jspsample/metadata.jsp
+# Specifies info about where and how the message MUST be
+# returned to the requester, in this case our SP.
+# URL Location where the from the IdP will be returned
+onelogin.saml2.sp.assertion_consumer_service.url = http://localhost:8080/java-saml-jspsample/acs.jsp
+
+# Specifies info about Logout service
+# URL Location where the from the IdP will be returned or where to send the
+onelogin.saml2.sp.single_logout_service.url = http://localhost:8080/java-saml-jspsample/sls.jsp
+
+# Attributes to be included in the Attribute Consuming Service when just one such service should be declared by the SP.
+# These are indexed properties, starting from 0. The index is used only to enumerate and sort attributes, but it's required.
+# The following properties allow to define each attribute:
+# - name: mandatory
+# - name_format: optional; if omitted, defaults to urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified
+# - friendly_name: optional; if omitted, it won't appear in SP metadata
+# - required: optional; if omitted or empty, defaults to false
+# - value[x]: an attribute value; the [x] is only used only to enumerate and sort values, but it's required
+# Please note that only simple values are currently supported and treated internally as strings. Hence no structured values
+# and no ability to specify an xsi:type attribute.
+# Attribute values are optional and most often they are simply omitted.
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name = Email_Wrong
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[0] = foo_wrong@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[0].value[1] = bar_wrong@example.org
+onelogin.saml2.sp.attribute_consuming_service.attribute[1].name = FirstName_Wrong
+
+# THE FOLLOWING PROPERTIES MUST BE PROCESSED INSTEAD
+
+onelogin.saml2.sp.attribute_consuming_service[0].name = Just e-mail
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name = Email
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[0] = foo@example.org
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[1] = bar@example.org
+onelogin.saml2.sp.attribute_consuming_service[1].name = Anagrafica
+onelogin.saml2.sp.attribute_consuming_service[1].description = Servizio completo
+onelogin.saml2.sp.attribute_consuming_service[1].lang = it
+onelogin.saml2.sp.attribute_consuming_service[1].default = true
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[0].name = FirstName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].name = LastName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].required = true
+
+# Identity Provider Data that we want connect with our SP
+# Identifier of the IdP entity (must be a URI)
+onelogin.saml2.idp.entityid = http://idp.example.com/
+
+# SSO endpoint info of the IdP. (Authentication Request protocol)
+# URL Target of the IdP where the SP will send the Authentication Request Message
+onelogin.saml2.idp.single_sign_on_service.url = http://idp.example.com/simplesaml/saml2/idp/SSOService.php
+
+# SLO endpoint info of the IdP.
+# URL Location of the IdP where the SP will send the SLO Request
+onelogin.saml2.idp.single_logout_service.url = http://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php
+
+# Public x509 certificate of the IdP
+onelogin.saml2.idp.x509cert = -----BEGIN CERTIFICATE-----\nMIIBrTCCAaGgAwIBAgIBATADBgEAMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMB4XDTEwMTAxMTIxMTUxMloXDTE1MTAxMTIxMTUxMlowZzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAcMDFNhbnRhIE1vbmljYTERMA8GA1UECgwIT25lTG9naW4xGTAXBgNVBAMMEGFwcC5vbmVsb2dpbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMPmjfjy7L35oDpeBXBoRVCgktPkLno9DOEWB7MgYMMVKs2B6ymWQLEWrDugMK1hkzWFhIb5fqWLGbWy0J0veGR9/gHOQG+rD/I36xAXnkdiXXhzoiAG/zQxM0edMOUf40n314FC8moErcUg6QabttzesO59HFz6shPuxcWaVAgxAgMBAAEwAwYBAAMBAA==\n-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/core/src/test/resources/config/config.sperrors_multi_attribute_consuming_services.properties b/core/src/test/resources/config/config.sperrors_multi_attribute_consuming_services.properties
new file mode 100644
index 00000000..267c8d35
--- /dev/null
+++ b/core/src/test/resources/config/config.sperrors_multi_attribute_consuming_services.properties
@@ -0,0 +1,51 @@
+# Identity Provider Data that we want connect with our SP
+# Identifier of the IdP entity (must be a URI)
+onelogin.saml2.idp.entityid = http://idp.example.com/
+
+# SSO endpoint info of the IdP. (Authentication Request protocol)
+# URL Target of the IdP where the SP will send the Authentication Request Message
+onelogin.saml2.idp.single_sign_on_service.url = http://idp.example.com/simplesaml/saml2/idp/SSOService.php
+
+# SLO endpoint info of the IdP.
+# URL Location of the IdP where the SP will send the SLO Request
+onelogin.saml2.idp.single_logout_service.url = http://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php
+
+# we have some Attribute Consuming Services with missing information and multiple defaults
+onelogin.saml2.sp.attribute_consuming_service[0].name = Just e-mail
+onelogin.saml2.sp.attribute_consuming_service[0].default = true
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name =
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].name_format = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].friendly_name = E-mail address
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].required = true
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[0] = foo@example.org
+onelogin.saml2.sp.attribute_consuming_service[0].attribute[0].value[1] = bar@example.org
+onelogin.saml2.sp.attribute_consuming_service[1].name =
+onelogin.saml2.sp.attribute_consuming_service[1].default = true
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[0].name = FirstName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].name = LastName
+onelogin.saml2.sp.attribute_consuming_service[1].attribute[1].required = true
+onelogin.saml2.sp.attribute_consuming_service[2].name = No requested attributes
+
+# Public x509 certificate of the IdP
+onelogin.saml2.idp.x509cert = -----BEGIN CERTIFICATE-----\nMIIBrTCCAaGgAwIBAgIBATA
+
+# Indicates a requirement for the , and
+# elements received by this SP to be signed.
+onelogin.saml2.security.want_messages_signed = true
+
+# Indicates that the nameID of the sent by this SP
+# will be encrypted.
+onelogin.saml2.security.nameid_encrypted = true
+
+# Indicates whether the messages sent by this SP
+# will be signed. [The Metadata of the SP will offer this info]
+onelogin.saml2.security.authnrequest_signed = true
+
+# Organization
+onelogin.saml2.organization.name = SP Java
+onelogin.saml2.organization.displayname = SP Java Example
+
+# Contacts
+onelogin.saml2.sp.contact[0].contactType=administrative
+onelogin.saml2.sp.contact[1].contactType=nonexistent
+onelogin.saml2.sp.contact[1].company=ACME
\ No newline at end of file