]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
SAML SSO unit tests etc. (#455)
authorAnthony Bucci <anthony@bucci.onl>
Thu, 20 Mar 2025 18:58:33 +0000 (14:58 -0400)
committerMichael Ritter <mike.ritter@lyrasis.org>
Thu, 20 Mar 2025 19:37:45 +0000 (13:37 -0600)
* Added two unit tests to SecurityUtilsTest to verify the found email address is correct
* Removed unused imports in SecurityUtilsTest
* Added unit tests for ServicesConfigReader while investigating DRYD-1702
* cleaned up leftover printlns in SecurityUtilsTest
* cleaned up imports in SecurityUtilsTest
* reorganized methods in ServicesConfigReaderImplTest
* refactored findUser method to make it easier to test and prepare it for deprecation of ReponseToken
* refactored some useful common code out of SecurityUtilsTest into AbstractSecurityTestBase
* refactored some of the SAML-object-creating utility methods
* made 'parse' methods public so they can be tested
* added unit test to check that 'identifier' probe assertions correctly pull out attribute values
* source cleanup: organizing imports, formatting
* added .mvn to .gitignore to ignore local, per-developer maven properties

---------

Co-authored-by: Anthony Bucci <abucci@bucci.onl>
.gitignore
services/common/src/main/java/org/collectionspace/services/common/security/CSpaceSaml2ResponseAuthenticationConverter.java
services/common/src/test/java/org/collectionspace/services/common/test/AbstractSecurityTestBase.java [new file with mode: 0644]
services/common/src/test/java/org/collectionspace/services/common/test/SecurityUtilsTest.java
services/config/pom.xml
services/config/src/main/java/org/collectionspace/services/common/config/AbstractConfigReaderImpl.java
services/config/src/test/java/org/collectionspace/services/common/config/ServicesConfigReaderImplTest.java [new file with mode: 0644]

index db5b3c75a2017baac0fd37918a9c9c65f595c574..f1b47dc39f248094733cc9eea125e5df23ad0700 100644 (file)
@@ -24,3 +24,4 @@ bin
 *.diff
 logged_schemas
 logs
+.mvn
index 0a2aa3564ab77c9cc6e3bdcb06e654e4d0bfdff9..d91bae9ca68a76b1efef5aff4faab519e288da9f 100644 (file)
@@ -22,124 +22,139 @@ import org.springframework.security.saml2.provider.service.authentication.OpenSa
 import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
 
 public class CSpaceSaml2ResponseAuthenticationConverter implements Converter<ResponseToken, CSpaceSaml2Authentication> {
-  private final Logger logger = LoggerFactory.getLogger(CSpaceSaml2ResponseAuthenticationConverter.class);
-
-  private CSpaceUserDetailsService userDetailsService;
-
-  public CSpaceSaml2ResponseAuthenticationConverter(CSpaceUserDetailsService userDetailsService) {
-    this.userDetailsService = userDetailsService;
-  }
-
-  @Override
-  public CSpaceSaml2Authentication convert(ResponseToken responseToken) {
-    Saml2Authentication authentication = OpenSamlAuthenticationProvider
-      .createDefaultResponseAuthenticationConverter()
-      .convert(responseToken);
-
-    String registrationId = responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
-    ServiceConfig serviceConfig = ServiceMain.getInstance().getServiceConfig();
-    SAMLRelyingPartyType relyingPartyRegistration = ConfigUtils.getSAMLRelyingPartyRegistration(serviceConfig, registrationId);
-    CSpaceUser user = findUser(relyingPartyRegistration, responseToken);
-
-    if (user != null) {
-      return new CSpaceSaml2Authentication(user, authentication);
-    }
-
-    return null;
-  }
-
-  /**
-   * Attempt to find a CSpace user for a SAML response.
-   *
-   * @param relyingPartyRegistration
-   * @param responseToken
-   * @return
-   */
-  private CSpaceUser findUser(SAMLRelyingPartyType relyingPartyRegistration, ResponseToken responseToken) {
-    AssertionProbesType assertionSsoIdProbes = (
-      relyingPartyRegistration != null
-        ? relyingPartyRegistration.getAssertionSsoIdProbes()
-        : null
-    );
-
-    AssertionProbesType assertionUsernameProbes = (
-      relyingPartyRegistration != null
-        ? relyingPartyRegistration.getAssertionUsernameProbes()
-        : null
-    );
-
-    List<String> attemptedUsernames = new ArrayList<>();
-    List<Assertion> assertions = responseToken.getResponse().getAssertions();
-
-    SecurityUtils.logSamlAssertions(assertions);
-
-    for (Assertion assertion : assertions) {
-      CSpaceUser user = null;
-      String ssoId = SecurityUtils.getSamlAssertionSsoId(assertion, assertionSsoIdProbes);
-
-      // First, look for a CSpace user whose SSO ID is the ID in the assertion.
-
-      if (ssoId != null) {
-        try {
-          user = (CSpaceUser) userDetailsService.loadUserBySsoId(ssoId);
-        }
-        catch (UsernameNotFoundException e) {
-        }
-      }
-
-      if (user != null) {
-        return user;
-      }
-
-      // Next, look for a CSpace user whose username is the email address in the assertion.
-
-      Set<String> candidateUsernames = SecurityUtils.findSamlAssertionCandidateUsernames(assertion, assertionUsernameProbes);
-
-      for (String candidateUsername : candidateUsernames) {
-        try {
-          user = (CSpaceUser) userDetailsService.loadUserByUsername(candidateUsername);
-
-          if (user != null) {
-            String expectedSsoId = user.getSsoId();
-
-            if (expectedSsoId == null) {
-              // Store the ID from the IdP to use in future log ins. Note that this does not save
-              // the SSO ID to the database. That happens in CSpaceAuthenticationSuccessEvent.
-
-              user.setSsoId(ssoId);
-
-              // TODO: If the email address in the assertion differs from the CSpace user's email,
-              // update the CSpace user.
-            } else if (!StringUtils.equals(expectedSsoId, ssoId)) {
-              // If the user previously logged in via SSO, but they had a different ID from the
-              // IdP, something's wrong. (Did an account on the IdP get assigned an email that
-              // previously belonged to a different account on the IdP?)
-
-              logger.warn("User with username {} has expected SSO ID {}, but received {} in SAML assertion",
-                candidateUsername, expectedSsoId, ssoId);
-
-              user = null;
-            }
-
-            if (user != null) {
-              return user;
-            }
-          }
-        }
-        catch(UsernameNotFoundException e) {
-        }
-      }
-
-      attemptedUsernames.addAll(candidateUsernames);
-    }
-
-    // No CSpace user was found for this SAML response.
-    // TODO: Auto-create a CSpace user, using the display name, email address, and ID in the response.
-
-    String errorMessage = attemptedUsernames.size() == 0
-      ? "The SAML response did not contain a CollectionSpace username."
-      : "No CollectionSpace account found for " + StringUtils.join(attemptedUsernames, " / ") + ".";
-
-    throw(new UsernameNotFoundException(errorMessage));
-  }
+       private final Logger logger = LoggerFactory.getLogger(CSpaceSaml2ResponseAuthenticationConverter.class);
+
+       private CSpaceUserDetailsService userDetailsService;
+
+       public CSpaceSaml2ResponseAuthenticationConverter(CSpaceUserDetailsService userDetailsService) {
+               this.userDetailsService = userDetailsService;
+       }
+
+       @Override
+       public CSpaceSaml2Authentication convert(ResponseToken responseToken) {
+               Saml2Authentication authentication = OpenSamlAuthenticationProvider
+                               .createDefaultResponseAuthenticationConverter().convert(responseToken);
+
+               String registrationId = responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
+               ServiceConfig serviceConfig = ServiceMain.getInstance().getServiceConfig();
+               SAMLRelyingPartyType relyingPartyRegistration = ConfigUtils.getSAMLRelyingPartyRegistration(serviceConfig,
+                               registrationId);
+               CSpaceUser user = findUser(relyingPartyRegistration, responseToken);
+
+               if (user != null) {
+                       return new CSpaceSaml2Authentication(user, authentication);
+               }
+
+               return null;
+       }
+
+       /**
+        * Attempt to find a CSpace user for from a list of relying parties
+        * (from the services configuration) and a list of assertions (from the SAML response)
+        * 
+        * @param relyingPartyRegistration object representing relying parties from the
+        *                                 service config
+        * @param assertions               list of SAML assertions to test
+        * @return                         the found CSpace user, if any
+        */
+       private CSpaceUser findUser(SAMLRelyingPartyType relyingPartyRegistration, List<Assertion> assertions) {
+               AssertionProbesType assertionSsoIdProbes = (relyingPartyRegistration != null
+                               ? relyingPartyRegistration.getAssertionSsoIdProbes()
+                               : null);
+
+               AssertionProbesType assertionUsernameProbes = (relyingPartyRegistration != null
+                               ? relyingPartyRegistration.getAssertionUsernameProbes()
+                               : null);
+
+               List<String> attemptedUsernames = new ArrayList<>();
+
+               SecurityUtils.logSamlAssertions(assertions);
+
+               for (Assertion assertion : assertions) {
+                       CSpaceUser user = null;
+                       String ssoId = SecurityUtils.getSamlAssertionSsoId(assertion, assertionSsoIdProbes);
+
+                       // First, look for a CSpace user whose SSO ID is the ID in the assertion.
+
+                       if (ssoId != null) {
+                               try {
+                                       user = (CSpaceUser) userDetailsService.loadUserBySsoId(ssoId);
+                               } catch (UsernameNotFoundException e) {
+                               }
+                       }
+
+                       if (user != null) {
+                               return user;
+                       }
+
+                       // Next, look for a CSpace user whose username is the email address in the
+                       // assertion.
+
+                       Set<String> candidateUsernames = SecurityUtils.findSamlAssertionCandidateUsernames(assertion,
+                                       assertionUsernameProbes);
+
+                       for (String candidateUsername : candidateUsernames) {
+                               try {
+                                       user = (CSpaceUser) userDetailsService.loadUserByUsername(candidateUsername);
+
+                                       if (user != null) {
+                                               String expectedSsoId = user.getSsoId();
+
+                                               if (expectedSsoId == null) {
+                                                       // Store the ID from the IdP to use in future log ins. Note that this does not
+                                                       // save
+                                                       // the SSO ID to the database. That happens in CSpaceAuthenticationSuccessEvent.
+
+                                                       user.setSsoId(ssoId);
+
+                                                       // TODO: If the email address in the assertion differs from the CSpace user's
+                                                       // email,
+                                                       // update the CSpace user.
+                                               } else if (!StringUtils.equals(expectedSsoId, ssoId)) {
+                                                       // If the user previously logged in via SSO, but they had a different ID from
+                                                       // the
+                                                       // IdP, something's wrong. (Did an account on the IdP get assigned an email that
+                                                       // previously belonged to a different account on the IdP?)
+
+                                                       logger.warn(
+                                                                       "User with username {} has expected SSO ID {}, but received {} in SAML assertion",
+                                                                       candidateUsername, expectedSsoId, ssoId);
+
+                                                       user = null;
+                                               }
+
+                                               if (user != null) {
+                                                       return user;
+                                               }
+                                       }
+                               } catch (UsernameNotFoundException e) {
+                               }
+                       }
+
+                       attemptedUsernames.addAll(candidateUsernames);
+               }
+
+               // No CSpace user was found for this SAML response.
+               // TODO: Auto-create a CSpace user, using the display name, email address, and
+               // ID in the response.
+
+               String errorMessage = attemptedUsernames.size() == 0
+                               ? "The SAML response did not contain a CollectionSpace username."
+                               : "No CollectionSpace account found for " + StringUtils.join(attemptedUsernames, " / ") + ".";
+
+               throw (new UsernameNotFoundException(errorMessage));
+       }
+
+       /**
+        * Attempt to find a CSpace user for a SAML response.
+        *
+        * @deprecated
+        * @param relyingPartyRegistration
+        * @param responseToken
+        * @return a CSpace user
+        */
+       private CSpaceUser findUser(SAMLRelyingPartyType relyingPartyRegistration, ResponseToken responseToken) {
+               List<Assertion> assertions = responseToken.getResponse().getAssertions();
+               return findUser(relyingPartyRegistration,assertions);
+       }
 }
diff --git a/services/common/src/test/java/org/collectionspace/services/common/test/AbstractSecurityTestBase.java b/services/common/src/test/java/org/collectionspace/services/common/test/AbstractSecurityTestBase.java
new file mode 100644 (file)
index 0000000..2fdd5da
--- /dev/null
@@ -0,0 +1,209 @@
+package org.collectionspace.services.common.test;
+
+import java.io.ByteArrayInputStream;
+
+import javax.xml.bind.JAXBException;
+import javax.xml.namespace.QName;
+
+import org.collectionspace.services.common.config.ServicesConfigReaderImpl;
+import org.collectionspace.services.config.ServiceConfig;
+import org.joda.time.DateTime;
+import org.opensaml.core.config.ConfigurationService;
+import org.opensaml.core.config.InitializationException;
+import org.opensaml.core.config.InitializationService;
+import org.opensaml.core.xml.XMLObjectBuilder;
+import org.opensaml.core.xml.XMLObjectBuilderFactory;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
+import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
+import org.opensaml.core.xml.schema.XSAny;
+import org.opensaml.core.xml.schema.XSString;
+import org.opensaml.saml.common.SAMLObject;
+import org.opensaml.saml.common.SAMLVersion;
+import org.opensaml.saml.saml2.core.Assertion;
+import org.opensaml.saml.saml2.core.Attribute;
+import org.opensaml.saml.saml2.core.AttributeStatement;
+import org.opensaml.saml.saml2.core.AttributeValue;
+import org.opensaml.saml.saml2.core.NameID;
+import org.opensaml.saml.saml2.core.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeSuite;
+
+public class AbstractSecurityTestBase {
+       private static final Logger logger = LoggerFactory.getLogger(SecurityUtilsTest.class);
+       protected static String BANNER = "-------------------------------------------------------";
+       protected static String FRIENDLY_ATTR_NAME = "mail";
+       protected static String ATTR_NAME = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
+       protected static String ATTR_NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri";
+       protected static String EMAIL_ADDRESS = "example@example.org";
+       protected static final String USERNAME_ATTRIBUTE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
+       protected static final String SSOID_ATTRIBUTE = "http://schemas.auth0.com/identifier";
+       protected static final String SSO_CONFIG_STRING = createDefaultTestConfig();
+
+       protected static String createDefaultTestConfig() {
+               return createTestConfig(USERNAME_ATTRIBUTE, SSOID_ATTRIBUTE);
+       }
+
+       protected static String createTestConfig(String usernameAttribute, String ssoAttribute) {
+               return new StringBuilder().append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+                               .append("<svc:service-config xmlns:svc='http://collectionspace.org/services/config'>")
+                               .append("<security>").append("<sso>").append("<saml>").append("<single-logout />")
+                               .append("<relying-party-registrations>").append("<relying-party id=\"auth0\">")
+                               .append("<name>Auth0 - Scenario 11</name>")
+                               .append("<icon location=\"https://cdn.auth0.com/manhattan/versions/1.4478.0/assets/badge.png\" />")
+                               .append("<metadata location=\"https://dev-cf0ltyyfory6gtqm.us.auth0.com/samlp/metadata/ZXtZfEN0mj96GP8LCmEUWcpuDO0OtqKY\" />")
+                               .append("<assertion-username-probes>").append("<attribute name=\"" + usernameAttribute + "\" />")
+                               .append("</assertion-username-probes>").append("<assertion-sso-id-probes>")
+                               .append("<attribute name=\"" + ssoAttribute + "\" />").append("</assertion-sso-id-probes>")
+                               .append("</relying-party>").append("</relying-party-registrations>").append("</saml>")
+                               .append("</sso>\n").append("</security>").append("</svc:service-config>").toString();
+       }
+
+       protected static final String MOCK_ROOT_DIR = "./";
+
+       protected ServiceConfig parseServiceConfigString() throws JAXBException {
+               return parseServiceConfigString(MOCK_ROOT_DIR, SSO_CONFIG_STRING);
+       }
+
+       protected ServiceConfig parseServiceConfigString(String mockRootDir, String seviceConfigString)
+                       throws JAXBException {
+               ServicesConfigReaderImpl rdr = new ServicesConfigReaderImpl(mockRootDir);
+               ByteArrayInputStream in = new ByteArrayInputStream(seviceConfigString.getBytes());
+               try {
+                       serviceConfig = (ServiceConfig) rdr.parse(in, ServiceConfig.class);
+               } catch (JAXBException e) {
+                       logger.warn("Could not create test service config: " + e.getLocalizedMessage());
+                       throw e;
+               }
+               return serviceConfig;
+       }
+
+       /* for mocking useful SAML objects */
+       protected <T extends SAMLObject> T createNewSAMLObject(Class<T> clazz)
+                       throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+               XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
+               QName defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null);
+
+               @SuppressWarnings("unchecked") // NOTE: the T extends SAMLObject ought to guarantee this works
+               T theObject = (T) builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName);
+               return theObject;
+       }
+
+       protected XSString createNewXSString(String value) {
+               XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
+               @SuppressWarnings("unchecked")
+               XMLObjectBuilder<XSString> stringBuilder = (XMLObjectBuilder<XSString>) builderFactory
+                               .getBuilder(XSString.TYPE_NAME);
+               XSString theString = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
+               theString.setValue(value);
+               return theString;
+       }
+
+       // NOTE: making the assumption that OpenSAML parses an untyped attribute value
+       // into XSAny with value in the text content
+       protected XSAny createNewXSAny(String value) {
+               XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
+               @SuppressWarnings("unchecked")
+               XMLObjectBuilder<XSAny> stringBuilder = (XMLObjectBuilder<XSAny>) builderFactory.getBuilder(XSAny.TYPE_NAME);
+               XSAny theAny = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME);
+               theAny.setTextContent(value);
+               return theAny;
+       }
+
+       protected Assertion createTestAssertionNoAttributes()
+                       throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+               Assertion testAssertion = createNewSAMLObject(Assertion.class);
+               testAssertion.setVersion(SAMLVersion.VERSION_20);
+               testAssertion.setIssueInstant(new DateTime());
+
+               Subject testSubject = createNewSAMLObject(Subject.class);
+               NameID testNameId = createNewSAMLObject(NameID.class);
+               testNameId.setValue("test subject nameid");
+               testSubject.setNameID(testNameId);
+               testAssertion.setSubject(testSubject);
+
+               return testAssertion;
+       }
+
+       protected Attribute createTestAttribute(boolean hasTypedAttributeValues, String attributeName,
+                       String attributeNameFormat)
+                       throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+               Attribute attr = createNewSAMLObject(Attribute.class);
+               attr.setFriendlyName(FRIENDLY_ATTR_NAME);
+               attr.setName(attributeName);
+               attr.setNameFormat(attributeNameFormat);
+               if (hasTypedAttributeValues) {
+                       XSString attrValue = createNewXSString(EMAIL_ADDRESS);
+                       attr.getAttributeValues().add(attrValue);
+               } else {
+                       XSAny attrValue = createNewXSAny(EMAIL_ADDRESS);
+                       attr.getAttributeValues().add(attrValue);
+               }
+
+               return attr;
+       }
+
+       protected Attribute createDefaultTestAttribute(boolean hasTypedAttributeValues)
+                       throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+               return createTestAttribute(hasTypedAttributeValues, ATTR_NAME, ATTR_NAME_FORMAT);
+       }
+
+       protected Assertion createTestAssertion(Attribute attribute)
+                       throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+               Assertion testAssertion = createTestAssertionNoAttributes();
+
+               AttributeStatement attrStmt = createNewSAMLObject(AttributeStatement.class);
+               attrStmt.getAttributes().add(attribute);
+               testAssertion.getAttributeStatements().add(attrStmt);
+
+               return testAssertion;
+       }
+
+       protected Assertion createTestAssertionTypedAttributeValues()
+                       throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+               return createTestAssertion(createDefaultTestAttribute(true));
+       }
+
+       protected Assertion createTestAssertionUntypedAttributeValues()
+                       throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
+               return createTestAssertion(createDefaultTestAttribute(false));
+       }
+
+       /* test suite setup below */
+       protected Assertion testAssertionTypedAttributeValues = null;
+       protected Assertion testAssertionUntypedAttributeValues = null;
+       protected ServiceConfig serviceConfig = null;
+
+       @BeforeSuite
+       protected void setup() throws InitializationException, NoSuchFieldException, IllegalAccessException, JAXBException {
+               /* try to set up openSAML */
+               XMLObjectProviderRegistry registry = new XMLObjectProviderRegistry();
+               ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
+               try {
+                       InitializationService.initialize();
+               } catch (InitializationException e) {
+                       logger.error("Could not initialize openSAML: " + e.getLocalizedMessage(), e);
+                       throw e;
+               }
+               // try to create a test assertion with typed attribute values; fail the test if
+               // this doesn't work
+               try {
+                       testAssertionTypedAttributeValues = createTestAssertionTypedAttributeValues();
+               } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
+                       logger.error("Could not create test assertion with typed attribute values: " + e.getLocalizedMessage(), e);
+                       throw e;
+               }
+               // try to create a test assertion with untyped attribute values; fail the test
+               // if this doesn't work
+               try {
+                       testAssertionUntypedAttributeValues = createTestAssertionUntypedAttributeValues();
+               } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
+                       logger.error("Could not create test assertion with untyped attribute values: " + e.getLocalizedMessage(),
+                                       e);
+                       throw e;
+               }
+
+               /* try to set up mock config */
+               serviceConfig = parseServiceConfigString();
+       }
+}
index 1677792fa0d521f68e73f97a05c2b56ce03f3b24..22b6fc62b7175c2f4dfa0b7dd122a5dae47f0a09 100644 (file)
 package org.collectionspace.services.common.test;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.BeforeSuite;
-import org.testng.annotations.Test;
-import org.w3c.dom.Element;
-
-import java.util.ArrayList;
 import java.util.Set;
 
-import javax.xml.namespace.QName;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
+import javax.xml.bind.JAXBException;
 
 import org.collectionspace.services.common.security.SecurityUtils;
-import org.collectionspace.services.config.AssertionAttributeProbeType;
 import org.collectionspace.services.config.AssertionProbesType;
-import org.joda.time.DateTime;
-import org.opensaml.core.config.ConfigurationService;
-import org.opensaml.core.config.InitializationException;
-import org.opensaml.core.config.InitializationService;
-import org.opensaml.core.xml.XMLObject;
-import org.opensaml.core.xml.XMLObjectBuilder;
-import org.opensaml.core.xml.XMLObjectBuilderFactory;
-import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
-import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
-import org.opensaml.core.xml.io.Marshaller;
-import org.opensaml.core.xml.io.MarshallingException;
-import org.opensaml.core.xml.schema.XSAny;
-import org.opensaml.core.xml.schema.XSString;
-import org.opensaml.saml.common.SAMLObject;
-import org.opensaml.saml.common.SAMLVersion;
-import org.opensaml.saml.saml2.core.AttributeStatement;
-import org.opensaml.saml.saml2.core.AttributeValue;
-import org.opensaml.saml.saml2.core.NameID;
-import org.opensaml.saml.saml2.core.Subject;
+import org.collectionspace.services.config.SAMLRelyingPartyRegistrationsType;
+import org.collectionspace.services.config.SAMLRelyingPartyType;
+import org.collectionspace.services.config.ServiceConfig;
 import org.opensaml.saml.saml2.core.Assertion;
 import org.opensaml.saml.saml2.core.Attribute;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
-public class SecurityUtilsTest {
+public class SecurityUtilsTest extends AbstractSecurityTestBase {
        private static final Logger logger = LoggerFactory.getLogger(SecurityUtilsTest.class);
-       private static String BANNER = "-------------------------------------------------------";
-       private static String FRIENDLY_ATTR_NAME = "mail";
-       private static String ATTR_NAME = "urn:oid:0.9.2342.19200300.100.1.3";
-       private static String ATTR_NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri";
-       private static String EMAIL_ADDRESS = "example@example.org";
-       private void testBanner(String msg) {      
-        logger.info("\r" + BANNER + "\r\n" + this.getClass().getName() + "\r\n" + msg + "\r\n" + BANNER);
-    }
-       /*
-       private String xml2String(XMLObject xmlObject) {
-               Element element = null;
-               String xmlString = "<uninitialized>";
-        try {
-            Marshaller out = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(xmlObject);
-            out.marshall(xmlObject);
-            element = xmlObject.getDOM();
-
-        } catch (MarshallingException e) {
-            logger.error(e.getMessage(), e);
-            e.printStackTrace();
-        }
-
-        try {
-            Transformer transformer = TransformerFactory.newInstance().newTransformer();
-            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-            StreamResult result = new StreamResult(new java.io.StringWriter());
-            
-            transformer.transform(new DOMSource(element), result);
-            xmlString = result.getWriter().toString();
-        } catch (TransformerConfigurationException e) {
-               logger.error("Transformer configuration exception: " + e.getLocalizedMessage());
-            e.printStackTrace();
-        } catch (TransformerException e) {
-               logger.error("Exception in transformer: " + e.getLocalizedMessage());
-            e.printStackTrace();
-        }
-        
-        return xmlString;
+
+       private void testBanner(String msg) {
+               logger.info("\r" + BANNER + "\r\n" + this.getClass().getName() + "\r\n" + msg + "\r\n" + BANNER);
        }
-       */
-       private <T extends SAMLObject> T createNewSAMLObject(Class<T> clazz) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
-       XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
-       QName defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null);
-       
-       @SuppressWarnings("unchecked") // NOTE: the T extends SAMLObject ought to guarantee this works
-               T theObject = (T) builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName);
-       return theObject;
-    }
-    private XSString createNewXSString(String value) {
-       XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
-       @SuppressWarnings("unchecked")
-               XMLObjectBuilder<XSString> stringBuilder = (XMLObjectBuilder<XSString>) builderFactory.getBuilder(XSString.TYPE_NAME);
-       XSString theString = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
-       theString.setValue(value);
-       return theString;
-    }
-    // NOTE: making the assumption that OpenSAML parses an untyped attribute value into XSAny with value in the text content 
-    private XSAny createNewXSAny(String value) {
-       XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
-       @SuppressWarnings("unchecked")
-               XMLObjectBuilder<XSAny> stringBuilder = (XMLObjectBuilder<XSAny>) builderFactory.getBuilder(XSAny.TYPE_NAME);
-       XSAny theAny = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,XSAny.TYPE_NAME);
-       theAny.setTextContent(value);
-       return theAny;
-    }
-    private Assertion createTestAssertionNoAttributes() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
-       Assertion testAssertion = createNewSAMLObject(Assertion.class);
-               testAssertion.setVersion(SAMLVersion.VERSION_20);
-               testAssertion.setIssueInstant(new DateTime());
-
-               Subject testSubject = createNewSAMLObject(Subject.class);
-               NameID testNameId = createNewSAMLObject(NameID.class);
-               testNameId.setValue("test subject nameid");
-               testSubject.setNameID(testNameId);
-               testAssertion.setSubject(testSubject);
-               
-       return testAssertion;
-    }
-    private Attribute createAttribute(boolean hasTypedAttributeValues) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
-               Attribute attr = createNewSAMLObject(Attribute.class);
-               attr.setFriendlyName(FRIENDLY_ATTR_NAME);
-               attr.setName(ATTR_NAME);
-               attr.setNameFormat(ATTR_NAME_FORMAT);
-               if(hasTypedAttributeValues) {
-                       XSString attrValue = createNewXSString(EMAIL_ADDRESS);
-                       attr.getAttributeValues().add(attrValue);
-               }
-               else {
-                       XSAny attrValue = createNewXSAny(EMAIL_ADDRESS);
-                       attr.getAttributeValues().add(attrValue);
-               }
-               
-               return attr;
-    }
-    private Assertion createTestAssertionTypedAttributeValues() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
-               Assertion testAssertion = createTestAssertionNoAttributes();
-
-               Attribute attr = createAttribute(true);
-               
-               AttributeStatement attrStmt = createNewSAMLObject(AttributeStatement.class);
-               attrStmt.getAttributes().add(attr);
-               testAssertion.getAttributeStatements().add(attrStmt);
-       
-               return testAssertion;
-    }
-    private Assertion createTestAssertionUntypedAttributeValues() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
-               Assertion testAssertion = createTestAssertionNoAttributes();
-
-               Attribute attr = createAttribute(false);
-               
-               AttributeStatement attrStmt = createNewSAMLObject(AttributeStatement.class);
-               attrStmt.getAttributes().add(attr);
-               testAssertion.getAttributeStatements().add(attrStmt);
-       
-               return testAssertion;
-    }
-
-    // the tests are below
-    private Assertion testAssertionTypedAttributeValues = null;
-    private Assertion testAssertionUntypedAttributeValues = null;
-       @BeforeSuite
-    private void setup() throws InitializationException,NoSuchFieldException,IllegalAccessException {
-       // try to set up openSAML
-               XMLObjectProviderRegistry registry = new XMLObjectProviderRegistry();
-               ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
+
+       @Test
+       public void assertionWithTypedAttributeValuesIsNotNull() {
+               testBanner("the mock assertion with typed attribute values is not null");
+               Assert.assertNotNull(testAssertionTypedAttributeValues);
+       }
+
+       @Test
+       public void assertionWithUntypedAttributeValuesIsNotNull() {
+               testBanner("the mock assertion with untyped attribute values is not null");
+               Assert.assertNotNull(testAssertionUntypedAttributeValues);
+       }
+
+       @Test(dependsOnMethods = { "assertionWithTypedAttributeValuesIsNotNull" })
+       public void candidateUsernamesTypedNotNullOrEmpty() {
+               testBanner("findSamlAssertionCandidateUsernames finds candidate usernames when they are typed as string");
+               Set<String> candidateUsernames = SecurityUtils
+                               .findSamlAssertionCandidateUsernames(testAssertionTypedAttributeValues, null);
+               Assert.assertNotNull(candidateUsernames);
+               if (null != candidateUsernames)
+                       Assert.assertFalse(candidateUsernames.isEmpty());
+       }
+
+       @Test(dependsOnMethods = { "assertionWithUntypedAttributeValuesIsNotNull" })
+       public void candidateUsernamesUntypedNotNullOrEmpty() {
+               testBanner("findSamlAssertionCandidateUsernames finds candidate usernames when they are not typed");
+               Set<String> candidateUsernames = SecurityUtils
+                               .findSamlAssertionCandidateUsernames(testAssertionUntypedAttributeValues, null);
+               Assert.assertNotNull(candidateUsernames);
+               if (null != candidateUsernames)
+                       Assert.assertFalse(candidateUsernames.isEmpty());
+       }
+
+       @Test(dependsOnMethods = { "assertionWithUntypedAttributeValuesIsNotNull" })
+       public void candidateUsernamesUntypedIsCorrect() {
+               testBanner("findSamlAssertionCandidateUsernames finds candidate usernames when they are not typed");
+               Set<String> candidateUsernames = SecurityUtils
+                               .findSamlAssertionCandidateUsernames(testAssertionUntypedAttributeValues, null);
+               Assert.assertNotNull(candidateUsernames);
+               if (null != candidateUsernames)
+                       Assert.assertEquals(candidateUsernames.iterator().next(), EMAIL_ADDRESS);
+       }
+
+       @Test(dependsOnMethods = { "assertionWithTypedAttributeValuesIsNotNull" })
+       public void candidateUsernamesTypedIsCorrect() {
+               testBanner("findSamlAssertionCandidateUsernames finds candidate usernames when they are typed as string");
+               Set<String> candidateUsernames = SecurityUtils
+                               .findSamlAssertionCandidateUsernames(testAssertionTypedAttributeValues, null);
+               Assert.assertNotNull(candidateUsernames);
+               if (null != candidateUsernames)
+                       Assert.assertEquals(candidateUsernames.iterator().next(), EMAIL_ADDRESS);
+       }
+
+       @Test
+       public void idenfitiferProbeFindsSsoId() throws JAXBException, IllegalArgumentException, IllegalAccessException,
+                       NoSuchFieldException, SecurityException {
+               testBanner("identifier probe finds sso id");
+
+               String nameFormat = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri";
+               String identifierName = "http://schemas.auth0.com/identifier";
+
+               // set up a minimal mock configuration string with the SSO ID probe we wish to
+               // test
+               String theConfigString = createTestConfig(USERNAME_ATTRIBUTE, identifierName);
+               ServiceConfig theServiceConfig = null;
                try {
-                       InitializationService.initialize();
-               } catch (InitializationException e) {
-                       logger.error("Could not initialize openSAML: " + e.getLocalizedMessage(), e);
+                       theServiceConfig = parseServiceConfigString(MOCK_ROOT_DIR, theConfigString);
+               } catch (JAXBException e) {
+                       logger.warn("Could not create mock service config: " + e.getLocalizedMessage());
                        throw e;
-               }       
-               // try to create a test assertion with typed attribute values; fail the test if this doesn't work
+               }
+               SAMLRelyingPartyRegistrationsType relyingPartyRegistrations = theServiceConfig.getSecurity().getSso().getSaml()
+                               .getRelyingPartyRegistrations();
+               SAMLRelyingPartyType relyingPartyRegistration = relyingPartyRegistrations.getRelyingParty().get(0);
+               AssertionProbesType assertionSsoIdProbes = (relyingPartyRegistration != null
+                               ? relyingPartyRegistration.getAssertionSsoIdProbes()
+                               : null);
+
+               // create an attribute with the same name identifier as the test probe
+               Attribute attribute = null;
                try {
-                       testAssertionTypedAttributeValues = createTestAssertionTypedAttributeValues();
+                       attribute = createTestAttribute(true, identifierName, nameFormat);
                } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
-                       logger.error("Could not create test assertion with typed attribute values: " + e.getLocalizedMessage(), e);
+                       logger.warn("Could not create mock attribute: " + e.getLocalizedMessage());
                        throw e;
                }
-               // try to create a test assertion with untyped attribute values; fail the test if this doesn't work
+               // create a SAML assertion with the attribute
+               Assertion assertion = null;
                try {
-                       testAssertionUntypedAttributeValues = createTestAssertionUntypedAttributeValues();
+                       assertion = createTestAssertion(attribute);
                } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
-                       logger.error("Could not create test assertion with untyped attribute values: " + e.getLocalizedMessage(), e);
+                       logger.warn("Could not create SAML assertion" + e.getLocalizedMessage());
                        throw e;
                }
-    }
-    
-    @Test
-    public void assertionWithTypedAttributeValuesIsNotNull() {
-       testBanner("the mock assertion with typed attribute values is not null");
-       Assert.assertNotNull(testAssertionTypedAttributeValues);
-    }
-    @Test
-    public void assertionWithUntypedAttributeValuesIsNotNull() {
-       testBanner("the mock assertion with untyped attribute values is not null");
-       Assert.assertNotNull(testAssertionUntypedAttributeValues);
-    }
-    @Test(dependsOnMethods = {"assertionWithTypedAttributeValuesIsNotNull"})
-    public void candidateUsernamesTypedNotNullOrEmpty() {
-       testBanner("findSamlAssertionCandidateUsernames finds candidate usernames when they are typed as string");
-       Set<String> candidateUsernames = SecurityUtils.findSamlAssertionCandidateUsernames(testAssertionTypedAttributeValues, null);
-               Assert.assertNotNull(candidateUsernames);
-               if(null != candidateUsernames)
-                       Assert.assertFalse(candidateUsernames.isEmpty());
-    }
-    @Test(dependsOnMethods = {"assertionWithUntypedAttributeValuesIsNotNull"})
-    public void candidateUsernamesUntypedNotNullOrEmpty() {
-       testBanner("findSamlAssertionCandidateUsernames finds candidate usernames when they are not typed");
-       Set<String> candidateUsernames = SecurityUtils.findSamlAssertionCandidateUsernames(testAssertionUntypedAttributeValues, null);
-               Assert.assertNotNull(candidateUsernames);
-               if(null != candidateUsernames)
-                       Assert.assertFalse(candidateUsernames.isEmpty());
-    }
+
+               // check whether getSamlAssertionSsoId finds the SSO ID we put in the assertion
+               // using the test probe
+               String ssoId = SecurityUtils.getSamlAssertionSsoId(assertion, assertionSsoIdProbes);
+               Assert.assertNotNull(ssoId);
+               Assert.assertFalse(ssoId.isEmpty());
+               Assert.assertEquals(ssoId, EMAIL_ADDRESS);
+       }
 }
index 1adf618ee18a65de5b0ed4ff7ae804384794323d..71136d895106352e553f3128c52402226cc6b61b 100644 (file)
             <groupId>javax.xml.bind</groupId>
             <artifactId>jaxb-api</artifactId>
         </dependency>
+        <dependency>
+               <groupId>com.sun.xml.bind</groupId>
+               <artifactId>jaxb-core</artifactId>
+               <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>xerces</groupId>
+                       <artifactId>xercesImpl</artifactId>
+                       <version>2.12.2</version>
+                       <scope>test</scope>
+               </dependency>
                <dependency>
                        <groupId>org.jvnet.jaxb2_commons</groupId>
                        <artifactId>jaxb2-basics</artifactId>
index eff40cbf44d559efc702e281d65df21fd8419bc1..a03e118f3555339d3222635d90ce371d86c5c091 100644 (file)
@@ -121,7 +121,7 @@ public abstract class AbstractConfigReaderImpl<T> implements ConfigReader<T> {
                return getFileChildren(rootDir, true);
        }
 
-       protected Object parse(File configFile, Class<?> clazz)
+       public Object parse(File configFile, Class<?> clazz)
                        throws FileNotFoundException, JAXBException {
                Object result = null;
 
@@ -144,7 +144,7 @@ public abstract class AbstractConfigReaderImpl<T> implements ConfigReader<T> {
         * @throws JAXBException
         * @throws Exception
         */
-       protected Object parse(InputStream configFileStream, Class<?> clazz)
+       public Object parse(InputStream configFileStream, Class<?> clazz)
                        throws JAXBException {
                Object result = null;
 
diff --git a/services/config/src/test/java/org/collectionspace/services/common/config/ServicesConfigReaderImplTest.java b/services/config/src/test/java/org/collectionspace/services/common/config/ServicesConfigReaderImplTest.java
new file mode 100644 (file)
index 0000000..73c612c
--- /dev/null
@@ -0,0 +1,139 @@
+package org.collectionspace.services.common.config;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.JAXBException;
+
+import org.collectionspace.services.config.AssertionAttributeProbeType;
+import org.collectionspace.services.config.SAMLRelyingPartyType;
+import org.collectionspace.services.config.SAMLType;
+import org.collectionspace.services.config.ServiceConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Test;
+
+public class ServicesConfigReaderImplTest {
+       private static final Logger logger = LoggerFactory.getLogger(ServicesConfigReaderImplTest.class);
+       private static String BANNER = "-------------------------------------------------------";
+       // NOTE: adapted from https://collectionspace.atlassian.net/browse/DRYD-1702?focusedCommentId=60649
+       private static final String USERNAME_ATTRIBUTE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
+       private static final String SSOID_ATTRIBUTE = "http://schemas.auth0.com/identifier";
+       private static final String SSO_CONFIG_STRING = new StringBuilder()
+                       .append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
+                       .append("<svc:service-config xmlns:svc='http://collectionspace.org/services/config'>")
+                       .append("<security>")
+                       .append("<sso>")
+                       .append("<saml>")
+                       .append("<single-logout />")
+                       .append("<relying-party-registrations>")
+                       .append("<relying-party id=\"auth0\">")
+                       .append("<name>Auth0 - Scenario 11</name>")
+                       .append("<icon location=\"https://cdn.auth0.com/manhattan/versions/1.4478.0/assets/badge.png\" />")
+                       .append("<metadata location=\"https://dev-cf0ltyyfory6gtqm.us.auth0.com/samlp/metadata/ZXtZfEN0mj96GP8LCmEUWcpuDO0OtqKY\" />")
+                       .append("<assertion-username-probes>")
+                       .append("<attribute name=\"" + USERNAME_ATTRIBUTE + "\" />")
+                       .append("</assertion-username-probes>")
+                       .append("<assertion-sso-id-probes>")
+                       .append("<attribute name=\"" + SSOID_ATTRIBUTE + "\" />")
+                       .append("</assertion-sso-id-probes>")
+                       .append("</relying-party>")
+                       .append("</relying-party-registrations>")
+                       .append("</saml>")
+                       .append("</sso>\n")
+                       .append("</security>")
+                       .append("</svc:service-config>")
+                       .toString();
+       private static final String MOCK_ROOT_DIR = "./";
+       private void testBanner(String msg) {      
+        logger.info("\r" + BANNER + "\r\n" + this.getClass().getName() + "\r\n" + msg + "\r\n" + BANNER);
+    }
+       private List<AssertionAttributeProbeType> getUserNameProbesFromConfig() {
+               return getUserNameProbesFromConfig(serviceConfig);
+       }
+       private List<AssertionAttributeProbeType> getUserNameProbesFromConfig(ServiceConfig serviceConfig) {
+               SAMLType samlConfig = serviceConfig.getSecurity().getSso().getSaml();
+               List<SAMLRelyingPartyType> relyingParties = samlConfig.getRelyingPartyRegistrations().getRelyingParty();
+               SAMLRelyingPartyType relyingParty = relyingParties.get(0);
+               
+               List<Object> usernameProbes = relyingParty.getAssertionUsernameProbes().getNameIdOrAttribute();
+               ArrayList<AssertionAttributeProbeType> up = new ArrayList<AssertionAttributeProbeType>();
+               for (Object obj : usernameProbes) {
+                       AssertionAttributeProbeType a = (AssertionAttributeProbeType) obj;
+                       up.add(a);
+               }
+               
+               return up;
+       }
+       private List<AssertionAttributeProbeType> getSsoIdProbesFromConfig() {
+               return getSsoIdProbesFromConfig(serviceConfig);
+       }
+       private List<AssertionAttributeProbeType> getSsoIdProbesFromConfig(ServiceConfig serviceConfig) {
+               SAMLType samlConfig = serviceConfig.getSecurity().getSso().getSaml();
+               List<SAMLRelyingPartyType> relyingParties = samlConfig.getRelyingPartyRegistrations().getRelyingParty();
+               SAMLRelyingPartyType relyingParty = relyingParties.get(0);
+               
+               List<Object> ssoIdProbes = relyingParty.getAssertionSsoIdProbes().getNameIdOrAttribute();
+               ArrayList<AssertionAttributeProbeType> up = new ArrayList<AssertionAttributeProbeType>();
+               for (Object obj : ssoIdProbes) {
+                       AssertionAttributeProbeType a = (AssertionAttributeProbeType) obj;
+                       up.add(a);
+               }
+               
+               return up;
+       }
+       // the tests are below
+       private ServiceConfig serviceConfig = null;
+       @BeforeSuite
+       public void setup() throws JAXBException {
+               ServicesConfigReaderImpl rdr = new ServicesConfigReaderImpl(MOCK_ROOT_DIR);
+               ByteArrayInputStream in = new ByteArrayInputStream(SSO_CONFIG_STRING.getBytes());
+               try {
+                       serviceConfig = (ServiceConfig) rdr.parse(in, ServiceConfig.class);
+               } catch (JAXBException e) {
+                       logger.warn("Could not create test service config: " + e.getLocalizedMessage());
+                       throw e;
+               }
+       }
+       @Test
+       public void usernameProbesNotNullOrEmpty() {
+               testBanner("the username probes list is not null or empty");
+               
+               List<AssertionAttributeProbeType> usernameProbes = getUserNameProbesFromConfig();
+               Assert.assertNotNull(usernameProbes);
+               if(null != usernameProbes) {
+                       Assert.assertFalse(usernameProbes.isEmpty());
+               }
+       }
+       @Test(dependsOnMethods = {"usernameProbesNotNullOrEmpty"})
+       public void usernameProbesCorrectlyParsedFromConfig() {
+               testBanner("the username probes list has expected contents");
+               
+               List<AssertionAttributeProbeType> usernameProbes = getUserNameProbesFromConfig();
+               Assert.assertEquals(usernameProbes.size(), 1);
+               AssertionAttributeProbeType probe = usernameProbes.get(0);
+               Assert.assertEquals(probe.getName(), USERNAME_ATTRIBUTE);
+       }
+       @Test
+       public void ssoIdProbesNotNullOrEmpty() {
+               testBanner("the SSO ID probes list is not null or empty");
+               
+               List<AssertionAttributeProbeType> ssoIdProbes = getSsoIdProbesFromConfig();
+               Assert.assertNotNull(ssoIdProbes);
+               if(null != ssoIdProbes) {
+                       Assert.assertFalse(ssoIdProbes.isEmpty());
+               }
+       }
+       @Test(dependsOnMethods = {"ssoIdProbesNotNullOrEmpty"})
+       public void ssoIdProbesCorrectlyParsedFromConfig() {
+               testBanner("the SSO ID probes list has expected contents");
+               
+               List<AssertionAttributeProbeType> ssoIdProbes = getSsoIdProbesFromConfig();
+               Assert.assertEquals(ssoIdProbes.size(), 1);
+               AssertionAttributeProbeType probe = ssoIdProbes.get(0);
+               Assert.assertEquals(probe.getName(), SSOID_ATTRIBUTE);
+       }
+}