[java-idp-plugin-duo] branch dev/JDUO-80 updated: Fix cookie handling bug and add unit tests.
Scott Cantor
cantor.2 at osu.edu
Thu Dec 28 18:09:51 UTC 2023
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch dev/JDUO-80
in repository java-idp-plugin-duo.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=e54382d297b5267e1a91221f3428116f56507850
The following commit(s) were added to refs/heads/dev/JDUO-80 by this push:
new e54382d2 Fix cookie handling bug and add unit tests.
e54382d2 is described below
commit e54382d297b5267e1a91221f3428116f56507850
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Dec 28 13:09:25 2023 -0500
Fix cookie handling bug and add unit tests.
---
.../duo/impl/CheckPasswordlessEnrollment.java | 30 +-
.../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml | 6 +-
.../authn/duo/impl/AbstractDuoActionTest.java | 6 +-
.../duo/impl/CheckPasswordlessEnrollmentTest.java | 321 +++++++++++++++++++++
.../authn/util/mock/TestResourceConverter.java | 127 ++++++++
.../idp/plugin/authn/duo/impl/SealerKeyStore.jks | Bin 0 -> 984 bytes
.../idp/plugin/authn/duo/impl/SealerKeyStore.kver | 1 +
7 files changed, 477 insertions(+), 14 deletions(-)
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java
index 983f5620..62d6728a 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollment.java
@@ -26,8 +26,6 @@ import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
-import com.google.common.net.UrlEscapers;
-
import jakarta.servlet.http.HttpServletRequest;
import net.shibboleth.idp.authn.AbstractExtractionAction;
import net.shibboleth.idp.authn.AuthnEventIds;
@@ -39,6 +37,7 @@ import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.net.CookieManager;
+import net.shibboleth.shared.net.URISupport;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.StringSupport;
import net.shibboleth.shared.security.DataSealer;
@@ -105,7 +104,7 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
new ChildContextLookup<>(DuoPasswordlessContext.class).compose(
new ChildContextLookup<>(AuthenticationContext.class));
- // TODO: Real default once implemented.
+ // TODO: BiPredicateSupport.alwaysFalse once API is bumped.
passwordlessCondition = (a,b) -> { return false; };
usernameFieldName = "j_username";
@@ -216,20 +215,28 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
}
passwordlessContext = duoPasswordlessContextLookupStrategy.apply(profileRequestContext);
- return passwordlessContext != null;
+ if (passwordlessContext == null) {
+ log.debug("{} No DuoPasswordlessContext found, nothing to do", getLogPrefix());
+ return false;
+ }
+ return true;
}
+// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final AuthenticationContext authenticationContext) {
+ boolean usernameChanged = false;
+
String username = getUsernameFromForm(authenticationContext);
if (username != null) {
if (!username.equals(passwordlessContext.getUsername()) ) {
log.debug("{} Populating username '{}' from form submission into Duo passwordless context",
getLogPrefix(), username);
passwordlessContext.setUsername(username);
+ usernameChanged = true;
}
} else {
username = getUsernameFromCookie(profileRequestContext);
@@ -238,6 +245,7 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
log.debug("{} Populating cached username '{}' from cookie into Duo passwordless context",
getLogPrefix(), username);
passwordlessContext.setUsername(username);
+ usernameChanged = true;
}
} else {
username = getUsernameFromSession(profileRequestContext);
@@ -245,6 +253,7 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
log.debug("{} Populating username '{}' from session into Duo passwordless context", getLogPrefix(),
username);
passwordlessContext.setUsername(username);
+ usernameChanged = true;
}
}
}
@@ -255,17 +264,20 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
return;
}
- if (!passwordlessContext.isEnrolled()) {
+ if (usernameChanged) {
passwordlessContext.setEnrolled(
passwordlessCondition.test(profileRequestContext, passwordlessContext.getUsername()));
+ log.debug("{} Username '{}' found to be {} of passwordless attempt", getLogPrefix(),
+ passwordlessContext.getUsername(), passwordlessContext.isEnrolled() ? "capable" : "incapable");
+ } else {
+ log.debug("{} Username not available, leaving DuoPasswordlessContext unchanged", getLogPrefix());
}
- log.debug("{} Username '{}' found to be {} of passwordless attempt", getLogPrefix(),
- passwordlessContext.getUsername(), passwordlessContext.isEnrolled() ? "capable" : "incapable");
if (!passwordlessContext.isEnrolled()) {
ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.REQUEST_UNSUPPORTED);
}
}
+// Checkstyle: CyclomaticComplexity ON
/**
* Gets the username from a form submission.
@@ -304,11 +316,11 @@ public class CheckPasswordlessEnrollment extends AbstractExtractionAction {
@Nullable private String getUsernameFromCookie(@Nonnull final ProfileRequestContext profileRequestContext) {
if (cookieManager != null && dataSealer != null && cookieName != null) {
- final String cookie = cookieManager.getCookieValue(cookieName, null);
+ final String cookie = URISupport.doURLDecode(cookieManager.getCookieValue(cookieName, null));
if (cookie != null) {
try {
assert dataSealer != null;
- return dataSealer.unwrap(UrlEscapers.urlFormParameterEscaper().escape(cookie));
+ return dataSealer.unwrap(cookie);
} catch (final DataSealerException e) {
log.warn("{} Unable to unwrap sealed username cookie", getLogPrefix(), e);
assert cookieName != null;
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
index 46023528..e8ef4825 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
@@ -142,6 +142,9 @@
<bean id="ValidateDuoResponseState" scope="prototype"
class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateDuoResponseState" />
+ <bean id="ExchangeCodeForDuoToken" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.duo.impl.ExchangeCodeForDuoToken" />
+
<bean id="ValidateTokenSignature" scope="prototype"
class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateTokenSignature"
p:signatureAlgorithm="HS512"/>
@@ -238,9 +241,6 @@
<value>iat</value>
</util:set>
- <bean id="ExchangeCodeForDuoToken" scope="prototype"
- class="net.shibboleth.idp.plugin.authn.duo.impl.ExchangeCodeForDuoToken" />
-
<bean id="shibboleth.authn.DuoOIDC.DefaultCleanupHook"
class="net.shibboleth.idp.plugin.authn.duo.DefaultDuoCleanupHook"
p:dataSealer="#{'%{idp.authn.usernameCookieName:}'.trim().isEmpty() ? null : getObject('shibboleth.DataSealer')}"
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java
index 4adf1333..31a220f1 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractDuoActionTest.java
@@ -27,6 +27,7 @@ import javax.annotation.Nullable;
import org.opensaml.profile.context.EventContext;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
@@ -41,6 +42,8 @@ import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
+import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration;
@@ -58,7 +61,6 @@ import net.shibboleth.shared.component.ComponentInitializationException;
*/
public abstract class AbstractDuoActionTest {
-
protected static final String CLIENT_ID = "DIU6GEFWG5LIUBVV2M3P";
protected static final String REDIRECT_URI = "http://localhost/";
@@ -903,4 +905,4 @@ public abstract class AbstractDuoActionTest {
return integ;
}
-}
+}
\ No newline at end of file
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollmentTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollmentTest.java
new file mode 100644
index 00000000..d2fffac3
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/CheckPasswordlessEnrollmentTest.java
@@ -0,0 +1,321 @@
+/*
+ * 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.idp.plugin.authn.duo.impl;
+
+
+import java.time.Instant;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.net.UrlEscapers;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import net.shibboleth.idp.authn.AuthenticationResult;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoPasswordlessContext;
+import net.shibboleth.idp.plugin.authn.util.mock.TestResourceConverter;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.session.IdPSession;
+import net.shibboleth.idp.session.SPSession;
+import net.shibboleth.idp.session.SessionException;
+import net.shibboleth.idp.session.context.SessionContext;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.net.CookieManager;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.DataSealerException;
+import net.shibboleth.shared.security.impl.BasicKeystoreKeyStrategy;
+import net.shibboleth.shared.servlet.impl.HttpServletRequestResponseContext;
+import net.shibboleth.shared.servlet.impl.ThreadLocalHttpServletRequestSupplier;
+import net.shibboleth.shared.servlet.impl.ThreadLocalHttpServletResponseSupplier;
+
+/** {@link CheckPasswordlessEnrollment} unit test. */
+ at SuppressWarnings("javadoc")
+public class CheckPasswordlessEnrollmentTest extends AbstractDuoActionTest {
+
+ @Nonnull @NotEmpty public static final String COOKIE_NAME = "_shib_idp_username";
+
+ private DataSealer dataSealer;
+
+ private CookieManager cookieManager;
+
+ private CheckPasswordlessEnrollment action;
+
+ @BeforeClass public void init() throws ComponentInitializationException {
+
+ final BasicKeystoreKeyStrategy strategy = new BasicKeystoreKeyStrategy();
+
+ strategy.setKeyAlias("secret");
+ strategy.setKeyPassword("kpassword");
+ strategy.setKeystorePassword("password");
+ strategy.setKeystoreResource(TestResourceConverter.of(
+ new ClassPathResource("net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.jks")));
+ strategy.setKeyVersionResource(TestResourceConverter.of(
+ new ClassPathResource("net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.kver")));
+ strategy.initialize();
+
+ dataSealer = new DataSealer();
+ dataSealer.setKeyStrategy(strategy);
+ dataSealer.initialize();
+
+ cookieManager = new CookieManager();
+ cookieManager.setHttpServletRequestSupplier(new ThreadLocalHttpServletRequestSupplier());
+ cookieManager.setHttpServletResponseSupplier(new ThreadLocalHttpServletResponseSupplier());
+ cookieManager.setCookiePath("/");
+ cookieManager.setMaxAge(300);
+ cookieManager.initialize();
+ }
+
+ @BeforeMethod public void setUp() throws ComponentInitializationException {
+ super.setup();
+
+ ac.ensureSubcontext(DuoPasswordlessContext.class);
+
+ HttpServletRequestResponseContext.loadCurrent(new MockHttpServletRequest(), new MockHttpServletResponse());
+
+ action = new CheckPasswordlessEnrollment();
+ action.setPasswordlessCondition((a,b) -> {return true;});
+ action.setDataSealer(dataSealer);
+ action.setCookieManager(cookieManager);
+ action.setCookieName(COOKIE_NAME);
+ action.setHttpServletRequestSupplier(new ThreadLocalHttpServletRequestSupplier());
+ action.setHttpServletResponseSupplier(new ThreadLocalHttpServletResponseSupplier());
+ action.setCheckSession(true);
+ action.initialize();
+ }
+
+ @AfterMethod public void tearDown() {
+ HttpServletRequestResponseContext.clearCurrent();
+ }
+
+ @Test public void testNoServlet() throws ComponentInitializationException {
+ action = new CheckPasswordlessEnrollment();
+ action.initialize();
+ final Event event = action.execute(src);
+
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.UNKNOWN_USERNAME);
+ }
+
+ @Test public void testMissingIdentity() {
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.UNKNOWN_USERNAME);
+ }
+
+ @Test public void testUnchangedIdentity() {
+ ac.ensureSubcontext(DuoPasswordlessContext.class).setUsername("bar");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.REQUEST_UNSUPPORTED);
+ }
+
+ @Test public void testUnchangedIdentityEnrolled() {
+ ac.ensureSubcontext(DuoPasswordlessContext.class).setUsername("bar").setEnrolled(true);
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ }
+
+ @Test public void testFromForm() {
+ ensureMockRequest().addParameter("j_username", "foo");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ final AuthenticationContext authCtx = prc.ensureSubcontext(AuthenticationContext.class);
+ Assert.assertTrue(authCtx.isResultCacheable());
+ final DuoPasswordlessContext duoCtx = authCtx.ensureSubcontext(DuoPasswordlessContext.class);
+ Assert.assertEquals(duoCtx.getUsername(), "foo");
+ Assert.assertTrue(duoCtx.isEnrolled());
+ }
+
+ @Test public void testSSOBypass() {
+ ensureMockRequest().addParameter("j_username", "foo");
+ ensureMockRequest().addParameter("donotcache", "1");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ final AuthenticationContext authCtx = prc.ensureSubcontext(AuthenticationContext.class);
+ Assert.assertFalse(authCtx.isResultCacheable());
+ final DuoPasswordlessContext duoCtx = authCtx.ensureSubcontext(DuoPasswordlessContext.class);
+ Assert.assertEquals(duoCtx.getUsername(), "foo");
+ Assert.assertTrue(duoCtx.isEnrolled());
+ }
+
+ @Test public void testFromCookie() throws DataSealerException {
+ // Wrong field name.
+ ensureMockRequest().addParameter("username", "foo");
+
+ final String wrapped = dataSealer.wrap("foo");
+ final Cookie cookie = new Cookie(COOKIE_NAME, UrlEscapers.urlFormParameterEscaper().escape(wrapped));
+ ensureMockRequest().setCookies(cookie);
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ final AuthenticationContext authCtx = prc.ensureSubcontext(AuthenticationContext.class);
+ Assert.assertTrue(authCtx.isResultCacheable());
+ final DuoPasswordlessContext duoCtx = authCtx.ensureSubcontext(DuoPasswordlessContext.class);
+ Assert.assertEquals(duoCtx.getUsername(), "foo");
+ Assert.assertTrue(duoCtx.isEnrolled());
+ }
+
+ @Test public void testFromSession() throws DataSealerException {
+ // Wrong field name.
+ ensureMockRequest().addParameter("username", "foo");
+
+ // Wrong cookie name.
+ final String wrapped = dataSealer.wrap("foo");
+ final Cookie cookie = new Cookie(COOKIE_NAME + "1", UrlEscapers.urlFormParameterEscaper().escape(wrapped));
+ ensureMockRequest().setCookies(cookie);
+
+ prc.ensureSubcontext(SessionContext.class).setIdPSession(new MockIdPSession());
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ final AuthenticationContext authCtx = prc.ensureSubcontext(AuthenticationContext.class);
+ Assert.assertTrue(authCtx.isResultCacheable());
+ final DuoPasswordlessContext duoCtx = authCtx.ensureSubcontext(DuoPasswordlessContext.class);
+ Assert.assertEquals(duoCtx.getUsername(), "foo");
+ Assert.assertTrue(duoCtx.isEnrolled());
+ }
+
+ @Nonnull private MockHttpServletRequest ensureMockRequest() {
+ final HttpServletRequest request = HttpServletRequestResponseContext.getRequest();
+ return MockHttpServletRequest.class.cast(request);
+ }
+
+ @Nonnull private MockHttpServletResponse ensureMockResponse() {
+ final HttpServletResponse request = HttpServletRequestResponseContext.getResponse();
+ return MockHttpServletResponse.class.cast(request);
+ }
+
+ private class MockIdPSession implements IdPSession {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public String getId() {
+ return "id";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public String getPrincipalName() {
+ return "foo";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Instant getCreationInstant() {
+ return Instant.now();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Instant getLastActivityInstant() {
+ return Instant.now();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean checkAddress(@Nonnull String address) throws SessionException {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean checkTimeout() throws SessionException {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Set<AuthenticationResult> getAuthenticationResults() {
+ return CollectionSupport.emptySet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public AuthenticationResult getAuthenticationResult(@Nonnull String flowId) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public AuthenticationResult addAuthenticationResult(@Nonnull AuthenticationResult result)
+ throws SessionException {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void updateAuthenticationResultActivity(@Nonnull AuthenticationResult result) throws SessionException {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean removeAuthenticationResult(@Nonnull AuthenticationResult result) throws SessionException {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ public Set<SPSession> getSPSessions() {
+ return CollectionSupport.emptySet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public SPSession getSPSession(@Nonnull String serviceId) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public SPSession addSPSession(@Nonnull SPSession spSession) throws SessionException {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean removeSPSession(@Nonnull SPSession spSession) throws SessionException {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/util/mock/TestResourceConverter.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/util/mock/TestResourceConverter.java
new file mode 100644
index 00000000..8863c0b4
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/util/mock/TestResourceConverter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.idp.plugin.authn.util.mock;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+
+import javax.annotation.Nonnull;
+
+import org.springframework.core.io.Resource;
+
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Bridging class between {@link Resource} and {@link net.shibboleth.shared.resource.Resource}.
+ */
+public final class TestResourceConverter implements net.shibboleth.shared.resource.Resource {
+
+ /** The cached Spring {@link Resource}. */
+ private Resource springResource;
+
+ /**
+ * A private for shimming the provided input.
+ *
+ * @param theResource the spring resource;
+ */
+ private TestResourceConverter(@Nonnull Resource theResource) {
+
+ springResource = Constraint.isNotNull(theResource, "provided Spring Resource should not be null");
+ }
+
+ /**
+ * Return a {@link Resource} that does all the work of the provided {@link Resource}.
+ *
+ * <p>
+ * If the input implements {@link Resource} then it is cast to the output, other a shim class is
+ * generated.
+ * </p>
+ *
+ * @param springResource the input
+ * @return a {@link Resource} which reflects what the Spring one does
+ */
+ @Nonnull public static net.shibboleth.shared.resource.Resource of(@Nonnull Resource springResource) {
+ if (springResource instanceof net.shibboleth.shared.resource.Resource) {
+ return (net.shibboleth.shared.resource.Resource) springResource;
+ }
+ return new TestResourceConverter(springResource);
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public InputStream getInputStream() throws IOException {
+ return springResource.getInputStream();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean exists() {
+ return springResource.exists();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isReadable() {
+ return springResource.isReadable();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isOpen() {
+ return springResource.isOpen();
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public URL getURL() throws IOException {
+ return springResource.getURL();
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public URI getURI() throws IOException {
+ return springResource.getURI();
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public File getFile() throws IOException {
+ return springResource.getFile();
+ }
+
+ /** {@inheritDoc} */
+ @Override public long contentLength() throws IOException {
+ return springResource.contentLength();
+ }
+
+ /** {@inheritDoc} */
+ @Override public long lastModified() throws IOException {
+ return springResource.lastModified();
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public net.shibboleth.shared.resource.Resource createRelativeResource(
+ @Nonnull String relativePath) throws IOException {
+
+ return of(springResource.createRelative(relativePath));
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getFilename() {
+ return springResource.getFilename();
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull public String getDescription() {
+ return springResource.getDescription();
+ }
+
+}
diff --git a/idp-duo-impl/src/test/resources/net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.jks b/idp-duo-impl/src/test/resources/net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.jks
new file mode 100644
index 00000000..147d92bb
Binary files /dev/null and b/idp-duo-impl/src/test/resources/net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.jks differ
diff --git a/idp-duo-impl/src/test/resources/net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.kver b/idp-duo-impl/src/test/resources/net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.kver
new file mode 100644
index 00000000..2cd48df3
--- /dev/null
+++ b/idp-duo-impl/src/test/resources/net/shibboleth/idp/plugin/authn/duo/impl/SealerKeyStore.kver
@@ -0,0 +1 @@
+CurrentVersion = 1
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list