[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