[java-plugin-shibd-saml] branch main updated: Unit testing.
Scott Cantor
cantor.2 at osu.edu
Wed Sep 18 21:59:52 UTC 2024
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-plugin-shibd-saml.
View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd-saml.git;a=commit;h=64bd80851c2500f085182b3be06d33f0f854880d
The following commit(s) were added to refs/heads/main by this push:
new 64bd808 Unit testing.
64bd808 is described below
commit 64bd80851c2500f085182b3be06d33f0f854880d
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Sep 18 17:59:50 2024 -0400
Unit testing.
---
sp-saml-impl/pom.xml | 20 ++
.../saml2/profile/impl/ExtractSAMLAttributes.java | 25 ++-
.../saml2/profile/impl/PrepareAgentResponse.java | 78 ++++----
.../profile/impl/PrepareAgentResponseTest.java | 217 +++++++++++++++++++++
4 files changed, 292 insertions(+), 48 deletions(-)
diff --git a/sp-saml-impl/pom.xml b/sp-saml-impl/pom.xml
index 69510b0..1e3c31b 100644
--- a/sp-saml-impl/pom.xml
+++ b/sp-saml-impl/pom.xml
@@ -91,6 +91,20 @@
</dependency>
<!-- Test Dependencies -->
+ <dependency>
+ <groupId>${shibd.groupId}</groupId>
+ <artifactId>sp-server-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>${shibd.groupId}</groupId>
+ <artifactId>sp-server-impl</artifactId>
+ <version>${shibd.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
<dependency>
<groupId>${idp.groupId}</groupId>
<artifactId>idp-testing</artifactId>
@@ -103,6 +117,12 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${shib-profile.groupId}</groupId>
+ <artifactId>shib-profile-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+
<dependency>
<groupId>${opensaml.groupId}</groupId>
<artifactId>opensaml-core-impl</artifactId>
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ExtractSAMLAttributes.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ExtractSAMLAttributes.java
index acc6416..cd6df43 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ExtractSAMLAttributes.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/ExtractSAMLAttributes.java
@@ -317,26 +317,25 @@ public class ExtractSAMLAttributes extends AbstractApplicationAction {
final Multimap<String,IdPAttribute> mapped = HashMultimap.create();
assert mapped != null;
+ final Subject subject = samlTokenContext.getSubject();
+ final NameID nameID = subject != null ? subject.getNameID() : null;
+
final AuthnStatement theStatement = samlTokenContext.getAuthnStatement();
assert theStatement != null;
try (final ServiceableComponent<AttributeTranscoderRegistry> component =
ensureApplication().getAttributeTranscoderRegistry().getServiceableComponent()) {
+
+ if (nameID != null) {
+ try {
+ decodeNameID(component.getComponent(), profileRequestContext, nameID, mapped);
+ } catch (final AttributeDecodingException e) {
+ log.error("{} Error decoding inbound NameID", getLogPrefix(), e);
+ }
+ }
+
final Response response = getResponse(profileRequestContext);
for (final Assertion assertion : response.getAssertions()) {
-
- if (assertion == theStatement.getParent()) {
- final Subject subject = assertion.getSubject();
- final NameID nameID = subject != null ? subject.getNameID() : null;
- if (nameID != null) {
- try {
- decodeNameID(component.getComponent(), profileRequestContext, nameID, mapped);
- } catch (final AttributeDecodingException e) {
- log.error("{} Error decoding inbound NameID", getLogPrefix(), e);
- }
- }
- }
-
for (final AttributeStatement statement : assertion.getAttributeStatements()) {
for (final Attribute designator : statement.getAttributes()) {
assert designator!=null;
diff --git a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareAgentResponse.java b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareAgentResponse.java
index 2c6c813..a2f630c 100644
--- a/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareAgentResponse.java
+++ b/sp-saml-impl/src/main/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareAgentResponse.java
@@ -24,16 +24,17 @@ import javax.annotation.Nullable;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.core.xml.util.XMLObjectSupport;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.saml.common.binding.SAMLBindingSupport;
-import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.AuthnStatement;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.Subject;
import org.slf4j.Logger;
import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.codec.Base64Support;
import net.shibboleth.shared.codec.EncodingException;
import net.shibboleth.shared.collection.CollectionSupport;
@@ -55,17 +56,21 @@ import net.shibboleth.sp.saml.saml2.context.SAMLTokenContext;
* is impossible, so null can be returned.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
*/
public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
+
+ /** DOM configuration parameters used by LSSerializer to exclude XML declaration. */
+ @Nonnull private static final Map<String, Object> NO_XML_DECL_PARAMS;
/** Class logger. */
@Nonnull private Logger log = LoggerFactory.getLogger(PrepareAgentResponse.class);
/** Strategy used to look up {@link SAMLTokenContext} to operate on. */
@Nonnull private Function<ProfileRequestContext,SAMLTokenContext> samlTokenContextLookupStrategy;
-
- /** DOM configuration parameters used by LSSerializer to exclude XML declaration. */
- @Nonnull private static final Map<String, Object> NO_XML_DECL_PARAMS;
+
+ /** Context to operate on. */
+ @NonnullBeforeExec private SAMLTokenContext samlTokenContext;
/** Constructor. */
public PrepareAgentResponse() {
@@ -88,6 +93,24 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
Constraint.isNotNull(strategy, "SAMLTokenContext lookup strategy cannot be null");
}
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ samlTokenContext = samlTokenContextLookupStrategy.apply(profileRequestContext);
+ if (samlTokenContext == null) {
+ log.warn("{} No SAMLTokenContext found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
/** {@inheritDoc} */
@Override
@Nullable protected String getStateToken(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -97,43 +120,28 @@ public class PrepareAgentResponse extends AbstractTokenConsumerResponseAction {
/** {@inheritDoc} */
@Override
@Nullable protected String getSessionData(@Nonnull final ProfileRequestContext profileRequestContext) {
- final SAMLTokenContext tokenContext = samlTokenContextLookupStrategy.apply(profileRequestContext);
- if (tokenContext == null) {
- log.warn("{} No SAMLTokenContext found", getLogPrefix());
- return null;
- }
- final AuthnStatement statement = tokenContext.getAuthnStatement();
- if (statement == null) {
- log.warn("{} No AuthnStatement found in SAMLTokenContext", getLogPrefix());
+ final Subject subject = samlTokenContext.getSubject();
+ final NameID nameID = subject != null ? subject.getNameID() : null;
+ if (nameID == null) {
+ log.debug("{} No NameID found in assertion, no session data to attach", getLogPrefix());
return null;
}
-
- if (statement.getParent() instanceof Assertion assertion) {
-
- final Subject subject = assertion.getSubject();
- final NameID nameID = subject != null ? subject.getNameID() : null;
- if (nameID == null) {
- log.debug("{} No NameID found in assertion, nothing to do", getLogPrefix());
- return null;
- }
- final String sessionIndex = statement.getSessionIndex();
- if (sessionIndex != null) {
- // If you tell anyone I did this, I will be sad.
- nameID.setSPProvidedID(sessionIndex);
- }
-
- try {
- final String xml = SerializeSupport.nodeToString(XMLObjectSupport.marshall(nameID), NO_XML_DECL_PARAMS);
- return Base64Support.encodeURLSafe(xml.getBytes(StandardCharsets.UTF_8));
- } catch (final MarshallingException | EncodingException e) {
- log.error("{} Error marshalling and encoding NameID", getLogPrefix(), e);
- return null;
- }
+ final AuthnStatement statement = samlTokenContext.getAuthnStatement();
+ final String sessionIndex = statement != null ? statement.getSessionIndex() : null;
+ if (sessionIndex != null) {
+ // If you tell anyone I did this, I will be sad.
+ nameID.setSPProvidedID(sessionIndex);
+ }
+
+ try {
+ final String xml = SerializeSupport.nodeToString(XMLObjectSupport.marshall(nameID), NO_XML_DECL_PARAMS);
+ return Base64Support.encodeURLSafe(xml.getBytes(StandardCharsets.UTF_8));
+ } catch (final MarshallingException | EncodingException e) {
+ log.error("{} Error marshalling and encoding NameID", getLogPrefix(), e);
}
- log.warn("{} No parent Assertion available via AuthnStatement", getLogPrefix());
return null;
}
diff --git a/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareAgentResponseTest.java b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareAgentResponseTest.java
new file mode 100644
index 0000000..f8d4623
--- /dev/null
+++ b/sp-saml-impl/src/test/java/net/shibboleth/sp/saml/saml2/profile/impl/PrepareAgentResponseTest.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.saml.saml2.profile.impl;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.attribute.ByteAttributeValue;
+import net.shibboleth.idp.attribute.DateTimeAttributeValue;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.ScopedStringAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
+import net.shibboleth.sp.profile.ConsumerConstants;
+import net.shibboleth.sp.profile.impl.BaseAgplicationActionTest;
+import net.shibboleth.sp.saml.saml2.context.SAMLTokenContext;
+
+/**
+ * Unit test for {@link PrepareAgentResponse} action.
+ */
+public class PrepareAgentResponseTest extends BaseAgplicationActionTest {
+
+ /** Test request URL. */
+ @Nonnull @NotEmpty private final static String TEST_URL = "https://sp.example.org/cgi-bin/test.cgi";
+
+ /** Test query string. */
+ @Nonnull @NotEmpty private final static String TEST_QUERY = "foo=bar%20baz&frobnitz=zorkmid";
+
+ private SAMLTokenContext samlTokenContext;
+ private AttributeContext attributeContext;
+ private PrepareAgentResponse action;
+
+ /**
+ * Set up test.
+ *
+ * @throws ComponentInitializationException
+ */
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ super.beforeMethod();
+
+ samlTokenContext = prc.ensureSubcontext(SAMLTokenContext.class);
+ attributeContext = samlTokenContext.ensureSubcontext(AttributeContext.class);
+
+ action = new PrepareAgentResponse();
+ action.initialize();
+ }
+
+ /**
+ * Tear down test.
+ */
+ @AfterMethod
+ public void tearDown() {
+ action.destroy();
+ }
+
+ /**
+ * Unit test when no SAMLTokenContext exists.
+ */
+ @Test
+ public void testNoSAMLContext() {
+ prc.removeSubcontext(samlTokenContext);
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_PROFILE_CTX);
+ }
+
+ /**
+ * Unit test when no data exists, so output is valid but empty.
+ */
+ @Test
+ public void testNoOutput() {
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ final DDF out = arc.getOutput();
+ assert out != null;
+ Assert.assertTrue(out.isstruct());
+ Assert.assertTrue(out.getmember("session").isnull());
+
+ final DDF http = out.getmember(RemotedHttpServletResponse.STRUCTURE_NAME);
+ Assert.assertTrue(http.isstruct());
+ Assert.assertEquals(http.getmember("redirect").unsafe_string(), new byte[] {'/'});
+ Assert.assertTrue(http.getmember("response").isnull());
+ Assert.assertTrue(http.getmember("headers").isnull());
+ }
+
+ /**
+ * Unit test for getting attributes serialized into output.
+ */
+ @Test
+ public void testAttributes() {
+ final Instant ts = Instant.now();
+ buildAttributes(ts);
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(ConsumerConstants.BASE_URL).unsafe_string("/secure/index.html".getBytes(StandardCharsets.UTF_8));
+ arc.setInput(input);
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+
+ final DDF out = arc.getOutput();
+ assert out != null;
+ Assert.assertTrue(out.isstruct());
+
+ final DDF http = out.getmember(RemotedHttpServletResponse.STRUCTURE_NAME);
+ Assert.assertTrue(http.isstruct());
+ Assert.assertEquals(http.getmember("redirect").unsafe_string(), "/secure/index.html".getBytes(StandardCharsets.UTF_8));
+
+ final DDF attrs = out.getmember(ConsumerConstants.SESSION_ATTRIBUTES);
+ Assert.assertTrue(attrs.islist());
+
+ for (final DDF child : attrs.asList()) {
+ final String id = child.name();
+ assert id != null;
+ switch (id) {
+ case "strings":
+ Assert.assertEquals(child.asList().stream().map(DDF::string).toList(),
+ CollectionSupport.listOf("one", "two"));
+ break;
+
+ case "scoped_strings":
+ Assert.assertEquals(child.asList().stream().map(obj -> obj.getmember("value").string()).toList(),
+ CollectionSupport.listOf("one", "two"));
+ Assert.assertEquals(child.asList().stream().map(obj -> obj.getmember("scope").string()).toList(),
+ CollectionSupport.listOf("domain1", "domain2"));
+ break;
+
+ case "times":
+ Assert.assertEquals(child.asList().stream().map(DDF::longinteger).toList(),
+ CollectionSupport.listOf(ts.getEpochSecond(), ts.plusSeconds(3600).getEpochSecond()));
+ break;
+
+ case "bytes":
+ int i = 0;
+ for (final DDF value : child.asList()) {
+ if (i == 0) {
+ Assert.assertEquals(value.unsafe_string(), "one".getBytes(StandardCharsets.UTF_8));
+ } else if (i == 1) {
+ Assert.assertEquals(value.unsafe_string(), "two".getBytes(StandardCharsets.UTF_8));
+ } else {
+ Assert.fail("Too many values");
+ }
+ ++i;
+ }
+ break;
+
+ default:
+ Assert.fail("Unexpected child name");
+ }
+ }
+ }
+
+ /**
+ * Adds mock attributes to context for serializing.
+ *
+ * @param ts timestamp for values
+ */
+ private void buildAttributes(@Nonnull final Instant ts) {
+ final IdPAttribute attr1 = new IdPAttribute("strings");
+ attr1.setValues(CollectionSupport.listOf(
+ StringAttributeValue.valueOf("one"),
+ StringAttributeValue.valueOf(""),
+ StringAttributeValue.valueOf("two")
+ ));
+
+ final IdPAttribute attr2 = new IdPAttribute("scoped_strings");
+ attr2.setValues(CollectionSupport.listOf(
+ ScopedStringAttributeValue.valueOf("one", "domain1"),
+ ScopedStringAttributeValue.valueOf("", "domain"),
+ ScopedStringAttributeValue.valueOf("two", "domain2")
+ ));
+
+ final IdPAttribute attr3 = new IdPAttribute("times");
+ attr3.setValues(CollectionSupport.listOf(
+ new DateTimeAttributeValue(ts),
+ new DateTimeAttributeValue(ts.plusSeconds(3600))
+ ));
+
+ final IdPAttribute attr4= new IdPAttribute("bytes");
+ attr4.setValues(CollectionSupport.listOf(
+ new ByteAttributeValue("one".getBytes(StandardCharsets.UTF_8)),
+ new ByteAttributeValue("two".getBytes(StandardCharsets.UTF_8))
+ ));
+
+ attributeContext.setIdPAttributes(CollectionSupport.listOf(attr1, attr2, attr3, attr4));
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list