[FIX] JWT should not attempt to unzip data by default (#2189)

diff --git a/pom.xml b/pom.xml
index 5ea2731..1113e01 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2409,7 +2409,6 @@
                 <groupId>io.jsonwebtoken</groupId>
                 <artifactId>jjwt-impl</artifactId>
                 <version>${jjwt.version}</version>
-                <scope>runtime</scope>
             </dependency>
             <dependency>
                 <groupId>io.jsonwebtoken</groupId>
diff --git a/server/apps/cassandra-app/sample-configuration/jvm.properties b/server/apps/cassandra-app/sample-configuration/jvm.properties
index 9415002..03997ab 100644
--- a/server/apps/cassandra-app/sample-configuration/jvm.properties
+++ b/server/apps/cassandra-app/sample-configuration/jvm.properties
@@ -62,4 +62,7 @@
 
 # Value from which dedicated BodyFactory shall start buffering data to a file.
 # Used for attachment parsing upon message creation. Default value: 100K.
-# james.mime4j.buffered.body.factory.file.threshold=100K
\ No newline at end of file
+# james.mime4j.buffered.body.factory.file.threshold=100K
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
index 3676aa5..181a7c3 100644
--- a/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
+++ b/server/apps/distributed-pop3-app/sample-configuration/jvm.properties
@@ -53,3 +53,6 @@
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/jpa-app/sample-configuration/jvm.properties b/server/apps/jpa-app/sample-configuration/jvm.properties
index 7154210..4ebef36 100644
--- a/server/apps/jpa-app/sample-configuration/jvm.properties
+++ b/server/apps/jpa-app/sample-configuration/jvm.properties
@@ -50,4 +50,7 @@
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
-openjpa.Multithreaded=true
\ No newline at end of file
+openjpa.Multithreaded=true
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/jpa-smtp-app/sample-configuration/jvm.properties b/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
index 7154210..4ebef36 100644
--- a/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
+++ b/server/apps/jpa-smtp-app/sample-configuration/jvm.properties
@@ -50,4 +50,7 @@
 # Disable Remote Code Execution feature from JMX
 # CF https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L646
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
-openjpa.Multithreaded=true
\ No newline at end of file
+openjpa.Multithreaded=true
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/memory-app/sample-configuration/jvm.properties b/server/apps/memory-app/sample-configuration/jvm.properties
index 8a4c348..1834ee9 100644
--- a/server/apps/memory-app/sample-configuration/jvm.properties
+++ b/server/apps/memory-app/sample-configuration/jvm.properties
@@ -52,4 +52,7 @@
 jmx.remote.x.mlet.allow.getMBeansFromURL=false
 
 # Default charset to use in JMAP to present text body parts
-# james.jmap.default.charset=US-ASCII
\ No newline at end of file
+# james.jmap.default.charset=US-ASCII
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
\ No newline at end of file
diff --git a/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties b/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties
index 4fb3f69..df272a1 100644
--- a/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties
+++ b/server/apps/scaling-pulsar-smtp/sample-configuration/jvm.properties
@@ -43,3 +43,6 @@
 # JMX, when enable causes RMI to plan System.gc every hour. Set this instead to once every 1000h.
 #sun.rmi.dgc.server.gcInterval=3600000000
 #sun.rmi.dgc.client.gcInterval=3600000000
+
+# Whether James should unzip JWTs. Default to false
+# james.jwt.zip.allow=false
diff --git a/server/protocols/jwt/pom.xml b/server/protocols/jwt/pom.xml
index 1828858..157f6f0 100644
--- a/server/protocols/jwt/pom.xml
+++ b/server/protocols/jwt/pom.xml
@@ -69,7 +69,6 @@
         <dependency>
             <groupId>io.jsonwebtoken</groupId>
             <artifactId>jjwt-impl</artifactId>
-            <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>io.jsonwebtoken</groupId>
diff --git a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
index c5bc4ff..878a8a6 100644
--- a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
+++ b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
@@ -25,16 +25,36 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 
 import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.CompressionCodecResolver;
 import io.jsonwebtoken.Jws;
 import io.jsonwebtoken.JwtException;
 import io.jsonwebtoken.JwtParser;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
 
 public class JwtTokenVerifier {
+    private static final CompressionCodecResolver DEFAULT_COMPRESSION_CODEC_RESOLVER = new DefaultCompressionCodecResolver();
+    private static final CompressionCodecResolver SECURE_COMPRESSION_CODEC_RESOLVER = header -> {
+        if (Optional.ofNullable(header.getCompressionAlgorithm()).isPresent()) {
+            throw new RuntimeException("Rejecting a ZIP JWT. Usage of ZIPPED JWT can result in " +
+                "excessive memory usage with malicious JWT tokens. To activate support for ZIPPed" +
+                "JWT please run James with the -Djames.jwt.zip.allow=true system property.");
+        }
+        return DEFAULT_COMPRESSION_CODEC_RESOLVER.resolveCompressionCodec(header);
+    };
+    private static final boolean allowZipJWT = Optional.ofNullable(System.getProperty("james.jwt.zip.allow"))
+        .map(Boolean::parseBoolean)
+        .orElse(false);
+    @VisibleForTesting
+    static CompressionCodecResolver CONFIGURED_COMPRESSION_CODEC_RESOLVER = Optional.of(allowZipJWT)
+        .filter(b -> b)
+        .map(any -> DEFAULT_COMPRESSION_CODEC_RESOLVER)
+        .orElse(SECURE_COMPRESSION_CODEC_RESOLVER);
 
     public interface Factory {
         JwtTokenVerifier create();
@@ -97,6 +117,7 @@
     private JwtParser toImmutableJwtParser(PublicKey publicKey) {
         return Jwts.parserBuilder()
             .setSigningKey(publicKey)
+            .setCompressionCodecResolver(CONFIGURED_COMPRESSION_CODEC_RESOLVER)
             .build();
     }
 }
diff --git a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
index ff18fcf..0d146a8 100644
--- a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
+++ b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
@@ -22,6 +22,7 @@
 import static org.apache.james.jwt.OidcTokenFixture.INTROSPECTION_RESPONSE;
 import static org.apache.james.jwt.OidcTokenFixture.USERINFO_RESPONSE;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.net.MalformedURLException;
@@ -40,6 +41,10 @@
 import org.mockserver.model.HttpRequest;
 import org.mockserver.model.HttpResponse;
 
+import io.jsonwebtoken.CompressionCodecs;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
 import reactor.core.publisher.Mono;
 
 class OidcJwtTokenVerifierTest {
@@ -87,6 +92,33 @@
     }
 
     @Test
+    void shouldRejectZippedJWTByDefault() {
+        String jws = Jwts.builder()
+            .claim("kid", "a".repeat(100))
+            .compressWith(CompressionCodecs.DEFLATE)
+            .signWith(SignatureAlgorithm.HS256, OidcTokenFixture.PRIVATE_KEY_BASE64.replace("\n", ""))
+            .compact();
+
+        assertThatThrownBy(() -> OidcJwtTokenVerifier.verifySignatureAndExtractClaim(jws, getJwksURL(), "kid"))
+            .isInstanceOf(RuntimeException.class)
+            .hasMessageContaining("Rejecting a ZIP JWT");
+    }
+
+    @Test
+    void shouldAcceptZippedJWTWhenConfigured() {
+        String jws = Jwts.builder()
+            .claim("kid", "a".repeat(100))
+            .compressWith(CompressionCodecs.DEFLATE)
+            .signWith(SignatureAlgorithm.HS256, OidcTokenFixture.PRIVATE_KEY_BASE64.replace("\n", ""))
+            .compact();
+
+        JwtTokenVerifier.CONFIGURED_COMPRESSION_CODEC_RESOLVER = new DefaultCompressionCodecResolver();
+
+        assertThatCode(() -> OidcJwtTokenVerifier.verifySignatureAndExtractClaim(jws, getJwksURL(), "kid"))
+            .doesNotThrowAnyException();
+    }
+
+    @Test
     void verifyAndClaimShouldReturnEmptyWhenValidTokenHasNotFoundKid() {
         assertThat(OidcJwtTokenVerifier.verifySignatureAndExtractClaim(OidcTokenFixture.VALID_TOKEN_HAS_NOT_FOUND_KID, getJwksURL(), "email_address"))
             .isEmpty();
diff --git a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
index eafd93a..d07646e 100644
--- a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
+++ b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcTokenFixture.java
@@ -21,8 +21,7 @@
 
 public class OidcTokenFixture {
 
-    public static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" +
-        "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCroSIEhNYajXzC\n" +
+    public static final String PRIVATE_KEY_BASE64 = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCroSIEhNYajXzC\n" +
         "gsn+xetgjqYc/SaihaHCIjWra2xMbkyl42BITRmjBFGbUxThMEg5YvaBXC1XQeib\n" +
         "auW7gJnBZs8S54K5FMyjgUXOKbjHqPRxE76vUaIYkAZoeufAnXosfDf/XUZTTKE2\n" +
         "yxyZJhdfgU/RSEpN19joXfskIQWmIXlMKkIG9lGqj7eIcyomdlHHuYxb9owqU+lP\n" +
@@ -47,7 +46,9 @@
         "EdbOTUKhdenKEcSvICOjCrRL/sZHQCSZCH+d7UkCgYAbGniqH/pp73sGd9NZyVT/\n" +
         "HJdOH5dfBfS9sBBJ1f0/pySJLKcArOXS9BMIFueOq4EIc+7hKDCQuqeyhpYZ6UCe\n" +
         "C9h0QNig49qGI/UEtlNrIlydHyPinTa1fDqu99EuRHG0d4RuONW45tmZAY7mGIbf\n" +
-        "PRhJhwOHZT9xO+uPrtQIAw==\n" +
+        "PRhJhwOHZT9xO+uPrtQIAw==\n";
+    public static final String PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" +
+        PRIVATE_KEY_BASE64 +
         "-----END PRIVATE KEY-----";
 
     public static final String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" +