From 3bac3905f5839f2e5fc4d196b588819b59afa6b8 Mon Sep 17 00:00:00 2001 From: Scott Stark Date: Sat, 26 May 2018 11:15:44 -0700 Subject: [PATCH] Add the mp.jwt.verify.requireiss and restore the mp.jwt.verify.issuer config options Signed-off-by: Scott Stark --- .../microprofile/jwt/config/Names.java | 18 ++- spec/src/main/asciidoc/configuration.asciidoc | 11 +- .../main/asciidoc/future-directions.asciidoc | 18 --- tck/README.adoc | 26 ++-- .../tck/config/IssNoValidationBadIssTest.java | 136 ++++++++++++++++++ ...est.java => IssNoValidationNoIssTest.java} | 20 +-- .../tck/config/IssValidationDefaultTest.java | 136 ++++++++++++++++++ .../jwt/tck/config/IssValidationFailTest.java | 126 ++++++++++++++++ .../jwt/tck/config/IssValidationTest.java | 135 +++++++++++++++++ .../jwt/tck/config/PublicKeyEndpoint.java | 83 ++++++++++- tck/src/test/resources/TokenBadIss.json | 17 +++ tck/tck-base-suite.xml | 6 +- 12 files changed, 686 insertions(+), 46 deletions(-) create mode 100644 tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssNoValidationBadIssTest.java rename tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/{OptionalIssTest.java => IssNoValidationNoIssTest.java} (85%) create mode 100644 tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationDefaultTest.java create mode 100644 tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationFailTest.java create mode 100644 tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationTest.java create mode 100644 tck/src/test/resources/TokenBadIss.json diff --git a/api/src/main/java/org/eclipse/microprofile/jwt/config/Names.java b/api/src/main/java/org/eclipse/microprofile/jwt/config/Names.java index 3cdbcf45..ae685a8c 100644 --- a/api/src/main/java/org/eclipse/microprofile/jwt/config/Names.java +++ b/api/src/main/java/org/eclipse/microprofile/jwt/config/Names.java @@ -30,15 +30,21 @@ public interface Names { */ String VERIFIER_PUBLIC_KEY = "mp.jwt.verify.publickey"; - /** - * The expected iss claim value to validate against an MP-JWT. If not provided, there will be no - * validation of the MP-JWT iss claim. - */ - String ISSUER = "mp.jwt.verify.issuer"; - /** * The relative path or full URL of the public key. All relative paths will be resolved within the archive using * ClassLoader.getResource. If the value is a URL it will be resolved using `new URL(“”).openStream()` */ String VERIFIER_PUBLIC_KEY_LOCATION = "mp.jwt.verify.publickey.location"; + + /** + * A boolean flag that indicates whether or not validation of the iss claim will be done. If true, the MP-JWT + * MUST include an iss claim that matches the {@linkplain #ISSUER} value. If false, no validation of the + * iss claim is performed. + */ + String REQUIRE_ISS = "mp.jwt.verify.requireiss"; + + /** + * The expected iss claim value to validate against an MP-JWT. + */ + String ISSUER = "mp.jwt.verify.issuer"; } diff --git a/spec/src/main/asciidoc/configuration.asciidoc b/spec/src/main/asciidoc/configuration.asciidoc index 1da9ab28..784721f5 100644 --- a/spec/src/main/asciidoc/configuration.asciidoc +++ b/spec/src/main/asciidoc/configuration.asciidoc @@ -347,9 +347,16 @@ public class Handler extends URLStreamHandler { } ---- -See https://docs.oracle.com/javase/8/docs/api/java/net/URL.html[java.net.URL] javadoc for -more details. +See https://docs.oracle.com/javase/8/docs/api/java/net/URL.html[java.net.URL] javadoc for more details. Parsing of the `InputStream` occurs as defined in <> and must return Public Key text in one of the supported formats. +#### `mp.jwt.verify.requireiss` +The `mp.jwt.verify.requireiss` config property is a boolean flag that indicates whether `iss` claim values are required in the MP-JWT tokens. When true (the assumed default), MP-JWT tokens are required to have an `iss` claim value that will be validated against the `mp.jwt.verify.issuer` config property. If false, then +any iss claim value, including none at all, is allowed, and validation of the `iss` claim MUST NOT be performed. + +#### `mp.jwt.verify.issuer` + +The `mp.jwt.verify.issuer` config property allows for the expected value of the `iss` +claim to be specified. When iss validation is enabled, the MicroProfile JWT implementation must verify the `iss` claim of incoming JWTs is present and matches the configured value of `mp.jwt.verify.issuer`. diff --git a/spec/src/main/asciidoc/future-directions.asciidoc b/spec/src/main/asciidoc/future-directions.asciidoc index 8533ea62..a363c106 100644 --- a/spec/src/main/asciidoc/future-directions.asciidoc +++ b/spec/src/main/asciidoc/future-directions.asciidoc @@ -56,24 +56,6 @@ The "aud" claim defined in RFC 7519 section 4.1.3 was considered for addition. Though a "aud" claim is not required, implementations that support it and applications that use it should do so as detailed in this section to ensure alignment for any future standardization. -### `mp.jwt.verify.issuer` configuration option - -Discussion of a standard configuration option for enabling the explicit checking of the -issuer was discussed. Discussion is still ongoing as to what the default behavior of -the property should be if no explicit value is supplied. The definition as last phrased -is below. - -The `mp.jwt.verify.issuer` config property allows for the expected value of the `iss` -claim to be optionally specified. When specified, the MicroProfile JWT implementation -must verify the `iss` claim of incoming JWTs is present and matches the configured value -of `mp.jwt.verify.issuer`. - -If the `mp.jwt.verify.issuer` config property has not been set, any issuer or none at all -is allowed. - -NOTE: In most cases relying on the digital signature check via the Public Key alone is -sufficient to establish trust. - ### `classpath:` URL Scheme The option to have a built-in `classpath:` URL Scheme was discussed with the intended diff --git a/tck/README.adoc b/tck/README.adoc index aa74b504..221bb4db 100644 --- a/tck/README.adoc +++ b/tck/README.adoc @@ -67,6 +67,9 @@ invalid tokens. A summary of the tck/resources directory contents is: ** The test issuer public key that MP-JWT implementations under test use to validate the token signature in the org.eclipse.microprofile.jwt.tck.config.* package tests. * RequiredClaims.json ** Used by the RequiredClaimsTest to generate a MP-JWT with the minimum required claims. +* TokenBadIss.json +** Used by the IssNoValidationBadIssTest to validate that the iss claim is +ignored when validation is disabled. * signer-key4k.jwk ** A JWK representation of the signer public key used by some of the org.eclipse.microprofile.jwt.tck.config.* package tests. * signer-keyset4k.jwk @@ -312,7 +315,7 @@ public class WFSwarmWarArchiveProcessor implements ApplicationArchiveProcessor { <1> The optional microprofile-config.properties. Only the config related tests currently have this asset. <2> The optional public key content of the token signer. <3> The optional 4096 bit public key content of the token signer. -<4> The optional base64 encoded string of the MP-JWT that will be passed by the test. Currently only the `OptionalIssTest` passes this in. +<4> The optional base64 encoded string of the MP-JWT that will be passed by the test. Currently only the `Iss*Validation*` tests pass this in. You can use this information to set vendor specific settings that are need to support proper operation of your MP-JWT implementation. @@ -454,7 +457,12 @@ your build root: - + + + + + + @@ -466,11 +474,11 @@ can be found in the https://github.com/MicroProfileJWT/wfswarm-jwt-auth-tck repo Running ```bash -[wfswarm-jwt-auth-tck 1316]$ mvn -Dswarm.resolver.offline=true test +[wfswarm-jwt-auth-tck 664]$ mvn -Dswarm.resolver.offline=true test [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ -[INFO] Building MicroProfile JWT Auth TCK Harness WFSwarm Implementation 1.0-SNAPSHOT +[INFO] Building MicroProfile JWT Auth TCK Harness WFSwarm Implementation 1.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ jwt-auth-tck --- @@ -494,17 +502,17 @@ Running [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running TestSuite -[INFO] Tests run: 19, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 49.95 s - in TestSuite +[INFO] Tests run: 116, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 469.176 s - in TestSuite [INFO] [INFO] Results: [INFO] -[INFO] Tests run: 19, Failures: 0, Errors: 0, Skipped: 0 +[INFO] Tests run: 116, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 52.805 s -[INFO] Finished at: 2017-08-23T17:23:41-07:00 -[INFO] Final Memory: 30M/619M +[INFO] Total time: 07:51 min +[INFO] Finished at: 2018-05-25T23:10:16-07:00 +[INFO] Final Memory: 73M/909M [INFO] ------------------------------------------------------------------------ ``` diff --git a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssNoValidationBadIssTest.java b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssNoValidationBadIssTest.java new file mode 100644 index 00000000..0bd2d040 --- /dev/null +++ b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssNoValidationBadIssTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 org.eclipse.microprofile.jwt.tck.config; + +import java.io.StringReader; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.PrivateKey; +import java.util.HashMap; +import java.util.Properties; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.jwt.config.Names; +import org.eclipse.microprofile.jwt.tck.container.jaxrs.TCKApplication; +import org.eclipse.microprofile.jwt.tck.util.TokenUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.Assert; +import org.testng.Reporter; +import org.testng.annotations.Test; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.microprofile.jwt.tck.TCKConstants.TEST_GROUP_CONFIG; + +/** + * Validate the handling of the JWT iss claim. + * + * Validate that if there is a {@linkplain Names#REQUIRE_ISS} property set to false, validation of + * the iss claim is not performed, and {@linkplain Names#ISSUER} property is ignored. + */ +public class IssNoValidationBadIssTest extends Arquillian { + /** + * The base URL for the container under test + */ + @ArquillianResource + private URL baseURL; + + /** + * The token used by the test + */ + private static String token; + + /** + * Create a CDI aware base web application archive that includes an embedded PEM public key + * that is included as the mp.jwt.verify.publickey property. + * The root url is / + * @return the base base web application archive + * @throws Exception - on resource failure + */ + @Deployment() + public static WebArchive createDeployment() throws Exception { + URL publicKey = PublicKeyAsPEMTest.class.getResource("/publicKey4k.pem"); + + PrivateKey privateKey = TokenUtils.readPrivateKey("/privateKey4k.pem"); + String kid = "publicKey4k"; + HashMap timeClaims = new HashMap<>(); + token = TokenUtils.generateTokenString(privateKey, kid, "/TokenBadIss.json", null, timeClaims); + + // Setup the microprofile-config.properties content + Properties configProps = new Properties(); + // Location points to the PEM bundled in the deployment + configProps.setProperty(Names.VERIFIER_PUBLIC_KEY_LOCATION, "/publicKey4k.pem"); + // Don't require validation of iss claim + configProps.setProperty(Names.REQUIRE_ISS, "false"); + // The issuer config value should be ignored + configProps.setProperty(Names.ISSUER, "https://ignore-me"); + StringWriter configSW = new StringWriter(); + configProps.store(configSW, "IssNoValidationBadIssTest microprofile-config.properties"); + StringAsset configAsset = new StringAsset(configSW.toString()); + + WebArchive webArchive = ShrinkWrap + .create(WebArchive.class, "IssNoValidationBadIssTest.war") + .addAsResource(publicKey, "/publicKey.pem") + .addAsResource(publicKey, "/publicKey4k.pem") + // Include the token for inspection by ApplicationArchiveProcessor + .add(new StringAsset(token), "MP-JWT") + .addClass(PublicKeyEndpoint.class) + .addClass(TCKApplication.class) + .addClass(SimpleTokenUtils.class) + .addAsWebInfResource("beans.xml", "beans.xml") + .addAsManifestResource(configAsset, "microprofile-config.properties") + ; + System.out.printf("WebArchive: %s\n", webArchive.toString(true)); + return webArchive; + } + + @RunAsClient + @Test(groups = TEST_GROUP_CONFIG, + description = "Validate that JWK with iss and mp.jwt.verify.requireiss=false returns HTTP_OK") + public void testNotRequiredIssIgnored() throws Exception { + Reporter.log("testNotRequiredIssIgnored, expect HTTP_OK"); + + String uri = baseURL.toExternalForm() + "endp/verifyBadIssIsOk"; + WebTarget echoEndpointTarget = ClientBuilder.newClient() + .target(uri) + ; + Response response = echoEndpointTarget.request(APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "Bearer "+token).get(); + Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK); + String replyString = response.readEntity(String.class); + JsonReader jsonReader = Json.createReader(new StringReader(replyString)); + JsonObject reply = jsonReader.readObject(); + Reporter.log(reply.toString()); + Assert.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); + } + +} diff --git a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/OptionalIssTest.java b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssNoValidationNoIssTest.java similarity index 85% rename from tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/OptionalIssTest.java rename to tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssNoValidationNoIssTest.java index c214b4aa..2f287c50 100644 --- a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/OptionalIssTest.java +++ b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssNoValidationNoIssTest.java @@ -53,10 +53,12 @@ import static org.eclipse.microprofile.jwt.tck.TCKConstants.TEST_GROUP_CONFIG; /** - * Validate that a JWT that has not iss claim, and that has no mp.jwt.verify.issuer config - * property is accepted for authentication and authorization + * Validate the handling of the JWT iss claim. + * + * Validate that if there is a {@linkplain Names#REQUIRE_ISS} property set to false, validation of + * the iss claim is not performed even if missing, and {@linkplain Names#ISSUER} property is ignored. */ -public class OptionalIssTest extends Arquillian { +public class IssNoValidationNoIssTest extends Arquillian { /** * The base URL for the container under test */ @@ -88,12 +90,14 @@ public static WebArchive createDeployment() throws Exception { Properties configProps = new Properties(); // Location points to the PEM bundled in the deployment configProps.setProperty(Names.VERIFIER_PUBLIC_KEY_LOCATION, "/publicKey4k.pem"); + // Don't require validation of iss claim + configProps.setProperty(Names.REQUIRE_ISS, "false"); StringWriter configSW = new StringWriter(); - configProps.store(configSW, "OptionalIssTest microprofile-config.properties"); + configProps.store(configSW, "IssNoValidationNoIssTest microprofile-config.properties"); StringAsset configAsset = new StringAsset(configSW.toString()); WebArchive webArchive = ShrinkWrap - .create(WebArchive.class, "OptionalIssTest.war") + .create(WebArchive.class, "IssNoValidationNoIssTest.war") .addAsResource(publicKey, "/publicKey.pem") .addAsResource(publicKey, "/publicKey4k.pem") // Include the token for inspection by ApplicationArchiveProcessor @@ -110,9 +114,9 @@ public static WebArchive createDeployment() throws Exception { @RunAsClient @Test(groups = TEST_GROUP_CONFIG, - description = "Validate that JWK without iss is accepted if there is no mp.jwt.verify.issuer config") - public void testMissingIssIsOk() throws Exception { - Reporter.log("testMissingIssIsOk, expect HTTP_OK"); + description = "Validate that JWK without iss and mp.jwt.verify.requireiss=false returns HTTP_OK") + public void testNotRequiredIssMissingIgnored() throws Exception { + Reporter.log("testNotRequiredIssMissingIgnored, expect HTTP_OK"); String uri = baseURL.toExternalForm() + "endp/verifyMissingIssIsOk"; WebTarget echoEndpointTarget = ClientBuilder.newClient() diff --git a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationDefaultTest.java b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationDefaultTest.java new file mode 100644 index 00000000..39f904be --- /dev/null +++ b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationDefaultTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 org.eclipse.microprofile.jwt.tck.config; + +import java.io.StringReader; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.PrivateKey; +import java.util.HashMap; +import java.util.Properties; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.jwt.config.Names; +import org.eclipse.microprofile.jwt.tck.TCKConstants; +import org.eclipse.microprofile.jwt.tck.container.jaxrs.TCKApplication; +import org.eclipse.microprofile.jwt.tck.util.TokenUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.Assert; +import org.testng.Reporter; +import org.testng.annotations.Test; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.microprofile.jwt.tck.TCKConstants.TEST_GROUP_CONFIG; + +/** + * Validate the handling of the JWT iss claim. + * + * Validate that if there is a {@linkplain Names#REQUIRE_ISS} property missing, validation against + * the {@linkplain Names#ISSUER} property is performed. + */ +public class IssValidationDefaultTest extends Arquillian { + /** + * The base URL for the container under test + */ + @ArquillianResource + private URL baseURL; + + /** + * The token used by the test + */ + private static String token; + + /** + * Create a CDI aware base web application archive that includes an embedded PEM public key + * that is included as the mp.jwt.verify.publickey property. + * The root url is / + * @return the base base web application archive + * @throws Exception - on resource failure + */ + @Deployment() + public static WebArchive createDeployment() throws Exception { + URL publicKey = PublicKeyAsPEMTest.class.getResource("/publicKey4k.pem"); + + PrivateKey privateKey = TokenUtils.readPrivateKey("/privateKey4k.pem"); + String kid = "publicKey4k"; + HashMap timeClaims = new HashMap<>(); + token = TokenUtils.generateTokenString(privateKey, kid, "/RequiredClaims.json", null, timeClaims); + + // Setup the microprofile-config.properties content + Properties configProps = new Properties(); + // Location points to the PEM bundled in the deployment + configProps.setProperty(Names.VERIFIER_PUBLIC_KEY_LOCATION, "/publicKey4k.pem"); + // The mp.jwt.verify.requireiss is not specified in this test, should default to true + //configProps.setProperty(Names.REQUIRE_ISS, "true"); + configProps.setProperty(Names.ISSUER, TCKConstants.TEST_ISSUER); + StringWriter configSW = new StringWriter(); + configProps.store(configSW, "IssValidationDefaultTest microprofile-config.properties"); + StringAsset configAsset = new StringAsset(configSW.toString()); + + WebArchive webArchive = ShrinkWrap + .create(WebArchive.class, "IssValidationDefaultTest.war") + .addAsResource(publicKey, "/publicKey.pem") + .addAsResource(publicKey, "/publicKey4k.pem") + // Include the token for inspection by ApplicationArchiveProcessor + .add(new StringAsset(token), "MP-JWT") + .addClass(PublicKeyEndpoint.class) + .addClass(TCKApplication.class) + .addClass(SimpleTokenUtils.class) + .addAsWebInfResource("beans.xml", "beans.xml") + .addAsManifestResource(configAsset, "microprofile-config.properties") + ; + System.out.printf("WebArchive: %s\n", webArchive.toString(true)); + return webArchive; + } + + @RunAsClient + @Test(groups = TEST_GROUP_CONFIG, + description = "Validate that JWK with iss and no mp.jwt.verify.requireiss returns HTTP_OK") + public void testDefaultValidation() throws Exception { + Reporter.log("testDefaultValidation, expect HTTP_OK"); + + String uri = baseURL.toExternalForm() + "endp/verifyIssIsOk"; + WebTarget echoEndpointTarget = ClientBuilder.newClient() + .target(uri) + ; + Response response = echoEndpointTarget.request(APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "Bearer "+token).get(); + Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK); + String replyString = response.readEntity(String.class); + JsonReader jsonReader = Json.createReader(new StringReader(replyString)); + JsonObject reply = jsonReader.readObject(); + Reporter.log(reply.toString()); + Assert.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); + } + +} diff --git a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationFailTest.java b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationFailTest.java new file mode 100644 index 00000000..f4f25b27 --- /dev/null +++ b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationFailTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 org.eclipse.microprofile.jwt.tck.config; + +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.PrivateKey; +import java.util.HashMap; +import java.util.Properties; + +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.jwt.config.Names; +import org.eclipse.microprofile.jwt.tck.container.jaxrs.TCKApplication; +import org.eclipse.microprofile.jwt.tck.util.TokenUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.Assert; +import org.testng.Reporter; +import org.testng.annotations.Test; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.microprofile.jwt.tck.TCKConstants.TEST_GROUP_CONFIG; + +/** + * Validate the handling of the JWT iss claim. + * + * Validate that if there is a {@linkplain Names#REQUIRE_ISS} property set to true, validation against + * the {@linkplain Names#ISSUER} property is performed, and fails if the jwt iss does not match + * the {@linkplain Names#ISSUER} property. + */ +public class IssValidationFailTest extends Arquillian { + /** + * The base URL for the container under test + */ + @ArquillianResource + private URL baseURL; + + /** + * The token used by the test + */ + private static String token; + + /** + * Create a CDI aware base web application archive that includes an embedded PEM public key + * that is included as the mp.jwt.verify.publickey property. + * The root url is / + * @return the base base web application archive + * @throws Exception - on resource failure + */ + @Deployment() + public static WebArchive createDeployment() throws Exception { + URL publicKey = PublicKeyAsPEMTest.class.getResource("/publicKey4k.pem"); + + PrivateKey privateKey = TokenUtils.readPrivateKey("/privateKey4k.pem"); + String kid = "publicKey4k"; + HashMap timeClaims = new HashMap<>(); + token = TokenUtils.generateTokenString(privateKey, kid, "/RequiredClaims.json", null, timeClaims); + + // Setup the microprofile-config.properties content + Properties configProps = new Properties(); + // Location points to the PEM bundled in the deployment + configProps.setProperty(Names.VERIFIER_PUBLIC_KEY_LOCATION, "/publicKey4k.pem"); + configProps.setProperty(Names.REQUIRE_ISS, "true"); + configProps.setProperty(Names.ISSUER, "https://IssValidationFailTest"); + StringWriter configSW = new StringWriter(); + configProps.store(configSW, "IssValidationFailTest microprofile-config.properties"); + StringAsset configAsset = new StringAsset(configSW.toString()); + + WebArchive webArchive = ShrinkWrap + .create(WebArchive.class, "IssValidationTest.war") + .addAsResource(publicKey, "/publicKey.pem") + .addAsResource(publicKey, "/publicKey4k.pem") + // Include the token for inspection by ApplicationArchiveProcessor + .add(new StringAsset(token), "MP-JWT") + .addClass(PublicKeyEndpoint.class) + .addClass(TCKApplication.class) + .addClass(SimpleTokenUtils.class) + .addAsWebInfResource("beans.xml", "beans.xml") + .addAsManifestResource(configAsset, "microprofile-config.properties") + ; + System.out.printf("WebArchive: %s\n", webArchive.toString(true)); + return webArchive; + } + + @RunAsClient + @Test(groups = TEST_GROUP_CONFIG, + description = "Validate that JWK without iss and mp.jwt.verify.requireiss=false returns HTTP_OK") + public void testNotRequiredIssMissingIgnored() throws Exception { + Reporter.log("testNotRequiredIssMissingIgnored, expect HTTP_UNAUTHORIZED"); + + String uri = baseURL.toExternalForm() + "endp/verifyIssIsOk"; + WebTarget echoEndpointTarget = ClientBuilder.newClient() + .target(uri) + ; + Response response = echoEndpointTarget.request(APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "Bearer "+token).get(); + Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_UNAUTHORIZED); + } + +} diff --git a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationTest.java b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationTest.java new file mode 100644 index 00000000..c7d00569 --- /dev/null +++ b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/IssValidationTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016-2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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 org.eclipse.microprofile.jwt.tck.config; + +import java.io.StringReader; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.PrivateKey; +import java.util.HashMap; +import java.util.Properties; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.jwt.config.Names; +import org.eclipse.microprofile.jwt.tck.TCKConstants; +import org.eclipse.microprofile.jwt.tck.container.jaxrs.TCKApplication; +import org.eclipse.microprofile.jwt.tck.util.TokenUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.Assert; +import org.testng.Reporter; +import org.testng.annotations.Test; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.microprofile.jwt.tck.TCKConstants.TEST_GROUP_CONFIG; + +/** + * Validate the handling of the JWT iss claim. + * + * Validate that if there is a {@linkplain Names#REQUIRE_ISS} property set to true, validation against + * the {@linkplain Names#ISSUER} property is performed. + */ +public class IssValidationTest extends Arquillian { + /** + * The base URL for the container under test + */ + @ArquillianResource + private URL baseURL; + + /** + * The token used by the test + */ + private static String token; + + /** + * Create a CDI aware base web application archive that includes an embedded PEM public key + * that is included as the mp.jwt.verify.publickey property. + * The root url is / + * @return the base base web application archive + * @throws Exception - on resource failure + */ + @Deployment() + public static WebArchive createDeployment() throws Exception { + URL publicKey = PublicKeyAsPEMTest.class.getResource("/publicKey4k.pem"); + + PrivateKey privateKey = TokenUtils.readPrivateKey("/privateKey4k.pem"); + String kid = "publicKey4k"; + HashMap timeClaims = new HashMap<>(); + token = TokenUtils.generateTokenString(privateKey, kid, "/RequiredClaims.json", null, timeClaims); + + // Setup the microprofile-config.properties content + Properties configProps = new Properties(); + // Location points to the PEM bundled in the deployment + configProps.setProperty(Names.VERIFIER_PUBLIC_KEY_LOCATION, "/publicKey4k.pem"); + configProps.setProperty(Names.REQUIRE_ISS, "true"); + configProps.setProperty(Names.ISSUER, TCKConstants.TEST_ISSUER); + StringWriter configSW = new StringWriter(); + configProps.store(configSW, "IssValidationTest microprofile-config.properties"); + StringAsset configAsset = new StringAsset(configSW.toString()); + + WebArchive webArchive = ShrinkWrap + .create(WebArchive.class, "IssValidationTest.war") + .addAsResource(publicKey, "/publicKey.pem") + .addAsResource(publicKey, "/publicKey4k.pem") + // Include the token for inspection by ApplicationArchiveProcessor + .add(new StringAsset(token), "MP-JWT") + .addClass(PublicKeyEndpoint.class) + .addClass(TCKApplication.class) + .addClass(SimpleTokenUtils.class) + .addAsWebInfResource("beans.xml", "beans.xml") + .addAsManifestResource(configAsset, "microprofile-config.properties") + ; + System.out.printf("WebArchive: %s\n", webArchive.toString(true)); + return webArchive; + } + + @RunAsClient + @Test(groups = TEST_GROUP_CONFIG, + description = "Validate that JWK without iss and mp.jwt.verify.requireiss=false returns HTTP_OK") + public void testRequiredIss() throws Exception { + Reporter.log("testRequiredIss, expect HTTP_OK"); + + String uri = baseURL.toExternalForm() + "endp/verifyIssIsOk"; + WebTarget echoEndpointTarget = ClientBuilder.newClient() + .target(uri) + ; + Response response = echoEndpointTarget.request(APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "Bearer "+token).get(); + Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK); + String replyString = response.readEntity(String.class); + JsonReader jsonReader = Json.createReader(new StringReader(replyString)); + JsonObject reply = jsonReader.readObject(); + Reporter.log(reply.toString()); + Assert.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); + } + +} diff --git a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/PublicKeyEndpoint.java b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/PublicKeyEndpoint.java index 58472704..0adac9b2 100644 --- a/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/PublicKeyEndpoint.java +++ b/tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/PublicKeyEndpoint.java @@ -56,7 +56,7 @@ import org.eclipse.microprofile.jwt.config.Names; /** - * The common endpoint used by the various PublicKeyAs... tests + * The common endpoint used by the various config tests */ @RequestScoped @Path("/endp") @@ -73,6 +73,9 @@ public class PublicKeyEndpoint { @ConfigProperty(name = Names.ISSUER) private Optional issuer; @Inject + @ConfigProperty(name = Names.REQUIRE_ISS) + private Optional requireISS; + @Inject @Claim(standard = Claims.iss) private ClaimValue> iss; @@ -450,7 +453,7 @@ public JsonObject verifyKeyLocationAsJWKSUrl(@QueryParam("kid") String kid) { } /** - * Check a token without an iss can be used when no mp.jwt.verify.issuer was provided + * Check a token without an iss can be used when mp.jwt.verify.requireiss=false * @return result of validation test */ @GET @@ -462,6 +465,7 @@ public JsonObject verifyMissingIssIsOk() { String msg; if(iss.getValue().isPresent()) { + // The iss claim should not be provided for this endpoint msg = String.format("MP-JWT has iss(%s) claim", iss.getValue().get()); } else if(issuer.isPresent()) { @@ -484,6 +488,81 @@ else if(issuer.isPresent()) { .build(); return result; } + /** + * Check a token with a bad iss can be used when mp.jwt.verify.requireiss=false + * @return result of validation test + */ + @GET + @Path("/verifyBadIssIsOk") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed("Tester") + public JsonObject verifyBadIssIsOk() { + boolean pass = false; + String msg; + + if(!iss.getValue().isPresent()) { + // The iss claim should be provided for this endpoint + msg = String.format("MP-JWT missing iss claim"); + } + else if(issuer.isPresent()) { + String claimIss = iss.getValue().get(); + String configIss = issuer.get(); + if(!configIss.equals(claimIss)) { + msg = String.format("endpoint accessed with bad iss(%s) != config.iss(%s) as expected PASS", + claimIss, configIss); + pass = true; + } + else { + msg = String.format("mp.jwt.verify.issuer(%s) == jwt.iss(%s)", configIss, claimIss); + } + } + else { + msg = "No mp.jwt.verify.issuer provided"; + } + JsonObject result = Json.createObjectBuilder() + .add("pass", pass) + .add("msg", msg) + .build(); + return result; + } + /** + * Check a token with an iss when mp.jwt.verify.requireiss=true matches the + * mp.jwt.verify.issuer value + * @return result of validation test + */ + @GET + @Path("/verifyIssIsOk") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed("Tester") + public JsonObject verifyIssIsOk() { + boolean pass = false; + String msg; + + if(!iss.getValue().isPresent()) { + // The iss claim should be provided for this endpoint + msg = String.format("MP-JWT missing iss claim"); + } + else if(issuer.isPresent()) { + String claimIss = iss.getValue().get(); + String configIss = issuer.get(); + if(configIss.equals(claimIss)) { + msg = String.format("endpoint accessed with iss(%s) = config.iss(%s) as expected PASS", + claimIss, configIss); + pass = true; + } + else { + msg = String.format("mp.jwt.verify.issuer(%s) != jwt.iss(%s)", configIss, claimIss); + } + } + else { + msg = "No mp.jwt.verify.issuer provided"; + } + JsonObject result = Json.createObjectBuilder() + .add("pass", pass) + .add("msg", msg) + .build(); + return result; + } /** * An endpoint that returns the contents of the bundled /publicKey4k.pem key diff --git a/tck/src/test/resources/TokenBadIss.json b/tck/src/test/resources/TokenBadIss.json new file mode 100644 index 00000000..b9b56a55 --- /dev/null +++ b/tck/src/test/resources/TokenBadIss.json @@ -0,0 +1,17 @@ +{ + "jti": "a-123", + "iss": "https://bad-issuer", + "sub": "24400320", + "upn": "jdoe@example.com", + "preferred_username": "jdoe", + "aud": "s6BhdRkqt3", + "exp": 1311281970, + "iat": 1311280970, + "auth_time": 1311280969, + "groups": [ + "Echoer", + "Tester", + "group1", + "group2" + ] +} diff --git a/tck/tck-base-suite.xml b/tck/tck-base-suite.xml index e34d9032..2574e598 100644 --- a/tck/tck-base-suite.xml +++ b/tck/tck-base-suite.xml @@ -59,7 +59,11 @@ - + + + + +