[java-idp-plugin-duo] 10/16: Add Nimbus SDK tests

Phil Smart philip.smart at jisc.ac.uk
Fri Oct 2 10:41:01 UTC 2020


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-idp-plugin-duo.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=fcef06ad48974aa38d0f2a9a3fc55c7de300a937

commit fcef06ad48974aa38d0f2a9a3fc55c7de300a937
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Sep 28 14:51:27 2020 +0100

    Add Nimbus SDK tests
---
 .../idp/plugin/authn/duo/model/DuoHealthCheck.java |   1 +
 .../duo/sdk/impl/DuoSDKClientFactoryTest.java      |  34 +++
 .../idp/plugin/authn/duo/nimbus/NimbusClient.java  |  39 ++-
 .../idp/plugin/authn/duo/nimbus/TokenResponse.java |   1 +
 .../authn/duo/nimbus/NimbusClientFactoryTest.java  |  94 ++++++
 .../plugin/authn/duo/nimbus/NimbusClientTest.java  | 319 +++++++++++++++++----
 6 files changed, 429 insertions(+), 59 deletions(-)

diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/model/DuoHealthCheck.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/model/DuoHealthCheck.java
index b489552..7a39e89 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/model/DuoHealthCheck.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/model/DuoHealthCheck.java
@@ -156,6 +156,7 @@ public final class DuoHealthCheck {
      * Builder to build {@link DuoHealthCheck}.
      */   
     @JsonPOJOBuilder(buildMethodName = "build",withPrefix = "with")
+    @JsonIgnoreProperties(ignoreUnknown = true)
     public static final class Builder implements IStatusStage, IBuildStage {
         private String status;
 
diff --git a/idp-duo-native-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java b/idp-duo-native-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java
index c196df4..ba6ef51 100644
--- a/idp-duo-native-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java
+++ b/idp-duo-native-client-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/sdk/impl/DuoSDKClientFactoryTest.java
@@ -1,3 +1,37 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.
+ */
+
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.sdk.impl;
 
 import static org.testng.Assert.assertNotNull;
diff --git a/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClient.java b/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClient.java
index 78f0b3a..ca40079 100644
--- a/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClient.java
+++ b/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClient.java
@@ -1,3 +1,37 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.
+ */
+
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.nimbus;
 
 import java.io.IOException;
@@ -20,6 +54,7 @@ import org.opensaml.security.httpclient.HttpClientSecuritySupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.JWSObject;
@@ -35,7 +70,7 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
- * A Duo client using the Nimbus OIDC library. Can only be instantiated by the corresponding factory method.
+ * A Duo client using the Nimbus OIDC library. Can/should only be instantiated by the corresponding factory method.
  */
 final class NimbusClient implements DuoOIDCClient{
 
@@ -158,7 +193,7 @@ final class NimbusClient implements DuoOIDCClient{
             
             final TokenResponse response = executeRequest(rb.build(),new TypeReference<TokenResponse>() {});
             log.trace("Duo token response: '{}'",response);
-            //Accepts only a JWS, not a JWE
+            //Accepts only a JWS not a JWE from Duo - method can return any.
             return SignedJWT.parse(response.getIdToken());            
             
         } catch (final URISyntaxException | ParseException e) {
diff --git a/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/TokenResponse.java b/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/TokenResponse.java
index 755e91a..c14199a 100644
--- a/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/TokenResponse.java
+++ b/idp-plugin-duo-nimbus-client/src/main/java/net/shibboleth/idp/plugin/authn/duo/nimbus/TokenResponse.java
@@ -144,6 +144,7 @@ public final class TokenResponse {
      * Builder to build {@link TokenResponse}.
      */  
     @JsonPOJOBuilder(buildMethodName = "build",withPrefix = "with")
+    @JsonIgnoreProperties(ignoreUnknown = true)
     public static final class Builder implements IIdTokenStage, IAccessTokenStage, ITokenTypeStage, IBuildStage {
         private String idToken;
 
diff --git a/idp-plugin-duo-nimbus-client/src/test/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClientFactoryTest.java b/idp-plugin-duo-nimbus-client/src/test/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClientFactoryTest.java
new file mode 100644
index 0000000..da27ab0
--- /dev/null
+++ b/idp-plugin-duo-nimbus-client/src/test/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClientFactoryTest.java
@@ -0,0 +1,94 @@
+
+package net.shibboleth.idp.plugin.authn.duo.nimbus;
+
+import static org.testng.Assert.assertNotNull;
+
+import javax.annotation.Nonnull;
+
+import org.apache.http.client.HttpClient;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import net.shibboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration;
+import net.shibboleth.idp.plugin.authn.duo.DuoClientException;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCClient;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+
+/** Test for the NimbusClientFactory class. */
+public class NimbusClientFactoryTest {
+
+    /** The factory. */
+    @Nonnull
+    private NimbusClientFactory factory;
+
+    /** Setup.*/
+    @BeforeMethod
+    public void setup() {
+        factory = new NimbusClientFactory();
+    }
+
+    /** 
+     * Test instance creation. 
+     * 
+     * @throws DuoClientException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test
+    public final void testCreateInstance() throws DuoClientException, ComponentInitializationException {
+
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        factory.setHttpClient(httpClient);
+        factory.setObjectMapper(new ObjectMapper());
+        factory.doInitialize();
+
+        final DefaultDuoOIDCIntegration integ = new DefaultDuoOIDCIntegration();
+        integ.setAPIHost("host.com");
+        integ.setClientId("DIU6GEFWG5LIUBVV2M3P");
+        integ.setRedirectURI("http://localhost/");
+        integ.setSecretKey("rFvDfPul27v3Wew2zb6xRPzAJewJ34MP2w8UitPh");
+        final DuoOIDCClient client = factory.createInstance(integ);
+        assertNotNull(client);
+    }
+
+    /** 
+     * Test initialization failure if no http client set. 
+     * 
+     * @throws DuoClientException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public final void testNoHttpClient() throws DuoClientException, ComponentInitializationException {
+        factory.setObjectMapper(new ObjectMapper());
+        factory.doInitialize();
+    }
+
+    /** 
+     * Test initialization failure if no object mapper set. 
+     * 
+     * @throws DuoClientException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public final void testNoObjectMapper() throws DuoClientException, ComponentInitializationException {
+        factory.setHttpClient(Mockito.mock(HttpClient.class));
+        factory.doInitialize();
+    }
+
+    /** 
+     * Test initialization success. 
+     * 
+     * @throws DuoClientException on error.
+     * @throws ComponentInitializationException on error.
+     */
+    @Test
+    public final void testInitializeSuccess() throws DuoClientException, ComponentInitializationException {
+        factory.setHttpClient(Mockito.mock(HttpClient.class));
+        factory.setObjectMapper(new ObjectMapper());
+        factory.doInitialize();
+    }
+
+}
diff --git a/idp-plugin-duo-nimbus-client/src/test/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClientTest.java b/idp-plugin-duo-nimbus-client/src/test/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClientTest.java
index 8682871..46e2cf6 100644
--- a/idp-plugin-duo-nimbus-client/src/test/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClientTest.java
+++ b/idp-plugin-duo-nimbus-client/src/test/java/net/shibboleth/idp/plugin/authn/duo/nimbus/NimbusClientTest.java
@@ -1,16 +1,50 @@
 
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.
+ */
+
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.nimbus;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.text.SimpleDateFormat;
-import java.time.Duration;
 
 import javax.annotation.Nonnull;
 
-import org.apache.http.client.ClientProtocolException;
+
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.entity.StringEntity;
@@ -21,58 +55,94 @@ import org.mockito.Mockito;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+
 import com.nimbusds.jwt.JWT;
 
-import net.shibboleth.ext.spring.factory.HttpClientFactoryBean;
+
 import net.shibboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration;
 import net.shibboleth.idp.plugin.authn.duo.DuoClientException;
 import net.shibboleth.idp.plugin.authn.duo.model.DuoHealthCheck;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 
+/** Tests for the NimbusClient class.*/
 public class NimbusClientTest {
 
-    @Nonnull
-    private NimbusClient client;
-
-    @Nonnull
-    private HttpClient httpClient;
+    /** The Nimbus client to test.*/
+    @Nonnull private NimbusClient client;
 
-    @Nonnull
-    private DefaultDuoOIDCIntegration integ;
+    /** The default OIDC integration params.*/
+    @Nonnull private DefaultDuoOIDCIntegration integ;
 
-    @Nonnull
-    @NotEmpty
+    /** Real but valid OAuth JSON token response that has an INVALID signature.*/
+    @Nonnull @NotEmpty
     private final String ID_TOKEN_RESPONSE = "{\n" + "   \"access_token\": \"SlAV32hkKG\",\n"
-            + "   \"token_type\": \"Bearer\",\n" + "   \"refresh_token\": \"8xLOxBtZp8\",\n"
+            + "   \"token_type\": \"Bearer\",\n" + "   \"refresh_token\": \"8xLOxBtZp9\",\n"
             + "   \"expires_in\": 3600,\n" + "   \"id_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9."
-            + "eyJpYXQiOjE2MDA5NTUxNDgsImV4cCI6MTYwMDk1ODc0OCwiYXV0aF90aW1lIjoxNjAwOTU1MTQ4"
-            + "LCJhdXRoX2NvbnRleHQiOnsiZW1haWwiOiJwaGlsaXAuc21hcnRAamlzYy5hYy51ayIsImFwcGxp"
-            + "Y2F0aW9uIjp7ImtleSI6IkRJVTZHRUZXRzVMSVVUVlYyTTNOIiwibmFtZSI6IlNoaWJib2xldGggS"
-            + "W50ZWdyYXRpb24gVGVzdGluZyJ9LCJhY2Nlc3NfZGV2aWNlIjp7InNlY3VyaXR5X2FnZW50cyI6In"
-            + "Vua25vd24iLCJpc19lbmNyeXB0aW9uX2VuYWJsZWQiOiJ1bmtub3duIiwibG9jYXRpb24iOnsiY2l"
-            + "0eSI6IkNhcmRpZmYiLCJjb3VudHJ5IjoiVW5pdGVkIEtpbmdkb20iLCJzdGF0ZSI6IldhbGVzIn0s"
-            + "Im9zIjoiTWFjIE9TIFgiLCJpc19wYXNzd29yZF9zZXQiOiJ1bmtub3duIiwiZmxhc2hfdmVyc2lvb"
-            + "iI6InVuaW5zdGFsbGVkIiwib3NfdmVyc2lvbiI6IjEwLjE1IiwiYnJvd3Nlcl92ZXJzaW9uIjoiODA"
-            + "uMCIsImphdmFfdmVyc2lvbiI6InVuaW5zdGFsbGVkIiwiaG9zdG5hbWUiOm51bGwsImlzX2ZpcmV3"
-            + "YWxsX2VuYWJsZWQiOiJ1bmtub3duIiwiaXAiOiI4Mi4xNy44OS4yMzIiLCJicm93c2VyIjoiRmlyZ"
-            + "WZveCJ9LCJ0aW1lc3RhbXAiOjE2MDA5NTUxNDgsImZhY3RvciI6ImR1b19wdXNoIiwiaXNvdGltZXN"
-            + "0YW1wIjoiMjAyMC0wOS0yNFQxMzo0NTo0OC4wMjg2ODgrMDA6MDAiLCJ0cnVzdGVkX2VuZHBvaW50"
-            + "X3N0YXR1cyI6InVua25vd24iLCJ1c2VyIjp7Imdyb3VwcyI6W10sImtleSI6IkRVR0w4VTQ2UUdKU"
-            + "09VSldHNTlXIiwibmFtZSI6InBoaWxzbWFydCJ9LCJvb2Rfc29mdHdhcmUiOm51bGwsInR4aWQiOi"
-            + "I4NzAwMDhmZi1iMmVjLTRjNGItYmY3Yi01ZWU2OWVhMDMwM2UiLCJyZXN1bHQiOiJzdWNjZXNzIiw"
-            + "iZXZlbnRfdHlwZSI6ImF1dGhlbnRpY2F0aW9uIiwiYXV0aF9kZXZpY2UiOnsibG9jYXRpb24iOnsi"
-            + "Y2l0eSI6IkNhcmRpZmYiLCJjb3VudHJ5IjoiVW5pdGVkIEtpbmdkb20iLCJzdGF0ZSI6IldhbGVzI"
-            + "n0sIm5hbWUiOiIrNDQgNzg1MiAxMTk4ODEiLCJpcCI6IjgyLjE3Ljg5LjIzMiJ9LCJhbGlhcyI6Ii"
-            + "IsInJlYXNvbiI6InVzZXJfYXBwcm92ZWQifSwiYXVkIjoiRElVNkdFRldHNUxJVVRWVjJNM04iLCJ"
-            + "hdXRoX3Jlc3VsdCI6eyJzdGF0dXNfbXNnIjoiTG9naW4gU3VjY2Vzc2Z1bCIsInN0YXR1cyI6ImFs"
-            + "bG93IiwicmVzdWx0IjoiYWxsb3cifSwicHJlZmVycmVkX3VzZXJuYW1lIjoicGhpbHNtYXJ0Iiwia"
-            + "XNzIjoiaHR0cHM6Ly9hcGktYzlmMjRjNWEuZHVvc2VjdXJpdHkuY29tL29hdXRoL3YxL3Rva2VuIi"
-            + "wic3ViIjoicGhpbHNtYXJ0In0.8-WYizEC_i1T4wXf1nh7f0RY4XmLp7bg7pq_-JRPeme40LwrPcr"
-            + "dQ9f2TtnBpiKjsD8MEkWYbthUurlyFWe5iQ\"\n" + "  }";
+                    + "eyJpc3MiOiJodHRwczovL2FwaS1jOWYyNGM1YS5kdW9zZWN1cml0eS5jb20vb2F1dGgvdjEvdG9rZW"
+                    + "4iLCJzdWIiOiJqZG9lIiwiYXV0aF9jb250ZXh0Ijp7ImZhY3RvciI6ImR1b19wdXNoIiwicmVhc29u"
+                    + "IjoidXNlcl9hcHByb3ZlZCIsInVzZXIiOnsia2V5IjoiRlU1TDhQNDZRR0dTT1VKV0c1OVkiLCJuYW"
+                    + "1lIjoiamRvZSIsImdyb3VwcyI6W119LCJ0eGlkIjoiNzAxMDE0MDQtZDM1Yi00OTYxLTk5OGYtZjY3"
+                    + "ZmQ1NTQxZDg3IiwiZW1haWwiOiJqZG9lQGV4YW1wbGUuY29tIiwiZXZlbnRfdHlwZSI6ImF1dGhlbn"
+                    + "RpY2F0aW9uIiwiYXBwbGljYXRpb24iOnsia2V5IjoiREZRNkdFUldHNUxJVUlWVjJNNFAiLCJuYW1l"
+                    + "IjoiU2hpYmJvbGV0aCBJbnRlZ3JhdGlvbiBUZXN0aW5nIn0sIm9vZF9zb2Z0d2FyZSI6bnVsbCwicm"
+                    + "VzdWx0Ijoic3VjY2VzcyIsImF1dGhfZGV2aWNlIjp7ImlwIjoiODIuMTcuODkuMjMyIiwibmFtZSI6"
+                    + "Iis0NCA3ODUyIDExOTg4MSIsImxvY2F0aW9uIjp7ImNvdW50cnkiOiJVbml0ZWQgS2luZ2RvbSIsIn"
+                    + "N0YXRlIjoiV2FsZXMiLCJjaXR5IjoiQ2FyZGlmZiJ9fSwiYWNjZXNzX2RldmljZSI6eyJob3N0bmFt"
+                    + "ZSI6bnVsbCwiaXAiOiI4Mi4xNy44OS4yMzIiLCJsb2NhdGlvbiI6eyJjb3VudHJ5IjoiVW5pdGVkIE"
+                    + "tpbmdkb20iLCJzdGF0ZSI6IldhbGVzIiwiY2l0eSI6IkNhcmRpZmYifX0sImlzb3RpbWVzdGFtcCI6"
+                    + "IjIwMjAtMDctMDdUMTY6MDg6MzAuNjA3MDc1KzAwOjAwIiwiYWxpYXMiOiIiLCJ0aW1lc3RhbXAiOj"
+                    + "E1OTQxMzgxMTB9LCJhdWQiOiJERlE2R0VSV0c1TElVSVZWMk00UCIsImF1dGhfcmVzdWx0Ijp7InN0"
+                    + "YXR1c19tc2ciOiJMb2dpbiBTdWNjZXNzZnVsIiwic3RhdHVzIjoiYWxsb3ciLCJyZXN1bHQiOiJhbG"
+                    + "xvdyJ9LCJpYXQiOjE1OTQxMzgxMTEsImV4cCI6MTU5NDE0MTcxMCwiYXV0aF90aW1lIjoxNTk0MTM4"
+                    + "MTEwfQ."
+                    + "K2PM8maUONOhMbRBgsVY8ty0mpQ6tUJQsr23vdqTWuH3PXRVyKtNfUeypm5d-OAJwka-epOUMbNmWZ"
+                    + "xcNYK5dQ\"\n" + "  }";
 
+    /** Real token response error message.*/
     @Nonnull @NotEmpty private final String ID_TOKEN_RESPONSE_ERROR ="\"{\"error\": \"invalid_grant\","
-            + " \"error_description\": \"The provided authorization grant (e.g., authorization code) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.\"}";
+            + " \"error_description\": \"The provided authorization grant (e.g., authorization code) "
+            + "or refresh token is invalid, expired, revoked, does not match the redirection URI used "
+            + "in the authorization request, or was issued to another client.\"}";
+    
+    /** A null token response, to mock an internal server error e.g. non-OK http status response.*/
+    @Nonnull @NotEmpty private final String ID_TOKEN_REPONSE_NULL ="";
+    
+    /** A normal health check response.*/
+    @Nonnull @NotEmpty private final String HEALTH_CHECK_REPONSE ="{\n" + 
+            "  \"stat\" : \"OK\",\n" + 
+            "  \"response\" : {\n" + 
+            "    \"timestamp\" : 1601288503\n" + 
+            "  },\n" + 
+            "  \"code\" : null,\n" + 
+            "  \"timestamp\" : null,\n" + 
+            "  \"message\" : null,\n" + 
+            "  \"message_detail\" : null\n" + 
+            "}";
+    
+    
+    
+    /** A normal health check response.*/
+    @Nonnull @NotEmpty private final String HEALTH_CHECK_REPONSE_ERROR ="{\n" + 
+            "  \"stat\" : \"FAIL\",\n" + 
+            "  \"response\" : null,\n" + 
+            "  \"code\" : 40002,\n" + 
+            "  \"timestamp\" : 1601290156,\n" + 
+            "  \"message\" : \"invalid_client\",\n" + 
+            "  \"message_detail\" : \"The provided client_assertion was invalid.\"\n" + 
+            "}";
+    
+    /** A health check response with more fields than in the model.*/
+    @Nonnull @NotEmpty private final String HEALTH_CHECK_REPONSE_MORE_FIELDS ="{\n" + 
+            "  \"stat\" : \"OK\",\n" + 
+            "  \"response\" : {\n" + 
+            "    \"timestamp\" : 1601288503\n" + 
+            "  },\n" + 
+            "  \"code\" : null,\n" + 
+            "  \"timestamp\" : null,\n" + 
+            "  \"message\" : null,\n" + 
+            "  \"new_field\" : null,\n" + 
+            "  \"message_detail\" : null\n" + 
+            "}";
 
     @BeforeMethod
     public void setup() throws Exception {
@@ -85,24 +155,19 @@ public class NimbusClientTest {
         integ.setTokenEndpoint("/oauth/v1/token");
         integ.setHealthCheckEndpoint("/oauth/v1/health_check");
 
-        final HttpClientFactoryBean factory = new HttpClientFactoryBean();
-        factory.setConnectionDisregardTLSCertificate(false);
-        factory.setConnectionTimeout(Duration.ofMinutes(1));
-        factory.setSocketTimeout(Duration.ofMinutes(1));
-        factory.setMaxConnectionsTotal(100);
-        factory.setMaxConnectionsPerRoute(100);
-        // TODO set the TLS socket factory?
-        httpClient = factory.buildClient();
-
-        final ObjectMapper mapper = new ObjectMapper();
-        mapper.setDateFormat(new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss.SSSZZ"));
-        client = new NimbusClient(integ, httpClient, null);
-        client.setObjectMapper(mapper);
-
     }
 
+
+    /** 
+     * Test the token exchange works using a mocked HTTP client and response.
+     * Does not check the signature is valid. Given the amount of mocking, is not
+     * a very thorough test.
+     * 
+     * @throws Exception on error.
+     */
     @Test
-    public void testTokenExchange() throws DuoClientException, ClientProtocolException, IOException {
+    public void testTokenExchange() throws Exception {
+
         final HttpClient httpClient = Mockito.mock(HttpClient.class);
         final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
         final StatusLine statusLine = Mockito.mock(StatusLine.class);
@@ -118,8 +183,148 @@ public class NimbusClientTest {
         client = new NimbusClient(integ, httpClient, null);
 
         final JWT jwt = client.exchangeAuthorizationCodeFor2FAResult("code", "jdoe");
-        System.out.println("id_token: " + jwt);
+        assertNotNull(jwt);
+        assertEquals(jwt.getJWTClaimsSet().getSubject(),"jdoe");
+
+    }
+    
+    /**
+     * Test an exception is thrown if a non-OK http response is returned. There is
+     * no known response body, so an empty one is used.
+     * 
+     * @throws Exception on error.
+     */
+    @Test(expectedExceptions = DuoClientException.class)
+    public void testTokenExchangeInternalServerErrorResponse() throws Exception {
+        
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(500);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(ID_TOKEN_REPONSE_NULL));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+
+        // create new client with mock response
+        client = new NimbusClient(integ, httpClient, null);
+
+        final JWT jwt = client.exchangeAuthorizationCodeFor2FAResult("code", "jdoe");
+        
+    }
+    
+    /**
+     * Test the health check using a mocked HTTP client and response.
+     * Given the amount of mocking, is not a very thorough test.
+     *  
+     * @throws Exception on error
+     */
+    @Test public void testHealthCheck() throws Exception {
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(HEALTH_CHECK_REPONSE));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+        
+     // create new client with mock response
+        client = new NimbusClient(integ, httpClient, null);
+        
+        final DuoHealthCheck check = client.healthCheck();
+        assertEquals(check.getStatus(), "OK");
+    }
+    
+    /**
+     * Test the health check using a mocked HTTP client and response.
+     * Given the amount of mocking, is not a very thorough test.
+     * 
+     * The response contains more fields than known, should not fail.
+     *  
+     * @throws Exception on error
+     */
+    @Test public void testHealthCheckMoreFields() throws Exception {
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(HEALTH_CHECK_REPONSE_MORE_FIELDS));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+        
+        // create new client with mock response
+        client = new NimbusClient(integ, httpClient, null);
+        
+        final DuoHealthCheck check = client.healthCheck();
+        assertEquals(check.getStatus(), "OK");
+    }
+    
+    /**
+     * Test the health check using a mocked HTTP client and response.
+     * Given the amount of mocking, is not a very thorough test.
+     * 
+     * The response shows a failure.
+     *  
+     * @throws Exception on error
+     */
+    @Test public void testHealthCheckError() throws Exception {
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+        final StatusLine statusLine = Mockito.mock(StatusLine.class);
 
+        Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+        Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+        Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(HEALTH_CHECK_REPONSE_ERROR));
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+                .thenReturn(httpResponse);
+        
+        // create new client with mock response
+        client = new NimbusClient(integ, httpClient, null);
+        
+        final DuoHealthCheck check = client.healthCheck();
+        assertEquals(check.getStatus(), "FAIL");
+    }
+    
+    @Test(expectedExceptions = DuoClientException.class)
+    public void testHealthEndpointTimeout() throws Exception {
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), 
+                (HttpContext) Mockito.any())).thenThrow(IOException.class);
+        
+        // create new client with mock response
+        client = new NimbusClient(integ, httpClient, null);
+        
+        final DuoHealthCheck check = client.healthCheck();
+    }
+    
+    /** 
+     * Test the auth URL is constructed correctly. 
+     * 
+     * @throws DuoClientException on error.
+     */
+    @Test public void testAuthURL() throws DuoClientException {
+        final HttpClient httpClient = Mockito.mock(HttpClient.class);
+        // create new client with mock response
+        client = new NimbusClient(integ, httpClient, null);
+        final String authUrl = client.createAuthUrl("jdoe", "RRFSFERGERGSDDSXXXXQWERG");
+        assertNotNull(authUrl);
+        assertTrue(authUrl.contains("response_type"));
+        assertTrue(authUrl.contains("client_id"));
+        assertTrue(authUrl.contains("request"));
+        assertTrue(authUrl.contains("redirect_uri"));
+        assertTrue(authUrl.contains("scope"));
+      
     }
+    
+    
 
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list