[java-idp-oidc] branch main updated: Improved flow testing.

Henri Mikkonen henri.mikkonen at iki.fi
Fri Apr 1 11:15:51 UTC 2022


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

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

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

The following commit(s) were added to refs/heads/main by this push:
     new b1db5da7 Improved flow testing.
b1db5da7 is described below

commit b1db5da7fc5e0727e3b034c69d2a7e39e68323f8
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Apr 1 14:15:45 2022 +0300

    Improved flow testing.
---
 .../oidc/op/profile/flow/AbstractOidcFlowTest.java |  11 +-
 .../flow/IssueRegistrationAccessTokenFlowTest.java | 206 ++++++++++++++++++++-
 .../flow/RegistrationManagementFlowTest.java       | 158 ++++++++++++++++
 3 files changed, 365 insertions(+), 10 deletions(-)

diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
index 8c05da65..b5a6b294 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
@@ -81,19 +81,26 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
     
     private String flowId;
     
+    private String endStateId;
+    
     @Autowired
     @Qualifier("shibboleth.oidc.TokenSealer")
     private DataSealer dataSealer;
     
     protected AbstractOidcFlowTest(final String id) {
+        this(id, END_STATE_ID);
+    }
+
+    protected AbstractOidcFlowTest(final String id, final String endId) {
         flowId = id;
+        endStateId = endId;
     }
-    
+
     /**
      * Initialize mock request, response, and external context. Overrides to remove authorization header.
      */
     @BeforeMethod public void initializeMocks() {
-        overrideEndStateOutput(flowId, END_STATE_ID);
+        overrideEndStateOutput(flowId, endStateId);
 
         request = new MockHttpServletRequest();
         response = new MockHttpServletResponse();
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java
index b0c09ac3..bd6b96eb 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java
@@ -21,6 +21,9 @@ import static org.testng.Assert.assertEquals;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -35,8 +38,13 @@ import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.github.jasminb.jsonapi.models.errors.Errors;
 import com.nimbusds.oauth2.sdk.AccessTokenResponse;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
 
 import net.shibboleth.idp.plugin.oidc.op.cli.IssueRegistrationAccessTokenArguments;
+import net.shibboleth.idp.plugin.oidc.op.profile.impl.BaseOIDCResponseActionTest;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.utilities.java.support.security.DataSealer;
 
 /**
  * issue-registration-access-token flow test.
@@ -59,7 +67,7 @@ public class IssueRegistrationAccessTokenFlowTest extends AbstractOidcFlowTest {
     @Test
     public void testWithNoParameters() throws Exception {
 
-        buildRequest(null, null, null);
+        buildRequest(null, null, null, null, null);
 
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
 
@@ -70,14 +78,32 @@ public class IssueRegistrationAccessTokenFlowTest extends AbstractOidcFlowTest {
     }
 
     /**
-     * Test the flow with valid parameters.
+     * Test the flow with invalid metadata location in the parameters.
      * 
      * @throws Exception if an error occurs
      */
     @Test
-    public void testWithValidParameters() throws Exception {
+    public void testWithInvalidLocation() throws Exception {
+        buildRequest("P1D", "src/test/not_existing", "mockRpId", null, null);
 
-        buildRequest("P1D", "src/test/resources/conf/metadata-policy1.json", "mockRpId");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(outcome.getId(), "CommitResponse");
+
+        assertErrorResponse(400, "Invalid Request");
+        
+    }
+
+    /**
+     * Test the flow with both policy ID and policy location in the parameters.
+     * 
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testWithValidPolicyLocationAndId() throws Exception {
+
+        buildRequest("P1D", "src/test/resources/conf/metadata-policy1.json", "mockRpId", null, null);
         
         initializeThreadLocals();
         
@@ -86,14 +112,172 @@ public class IssueRegistrationAccessTokenFlowTest extends AbstractOidcFlowTest {
         final FlowExecutionOutcome outcome = result.getOutcome();
         assertEquals(response.getStatus(), 200);
         assertEquals(outcome.getId(), "CommitResponse");
-        final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
-        Assert.assertNotNull(response.getTokens().getAccessToken());
+
+        validateToken(parseSuccessResponse(result, AccessTokenResponse.class), "mockRpId", null,
+                buildMetadataPolicy1());
+    }
+
+    /**
+     * Test the flow with both policy location in the parameters, without policy ID.
+     * 
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testWithPolicyLocationOnly() throws Exception {
+
+        buildRequest("P1D", "src/test/resources/conf/metadata-policy1.json", null, null, null);
         
-        //TODO: verify the contents of the access token
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(response.getStatus(), 200);
+        assertEquals(outcome.getId(), "CommitResponse");
+
+        validateToken(parseSuccessResponse(result, AccessTokenResponse.class), null, null,
+                buildMetadataPolicy1());
     }
 
+    /**
+     * Test the flow with policy ID in the parameters, without policy location.
+     * 
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testWithPolicyIdOnly() throws Exception {
+
+        buildRequest("P1D", null, "mockRpId", null, null);
+        
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(response.getStatus(), 200);
+        assertEquals(outcome.getId(), "CommitResponse");
+
+        validateToken(parseSuccessResponse(result, AccessTokenResponse.class), "mockRpId", null,
+                null);
+    }
+
+    /**
+     * Test the flow with policy ID in the parameters, without policy location. Client ID is set.
+     * 
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testWithPolicyIdClientId() throws Exception {
+
+        buildRequest("P1D", null, "mockRpId", "mockClientId", null);
+        
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(response.getStatus(), 200);
+        assertEquals(outcome.getId(), "CommitResponse");
+
+        validateToken(parseSuccessResponse(result, AccessTokenResponse.class), "mockRpId", "mockClientId",
+                null);
+    }
+
+    /**
+     * Test the flow with policy ID in the parameters, without policy location. Client ID is not set so replacement
+     * should be null even when set in the request.
+     * 
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testWithPolicyIdReplacementIgnoredWithoutClientId() throws Exception {
+
+        buildRequest("P1D", null, "mockRpId", null, "true");
+        
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(response.getStatus(), 200);
+        assertEquals(outcome.getId(), "CommitResponse");
+
+        
+        final RegistrationClaimsSet claimsSet = validateToken(parseSuccessResponse(result, AccessTokenResponse.class),
+                "mockRpId", null, null);
+        Assert.assertFalse(claimsSet.isReplacement());
+    }
+
+    /**
+     * Test the flow with policy ID in the parameters, without policy location. Client ID is set so replacement
+     * can be set too.
+     * 
+     * @throws Exception if an error occurs
+     */
+    @Test
+    public void testWithPolicyIdReplacemenSetWithClientId() throws Exception {
+
+        buildRequest("P1D", null, "mockRpId", "mockClientId", "true");
+        
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+
+        final FlowExecutionOutcome outcome = result.getOutcome();
+        assertEquals(response.getStatus(), 200);
+        assertEquals(outcome.getId(), "CommitResponse");
+
+        
+        final RegistrationClaimsSet claimsSet = validateToken(parseSuccessResponse(result, AccessTokenResponse.class),
+                "mockRpId", "mockClientId", null);
+        Assert.assertTrue(claimsSet.isReplacement());
+    }
+    protected RegistrationClaimsSet validateToken(final AccessTokenResponse tokenResponse, final String policyId,
+            final String clientId, final Map<String, MetadataPolicy> policy)
+            throws Exception {
+        Assert.assertNotNull(tokenResponse.getTokens());
+        final BearerAccessToken accessToken = tokenResponse.getTokens().getBearerAccessToken();
+        Assert.assertNotNull(accessToken);
+        final DataSealer dataSealer = BaseOIDCResponseActionTest.initializeDataSealer();
+
+        final String decryptedToken = dataSealer.unwrap(accessToken.getValue());
+        final RegistrationClaimsSet claimsSet = new ObjectMapper().readValue(decryptedToken,
+                RegistrationClaimsSet.class);
+        Assert.assertEquals(claimsSet.getKeyType(), "rt");
+        Assert.assertNotNull(claimsSet.getJti());
+        Assert.assertEquals(claimsSet.getIssuer(), "https://op.example.org");
+        Assert.assertEquals(claimsSet.getRelyingPartyId(), policyId);
+        Assert.assertEquals(claimsSet.getClientId(), clientId);
+        if (policy != null) {
+            final Map<String, MetadataPolicy> tokenPolicy = claimsSet.getMetadata();
+            Assert.assertNotNull(tokenPolicy);
+            Assert.assertEquals(tokenPolicy.size(), policy.size());
+            for (final String claim : tokenPolicy.keySet()) {
+                Assert.assertEquals(tokenPolicy.get(claim).toString(), policy.get(claim).toString());
+            }
+        } else {
+            Assert.assertNull(claimsSet.getMetadata());
+        }
+        return claimsSet;
+    }
+    
+    private Map<String, MetadataPolicy> buildMetadataPolicy1() {
+        final Map<String, MetadataPolicy> policies = new HashMap<>();
+        policies.put("grant_types", new MetadataPolicy.Builder().withOneOfValues(List.of("authorization_code",
+                "implicit")).build());
+        policies.put("client_name", new MetadataPolicy.Builder().withDefaultValue("A known test application").build());
+        policies.put("organization_name", new MetadataPolicy.Builder().withValue("A trusted organization").build());
+        policies.put("redirect_uris", new MetadataPolicy.Builder()
+                .withRegexp("^https:\\/\\/(?:([^.]+).)?example.org\\/(.*)").withEssential(true).build());
+        policies.put("id_token_signed_response_alg", new MetadataPolicy.Builder()
+                .withSubsetOfValues(List.of("RS256", "RS384", "RS512")).build());
+        policies.put("scope", new MetadataPolicy.Builder()
+                .withSubsetOfValues(List.of("openid", "profile", "email", "phone")).build());
+        return policies;
+    }
+    
     private void buildRequest(@Nullable final String lifetime, @Nullable final String metadataLocation,
-            @Nullable final String relyingPartyId)
+            @Nullable final String relyingPartyId, @Nullable final String clientId, @Nullable final String replacement)
             throws UnsupportedEncodingException {
         request.setMethod("GET");
         if (lifetime != null) {
@@ -107,6 +291,12 @@ public class IssueRegistrationAccessTokenFlowTest extends AbstractOidcFlowTest {
         if (relyingPartyId != null) {
             request.addParameter(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_ID, relyingPartyId);
         }
+        if (clientId != null) {
+            request.addParameter(IssueRegistrationAccessTokenArguments.URL_PARAM_CLIENT_ID, clientId);
+        }
+        if (replacement != null) {
+            request.addParameter(IssueRegistrationAccessTokenArguments.URL_PARAM_REPLACEMENT, replacement);
+        }
     }
     
     private void assertErrorResponse(final int status, final String title)
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationManagementFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationManagementFlowTest.java
new file mode 100644
index 00000000..1ddffc5c
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationManagementFlowTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.oidc.op.profile.flow;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.servlet.http.HttpServletResponse;
+
+import org.opensaml.storage.StorageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.oidc.metadata.impl.BaseStorageServiceClientInformationComponent;
+
+/**
+ * clients administration flow test.
+ */
+public class RegistrationManagementFlowTest extends AbstractOidcFlowTest {
+
+    /** The flow id. */
+    @Nonnull public static final String FLOW_ID = "admin/oidc/clients";
+
+    @Autowired
+    @Qualifier("shibboleth.StorageService")
+    StorageService storageService;
+    
+    String clientId;
+
+    /**
+     * Constructor.
+     */
+    public RegistrationManagementFlowTest() {
+        super(FLOW_ID, "end");
+    }
+
+    @BeforeMethod
+    public void setUp() {
+        clientId = null;
+    }
+    
+    @AfterMethod
+    public void tearDown() throws IOException {
+        if (clientId != null) {
+            storageService.delete(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId);
+        }
+    }
+
+    @Test
+    public void testNoRequiredParameters() throws Exception {
+        request.setMethod("GET");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertEquals(result.getOutcome().getId(), "end");
+        Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_NOT_FOUND);
+    }
+
+    @Test
+    public void testFetchingExistingClient() throws Exception {
+        clientId = "mockClientId";
+        storageService.create(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId, sampleRecord(), null);
+        Assert.assertTrue(recordExists(clientId));
+        request.setMethod("GET");
+        request.addParameter("client_id", clientId);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertEquals(result.getOutcome().getId(), "end");
+        assertClientInfoFromResponse();
+        Assert.assertTrue(recordExists(clientId));        
+    }
+
+    @Test
+    public void testDeletingExistingClient() throws Exception {
+        clientId = "mockClientId";
+        storageService.create(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId, sampleRecord(), null);
+        Assert.assertTrue(recordExists(clientId));
+        request.setMethod("DELETE");
+        request.addParameter("client_id", clientId);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertEquals(result.getOutcome().getId(), "end");
+        Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_NO_CONTENT);
+        Assert.assertFalse(recordExists(clientId));        
+    }
+
+    @Test
+    public void testFetchinggNonExistingClient() throws Exception {
+        clientId = "mockClientId";
+        Assert.assertFalse(recordExists(clientId));
+        request.setMethod("GET");
+        request.addParameter("client_id", clientId);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertEquals(result.getOutcome().getId(), "end");
+        Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_NOT_FOUND);
+        Assert.assertFalse(recordExists(clientId));
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void assertClientInfoFromResponse() throws Exception {
+        final Map<String, Object> map = new ObjectMapper().readValue(response.getContentAsString(), Map.class);
+        final OIDCClientInformation clientInfo = OIDCClientInformation.parse(new JSONObject(map));
+        Assert.assertEquals(clientInfo.getID(), new ClientID(clientId));
+        Assert.assertEquals(clientInfo.getSecret().getValue(), "testSecret1234");
+        final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
+        Assert.assertEquals(metadata.getScope().size(), 2);
+        Assert.assertTrue(metadata.getScope().contains("openid"));
+        Assert.assertTrue(metadata.getScope().contains("profile"));
+        Assert.assertEquals(metadata.getRedirectionURI(), new URI("https://example.org/redirect_uri"));
+        Assert.assertEquals(metadata.getResponseTypes().size(), 1);
+        Assert.assertEquals(metadata.getResponseTypes().iterator().next(), ResponseType.CODE);
+        Assert.assertEquals(metadata.getGrantTypes().size(), 1);
+        Assert.assertEquals(metadata.getGrantTypes().iterator().next(), GrantType.AUTHORIZATION_CODE);
+    }
+    
+    protected boolean recordExists(final String clientId) throws Exception {
+        return storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME, clientId) != null;        
+    }
+    
+    private String sampleRecord() {
+        return "  {\n"
+                + "    \"scope\":\"openid profile\",\n"
+                + "    \"redirect_uris\":[\"https://example.org/redirect_uri\"],\n"
+                + "    \"client_id\":\"mockClientId\",\n"
+                + "    \"client_secret\":\"testSecret1234\",\n"
+                + "    \"response_types\":[\"code\"],\n"
+                + "    \"grant_types\":[\"authorization_code\"]\n"
+                + "}";
+    }
+}

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


More information about the commits mailing list