Skip to content

Commit

Permalink
Implement tomee.mp.jwt.allow.no-exp property over mp.jwt.tomee.allow.…
Browse files Browse the repository at this point in the history
…no-exp
  • Loading branch information
tichovz authored and jgallimore committed Dec 6, 2023
1 parent f4215af commit 8414084
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 3 deletions.
3 changes: 2 additions & 1 deletion docs/microprofile/jwt.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ In addition to the standard MicroProfile JWT configuration properties above, the
| Property
| Type
| Description
| `mp.jwt.tomee.allow.no-exp`
| `mp.jwt.tomee.allow.no-exp` is deprecated please use `tomee.mp.jwt.allow.no-exp` property instead
| `tomee.mp.jwt.allow.no-exp`
| Boolean
| Disables enforcing the `exp` time of the JWT. Useful if JWTs are also verified by an API Gateway or proxy before reaching the server. The default value is `false`
| `tomee.jwt.verify.publickey.cache`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.tomee.microprofile.jwt.itest;

import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.RequestScoped;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Base64;
import static java.util.Collections.singletonList;
import java.util.Optional;
import org.apache.cxf.feature.LoggingFeature;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.johnzon.jaxrs.JohnzonProvider;
import org.apache.tomee.server.composer.Archive;
import org.apache.tomee.server.composer.TomEE;
import org.eclipse.microprofile.auth.LoginConfig;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;


public class AllowNoExpPropertyTest {

@Test
public void testNewPropertyOverridesOld1() throws Exception {
final Tokens tokens = Tokens.rsa(2048, 256);
final File appJar = Archive.archive()
.add(AllowNoExpPropertyTest.class)
.add(ColorService.class)
.add(Api.class)
.add("META-INF/microprofile-config.properties", "#\n" +
"mp.jwt.verify.publickey=" + Base64.getEncoder().encodeToString(tokens.getPublicKey().getEncoded())
+ "\n" + "mp.jwt.tomee.allow.no-exp=false"
+ "\n" + "tomee.mp.jwt.allow.no-exp=true")
.asJar();

final ArrayList<String> output = new ArrayList<>();
final TomEE tomee = TomEE.microprofile()
.add("webapps/test/WEB-INF/beans.xml", "")
.add("webapps/test/WEB-INF/lib/app.jar", appJar)
.watch("org.apache.tomee.microprofile.jwt.", "\n", output::add)
.build();

final WebClient webClient = createWebClient(tomee.toURI().resolve("/test").toURL());

final String claims = "{" +
" \"sub\":\"Jane Awesome\"" +
"}";

{// invalid token
final String token = tokens.asToken(claims);
final Response response = webClient.reset()
.path("/movies")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.get();
assertEquals(403, response.getStatus());
}

assertPresent(output , "mp.jwt.tomee.allow.no-exp property is deprecated");
assertNotPresent(output, "rejected due to invalid claims");
assertNotPresent(output, "No Expiration Time (exp) claim present.");
assertNotPresent(output, "\tat org."); // no stack traces
}

@Test
public void testNewPropertyOverridesOld2() throws Exception {
final Tokens tokens = Tokens.rsa(2048, 256);
final File appJar = Archive.archive()
.add(AllowNoExpPropertyTest.class)
.add(ColorService.class)
.add(Api.class)
.add("META-INF/microprofile-config.properties", "#\n" +
"mp.jwt.verify.publickey=" + Base64.getEncoder().encodeToString(tokens.getPublicKey().getEncoded())
+ "\n" + "mp.jwt.tomee.allow.no-exp=true"
+ "\n" + "tomee.mp.jwt.allow.no-exp=false")
.asJar();

final ArrayList<String> output = new ArrayList<>();
final TomEE tomee = TomEE.microprofile()
.add("webapps/test/WEB-INF/beans.xml", "")
.add("webapps/test/WEB-INF/lib/app.jar", appJar)
.watch("org.apache.tomee.microprofile.jwt.", "\n", output::add)
.build();

final WebClient webClient = createWebClient(tomee.toURI().resolve("/test").toURL());

final String claims = "{" +
" \"sub\":\"Jane Awesome\"" +
"}";

{// invalid token
final String token = tokens.asToken(claims);
final Response response = webClient.reset()
.path("/movies")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.get();
assertEquals(401, response.getStatus());
}

assertPresent(output , "mp.jwt.tomee.allow.no-exp property is deprecated");
assertPresent(output, "rejected due to invalid claims");
assertPresent(output, "No Expiration Time (exp) claim present.");
assertNotPresent(output, "\tat org."); // no stack traces
}

@Test
public void testNewProperty() throws Exception {
final Tokens tokens = Tokens.rsa(2048, 256);
final File appJar = Archive.archive()
.add(AllowNoExpPropertyTest.class)
.add(ColorService.class)
.add(Api.class)
.add("META-INF/microprofile-config.properties", "#\n" +
"mp.jwt.verify.publickey=" + Base64.getEncoder().encodeToString(tokens.getPublicKey().getEncoded())
+ "\n" + "tomee.mp.jwt.allow.no-exp=true")
.asJar();

final ArrayList<String> output = new ArrayList<>();
final TomEE tomee = TomEE.microprofile()
.add("webapps/test/WEB-INF/beans.xml", "")
.add("webapps/test/WEB-INF/lib/app.jar", appJar)
.watch("org.apache.tomee.microprofile.jwt.", "\n", output::add)
.build();

final WebClient webClient = createWebClient(tomee.toURI().resolve("/test").toURL());

final String claims = "{" +
" \"sub\":\"Jane Awesome\"" +
"}";

{// invalid token
final String token = tokens.asToken(claims);
final Response response = webClient.reset()
.path("/movies")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.get();
assertEquals(403, response.getStatus());
}

assertNotPresent(output , "mp.jwt.tomee.allow.no-exp property is deprecated");
assertNotPresent(output, "rejected due to invalid claims");
assertNotPresent(output, "No Expiration Time (exp) claim present.");
assertNotPresent(output, "\tat org."); // no stack traces
}

@Test
public void testOldProperty() throws Exception {
final Tokens tokens = Tokens.rsa(2048, 256);
final File appJar = Archive.archive()
.add(AllowNoExpPropertyTest.class)
.add(ColorService.class)
.add(Api.class)
.add("META-INF/microprofile-config.properties", "#\n" +
"mp.jwt.verify.publickey=" + Base64.getEncoder().encodeToString(tokens.getPublicKey().getEncoded())
+ "\n" + "mp.jwt.tomee.allow.no-exp=true")
.asJar();

final ArrayList<String> output = new ArrayList<>();
final TomEE tomee = TomEE.microprofile()
.add("webapps/test/WEB-INF/beans.xml", "")
.add("webapps/test/WEB-INF/lib/app.jar", appJar)
.watch("org.apache.tomee.microprofile.jwt.", "\n", output::add)
.build();

final WebClient webClient = createWebClient(tomee.toURI().resolve("/test").toURL());

final String claims = "{" +
" \"sub\":\"Jane Awesome\"" +
"}";

{// invalid token
final String token = tokens.asToken(claims);
final Response response = webClient.reset()
.path("/movies")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token)
.get();
assertEquals(403, response.getStatus());
}

assertPresent(output , "mp.jwt.tomee.allow.no-exp property is deprecated");
assertNotPresent(output, "rejected due to invalid claims");
assertNotPresent(output, "No Expiration Time (exp) claim present.");
assertNotPresent(output, "\tat org."); // no stack traces
}

public void assertPresent(final ArrayList<String> output, final String s) {
final Optional<String> actual = output.stream()
.filter(line -> line.contains(s))
.findFirst();

assertTrue(actual.isPresent());
}
public void assertNotPresent(final ArrayList<String> output, final String s) {
final Optional<String> actual = output.stream()
.filter(line -> line.contains(s))
.findFirst();

assertTrue(!actual.isPresent());
}

private static WebClient createWebClient(final URL base) {
return WebClient.create(base.toExternalForm(), singletonList(new JohnzonProvider<>()),
singletonList(new LoggingFeature()), null);
}

@ApplicationPath("/api")
@LoginConfig(authMethod = "MP-JWT")
public class Api extends Application {
}

@Path("/movies")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RequestScoped
public static class ColorService {

@GET
@RolesAllowed({"manager", "user"})
public String getAllMovies() {
return "Green";
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

import static org.eclipse.microprofile.jwt.config.Names.AUDIENCES;
Expand All @@ -59,6 +60,7 @@
public class JWTAuthConfigurationProperties {
public static final String PUBLIC_KEY_ERROR = "Could not read MicroProfile Public Key";
public static final String PUBLIC_KEY_ERROR_LOCATION = PUBLIC_KEY_ERROR + " from Location: ";
private static final Logger CONFIGURATION = Logger.getInstance(JWTLogCategories.CONFIG, JWTAuthConfigurationProperties.class);

private Config config;
private JWTAuthConfiguration jwtAuthConfiguration;
Expand Down Expand Up @@ -104,8 +106,8 @@ private JWTAuthConfiguration createJWTAuthConfiguration() {
final Supplier<Map<String, Key>> publicKeys = Keys.VERIFY.configure(config);
final Supplier<Map<String, Key>> decryptKeys = Keys.DECRYPT.configure(config);

final Boolean allowNoExp = config.getOptionalValue("mp.jwt.tomee.allow.no-exp", Boolean.class).orElse(false);

final Boolean allowNoExp = queryAllowExp();
return new JWTAuthConfiguration(
publicKeys,
getIssuer().orElse(null),
Expand All @@ -117,6 +119,15 @@ private JWTAuthConfiguration createJWTAuthConfiguration() {
config.getOptionalValue("mp.jwt.decrypt.key.algorithm", String.class).orElse(null),
config.getOptionalValue("mp.jwt.verify.publickey.algorithm", String.class).orElse(null));
}

private Boolean queryAllowExp(){
AtomicBoolean result = new AtomicBoolean(false);
config.getOptionalValue("mp.jwt.tomee.allow.no-exp", Boolean.class).ifPresent(value -> {
result.set(value);
CONFIGURATION.warning("mp.jwt.tomee.allow.no-exp property is deprecated, use tomee.mp.jwt.allow.no-exp propert instead.");
});
return config.getOptionalValue("tomee.mp.jwt.allow.no-exp", Boolean.class).orElse(result.get());
}

enum Keys {
VERIFY("mp.jwt.verify.publickey", "tomee.jwt.verify.publickey"),
Expand Down

0 comments on commit 8414084

Please sign in to comment.