[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