This is an automated email from the ASF dual-hosted git repository.

ndipiazza pushed a commit to branch 
TIKA-4237-Add-JWT-authentication-ability-to-the-http-fetcher
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to 
refs/heads/TIKA-4237-Add-JWT-authentication-ability-to-the-http-fetcher by this 
push:
     new eccd23add add jwt fetching
eccd23add is described below

commit eccd23adde8832d83edd72f90b513245963edae1
Author: Nicholas DiPiazza <[email protected]>
AuthorDate: Fri Apr 5 16:17:47 2024 -0500

    add jwt fetching
---
 .../tika/pipes/fetcher/http/HttpFetcher.java       | 82 +++++++++++++++++++++-
 .../tika/pipes/fetcher/http/jwt/JwtCreds.java      | 25 +++++++
 .../pipes/fetcher/http/{ => jwt}/JwtGenerator.java | 50 ++++++-------
 .../pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java | 43 ++++++++++++
 .../pipes/fetcher/http/jwt/JwtSecretCreds.java     | 14 ++++
 .../pipes/fetcher/http/jwt/JwtGeneratorTest.java   | 41 +++++++++++
 6 files changed, 225 insertions(+), 30 deletions(-)

diff --git 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
index 35e6f3e82..abf24825a 100644
--- 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
+++ 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/HttpFetcher.java
@@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
+import java.security.PrivateKey;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -36,6 +37,7 @@ import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import com.nimbusds.jose.JOSEException;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
 import org.apache.http.ConnectionClosedException;
@@ -69,6 +71,10 @@ import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.pipes.fetcher.AbstractFetcher;
 import org.apache.tika.pipes.fetcher.RangeFetcher;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtCreds;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtGenerator;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtPrivateKeyCreds;
+import org.apache.tika.pipes.fetcher.http.jwt.JwtSecretCreds;
 import org.apache.tika.utils.StringUtils;
 
 /**
@@ -128,13 +134,20 @@ public class HttpFetcher extends AbstractFetcher 
implements Initializable, Range
     private int maxErrMsgSize = 10000;
 
     //httpHeaders to capture in the metadata
-    private Set<String> httpHeaders = new HashSet<>();
+    private final Set<String> httpHeaders = new HashSet<>();
+
+    private String jwtIssuer;
+    private String jwtSubject;
+    private int jwtExpiresInSeconds;
+    private String jwtSecret;
+    private String jwtPrivateKeyBase64;
+
+    private JwtCreds jwtCreds;
 
     //When making the request, what User-Agent is sent.
     //By default httpclient adds e.g. "Apache-HttpClient/4.5.13 (Java/x.y.z)"
     private String userAgent = null;
 
-
     @Override
     public InputStream fetch(String fetchKey, Metadata metadata) throws 
IOException, TikaException {
         HttpGet get = new HttpGet(fetchKey);
@@ -143,9 +156,16 @@ public class HttpFetcher extends AbstractFetcher 
implements Initializable, Range
                         .setMaxRedirects(maxRedirects)
                         .setRedirectsEnabled(true).build();
         get.setConfig(requestConfig);
-        if (! StringUtils.isBlank(userAgent)) {
+        if (!StringUtils.isBlank(userAgent)) {
             get.setHeader(USER_AGENT, userAgent);
         }
+        if (jwtCreds != null) {
+            try {
+                JwtGenerator.jwt(jwtCreds);
+            } catch (JOSEException e) {
+                throw new TikaException("Could not generate JWT", e);
+            }
+        }
         return execute(get, metadata, httpClient, true);
     }
 
@@ -437,17 +457,73 @@ public class HttpFetcher extends AbstractFetcher 
implements Initializable, Range
         this.userAgent = userAgent;
     }
 
+    public String getJwtIssuer() {
+        return jwtIssuer;
+    }
+
+    @Field
+    public void setJwtIssuer(String jwtIssuer) {
+        this.jwtIssuer = jwtIssuer;
+    }
+
+    public String getJwtSubject() {
+        return jwtSubject;
+    }
+
+    @Field
+    public void setJwtSubject(String jwtSubject) {
+        this.jwtSubject = jwtSubject;
+    }
+
+    public int getJwtExpiresInSeconds() {
+        return jwtExpiresInSeconds;
+    }
+
+    @Field
+    public void setJwtExpiresInSeconds(int jwtExpiresInSeconds) {
+        this.jwtExpiresInSeconds = jwtExpiresInSeconds;
+    }
+
+    public String getJwtSecret() {
+        return jwtSecret;
+    }
+
+    @Field
+    public void setJwtSecret(String jwtSecret) {
+        this.jwtSecret = jwtSecret;
+    }
+
+    public String getJwtPrivateKeyBase64() {
+        return jwtPrivateKeyBase64;
+    }
+
+    @Field
+    public void setJwtPrivateKeyBase64(String jwtPrivateKeyBase64) {
+        this.jwtPrivateKeyBase64 = jwtPrivateKeyBase64;
+    }
+
     @Override
     public void initialize(Map<String, Param> params) throws 
TikaConfigException {
         httpClient = httpClientFactory.build();
         HttpClientFactory cp = httpClientFactory.copy();
         cp.setDisableContentCompression(true);
         noCompressHttpClient = cp.build();
+        if (!StringUtils.isBlank(jwtPrivateKeyBase64)) {
+            PrivateKey key = 
JwtPrivateKeyCreds.convertBase64ToPrivateKey(jwtPrivateKeyBase64);
+            jwtCreds = new JwtPrivateKeyCreds(key, jwtIssuer, jwtSubject, 
jwtExpiresInSeconds);
+        } else if (!StringUtils.isBlank(jwtSecret)) {
+            jwtCreds = new 
JwtSecretCreds(jwtSecret.getBytes(StandardCharsets.UTF_8), jwtIssuer,
+                    jwtSubject, jwtExpiresInSeconds);
+        }
     }
 
     @Override
     public void checkInitialization(InitializableProblemHandler problemHandler)
             throws TikaConfigException {
+        if (!StringUtils.isBlank(jwtSecret) && 
!StringUtils.isBlank(jwtPrivateKeyBase64)) {
+            throw new TikaConfigException("Both JWT secret and JWT private key 
base 64 were " +
+                    "specified. Only one or the other is supported");
+        }
     }
 
     // For test purposes
diff --git 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtCreds.java
 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtCreds.java
new file mode 100644
index 000000000..6ff445dfc
--- /dev/null
+++ 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtCreds.java
@@ -0,0 +1,25 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+public abstract class JwtCreds {
+    private final String issuer;
+    private final String subject;
+    private final int expiresInSeconds;
+
+    public JwtCreds(String issuer, String subject, int expiresInSeconds) {
+        this.issuer = issuer;
+        this.subject = subject;
+        this.expiresInSeconds = expiresInSeconds;
+    }
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public int getExpiresInSeconds() {
+        return expiresInSeconds;
+    }
+}
diff --git 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/JwtGenerator.java
 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGenerator.java
similarity index 51%
rename from 
tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/JwtGenerator.java
rename to 
tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGenerator.java
index 13e936270..91b3e3295 100644
--- 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/JwtGenerator.java
+++ 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGenerator.java
@@ -1,8 +1,5 @@
-package org.apache.tika.pipes.fetcher.http;
+package org.apache.tika.pipes.fetcher.http.jwt;
 
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.Date;
@@ -17,21 +14,20 @@ import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 
 public class JwtGenerator {
-    public static void main(String[] args) throws Exception {
-        KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance("RSA");
-        keyPairGenerator.initialize(2048);
-        byte[] randomBytes = new byte[32];
-        new SecureRandom().nextBytes(randomBytes);
-        System.out.println(jwt(randomBytes, "nick", "subject", 120));
-        
System.out.println(jwt(keyPairGenerator.generateKeyPair().getPrivate(), "nick", 
"subject", 120));
+    public static String jwt(JwtCreds jwtCreds) throws JOSEException {
+        if (jwtCreds instanceof JwtSecretCreds) {
+            return jwtHS256((JwtSecretCreds) jwtCreds);
+        } else {
+            return jwtRS256((JwtPrivateKeyCreds) jwtCreds);
+        }
     }
 
-    public static String jwt(byte[] secret, String issuer, String subject,
-                             int expiresInSeconds)
+    public static String jwtHS256(JwtSecretCreds jwtSecretCreds)
             throws JOSEException {
-        JWSSigner signer = new MACSigner(secret);
+        JWSSigner signer = new MACSigner(jwtSecretCreds.getSecret());
 
-        JWTClaimsSet claimsSet = getJwtClaimsSet(issuer, subject, 
expiresInSeconds);
+        JWTClaimsSet claimsSet = getJwtClaimsSet(jwtSecretCreds.getIssuer(),
+                jwtSecretCreds.getSubject(), 
jwtSecretCreds.getExpiresInSeconds());
 
         SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), 
claimsSet);
         signedJWT.sign(signer);
@@ -39,20 +35,12 @@ public class JwtGenerator {
         return signedJWT.serialize();
     }
 
-    private static JWTClaimsSet getJwtClaimsSet(String issuer, String subject, 
int expiresInSeconds) {
-        return new JWTClaimsSet.Builder()
-                .subject(subject)
-                .issuer(issuer)
-                .expirationTime(Date.from(Instant.now().plus(expiresInSeconds, 
ChronoUnit.SECONDS)))
-                .build();
-    }
-
-    public static String jwt(PrivateKey privateKey, String issuer, String 
subject,
-                             int expiresInSeconds)
+    public static String jwtRS256(JwtPrivateKeyCreds jwtPrivateKeyCreds)
             throws JOSEException {
-        JWSSigner signer = new RSASSASigner(privateKey);
+        JWSSigner signer = new 
RSASSASigner(jwtPrivateKeyCreds.getPrivateKey());
 
-        JWTClaimsSet claimsSet = getJwtClaimsSet(issuer, subject, 
expiresInSeconds);
+        JWTClaimsSet claimsSet = 
getJwtClaimsSet(jwtPrivateKeyCreds.getIssuer(),
+                jwtPrivateKeyCreds.getSubject(), 
jwtPrivateKeyCreds.getExpiresInSeconds());
 
         SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), 
claimsSet);
 
@@ -60,4 +48,12 @@ public class JwtGenerator {
 
         return signedJWT.serialize();
     }
+
+    private static JWTClaimsSet getJwtClaimsSet(String issuer, String subject, 
int expiresInSeconds) {
+        return new JWTClaimsSet.Builder()
+                .subject(subject)
+                .issuer(issuer)
+                .expirationTime(Date.from(Instant.now().plus(expiresInSeconds, 
ChronoUnit.SECONDS)))
+                .build();
+    }
 }
diff --git 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java
 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java
new file mode 100644
index 000000000..aac7f155d
--- /dev/null
+++ 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtPrivateKeyCreds.java
@@ -0,0 +1,43 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+
+import org.apache.tika.exception.TikaConfigException;
+
+public class JwtPrivateKeyCreds extends JwtCreds {
+    private final PrivateKey privateKey;
+    public JwtPrivateKeyCreds(PrivateKey privateKey, String issuer, String 
subject,
+                              int expiresInSeconds) {
+        super(issuer, subject, expiresInSeconds);
+        this.privateKey = privateKey;
+    }
+
+    public PrivateKey getPrivateKey() {
+        return privateKey;
+    }
+
+    public static String convertPrivateKeyToBase64(PrivateKey privateKey) {
+        // Get the encoded form of the private key
+        byte[] privateKeyEncoded = privateKey.getEncoded();
+        // Encode the byte array using Base64
+        return Base64.getEncoder().encodeToString(privateKeyEncoded);
+    }
+
+    public static PrivateKey convertBase64ToPrivateKey(String privateKeyBase64)
+            throws TikaConfigException {
+        try {
+            byte[] privateKeyEncoded = 
Base64.getDecoder().decode(privateKeyBase64);
+
+            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+            PKCS8EncodedKeySpec keySpec = new 
PKCS8EncodedKeySpec(privateKeyEncoded);
+            return keyFactory.generatePrivate(keySpec);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+            throw new TikaConfigException("Could not convert private key 
base64 to PrivateKey", e);
+        }
+    }
+}
diff --git 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtSecretCreds.java
 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtSecretCreds.java
new file mode 100644
index 000000000..a8c121b23
--- /dev/null
+++ 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/main/java/org/apache/tika/pipes/fetcher/http/jwt/JwtSecretCreds.java
@@ -0,0 +1,14 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+public class JwtSecretCreds extends JwtCreds {
+    private final byte[] secret;
+    public JwtSecretCreds(byte[] secret, String issuer, String subject, int 
expiresInSeconds) {
+        super(issuer, subject, expiresInSeconds);
+        this.secret = secret;
+    }
+
+    public byte[] getSecret() {
+        return secret;
+    }
+
+}
diff --git 
a/tika-pipes/tika-fetchers/tika-fetcher-http/src/test/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGeneratorTest.java
 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/test/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGeneratorTest.java
new file mode 100644
index 000000000..cb5b1ada5
--- /dev/null
+++ 
b/tika-pipes/tika-fetchers/tika-fetcher-http/src/test/java/org/apache/tika/pipes/fetcher/http/jwt/JwtGeneratorTest.java
@@ -0,0 +1,41 @@
+package org.apache.tika.pipes.fetcher.http.jwt;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.interfaces.RSAPublicKey;
+
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.MACVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.SignedJWT;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class JwtGeneratorTest {
+    @Test
+    void jwtSecret() throws Exception {
+        byte[] randomBytes = new byte[32];
+        new SecureRandom().nextBytes(randomBytes);
+        String jwt = JwtGenerator.jwtHS256(new JwtSecretCreds(randomBytes, 
"nick", "subject",
+                120));
+        SignedJWT signedJWT = SignedJWT.parse(jwt);
+        JWSVerifier verifier = new MACVerifier(randomBytes);
+        Assertions.assertTrue(signedJWT.verify(verifier));
+    }
+
+    @Test
+    void jwtPrivateKey() throws Exception {
+        KeyPairGenerator keyPairGenerator = 
KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(2048);
+        byte[] randomBytes = new byte[32];
+        new SecureRandom().nextBytes(randomBytes);
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        String jwt = JwtGenerator.jwtRS256(
+                new JwtPrivateKeyCreds(keyPair.getPrivate(), "nick",
+                        "subject", 120));
+        JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) 
keyPair.getPublic());
+        SignedJWT signedJWT = SignedJWT.parse(jwt);
+        Assertions.assertTrue(signedJWT.verify(verifier));
+    }
+}

Reply via email to