|
| 1 | +package org.kohsuke.github.extras.auth; |
| 2 | + |
| 3 | +import io.jsonwebtoken.JwtBuilder; |
| 4 | +import io.jsonwebtoken.Jwts; |
| 5 | +import io.jsonwebtoken.SignatureAlgorithm; |
| 6 | +import org.kohsuke.github.CredentialProvider; |
| 7 | + |
| 8 | +import java.io.IOException; |
| 9 | +import java.nio.file.Files; |
| 10 | +import java.nio.file.Path; |
| 11 | +import java.security.KeyFactory; |
| 12 | +import java.security.NoSuchAlgorithmException; |
| 13 | +import java.security.PrivateKey; |
| 14 | +import java.security.spec.InvalidKeySpecException; |
| 15 | +import java.security.spec.PKCS8EncodedKeySpec; |
| 16 | +import java.time.Duration; |
| 17 | +import java.util.Date; |
| 18 | + |
| 19 | +/** |
| 20 | + * A credential provider that gives valid JWT tokens. These tokens are then used to create a time-based token to |
| 21 | + * authenticate as an application. This token provider does not provide any kind of caching, and will always request a |
| 22 | + * new token to the API. |
| 23 | + */ |
| 24 | +public class JWTTokenProvider implements CredentialProvider { |
| 25 | + |
| 26 | + private static final long MINUTES_10 = Duration.ofMinutes(10).toMillis(); |
| 27 | + |
| 28 | + private final PrivateKey privateKey; |
| 29 | + |
| 30 | + /** |
| 31 | + * The identifier for the application |
| 32 | + */ |
| 33 | + private final String applicationId; |
| 34 | + |
| 35 | + public JWTTokenProvider(String applicationId, Path keyPath) |
| 36 | + throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { |
| 37 | + this.privateKey = loadPrivateKey(keyPath); |
| 38 | + this.applicationId = applicationId; |
| 39 | + } |
| 40 | + |
| 41 | + /**add dependencies for a jwt suite |
| 42 | + * You can generate a key to load with this method with: |
| 43 | + * |
| 44 | + * <pre> |
| 45 | + * openssl pkcs8 -topk8 -inform PEM -outform DER -in ~/github-api-app.private-key.pem -out ~/github-api-app.private-key.der -nocrypt |
| 46 | + * </pre> |
| 47 | + */ |
| 48 | + private PrivateKey loadPrivateKey(Path keyPath) |
| 49 | + throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { |
| 50 | + |
| 51 | + byte[] keyBytes = Files.readAllBytes(keyPath); |
| 52 | + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); |
| 53 | + KeyFactory kf = KeyFactory.getInstance("RSA"); |
| 54 | + return kf.generatePrivate(spec); |
| 55 | + } |
| 56 | + |
| 57 | + public String getJWT() { |
| 58 | + long nowMillis = System.currentTimeMillis(); |
| 59 | + Date now = new Date(nowMillis); |
| 60 | + |
| 61 | + // Let's set the JWT Claims |
| 62 | + JwtBuilder builder = Jwts.builder() |
| 63 | + .setIssuedAt(now) |
| 64 | + .setIssuer(this.applicationId) |
| 65 | + .signWith(privateKey, SignatureAlgorithm.RS256); |
| 66 | + |
| 67 | + // if it has been specified, let's add the expiration |
| 68 | + if (MINUTES_10 > 0) { |
| 69 | + long expMillis = nowMillis + MINUTES_10; |
| 70 | + Date exp = new Date(expMillis); |
| 71 | + builder.setExpiration(exp); |
| 72 | + } |
| 73 | + |
| 74 | + // Builds the JWT and serializes it to a compact, URL-safe string |
| 75 | + return builder.compact(); |
| 76 | + } |
| 77 | + |
| 78 | + @Override |
| 79 | + public String getEncodedAuthorization() throws IOException { |
| 80 | + return getJWT(); |
| 81 | + } |
| 82 | + |
| 83 | +} |
0 commit comments