Skip to content

Commit 8a474a3

Browse files
author
Marcos.Cela
committed
add: example for Org Installation token on extras package
1 parent 59e18d1 commit 8a474a3

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.kohsuke.github.extras.auth;
2+
3+
import org.kohsuke.github.*;
4+
5+
import java.io.IOException;
6+
import java.nio.file.Paths;
7+
import java.security.NoSuchAlgorithmException;
8+
import java.security.spec.InvalidKeySpecException;
9+
import java.util.Date;
10+
11+
/**
12+
* This helper class provides an example on how to authenticate a GitHub instance with an installation token, that will
13+
* be automatically refreshed when required.
14+
*/
15+
public class OrgInstallationCredentialProvider implements CredentialProvider {
16+
17+
private final GitHub gitHub;
18+
19+
private final String organizationName;
20+
21+
private String latestToken;
22+
23+
private Date validUntil;
24+
25+
public OrgInstallationCredentialProvider(String organizationName, GitHub gitHub) {
26+
this.organizationName = organizationName;
27+
this.gitHub = gitHub;
28+
}
29+
/**
30+
* Obtains a new OAuth2 token, using the configured client to request it. The configured client <b>must</b> be able
31+
* to request the token, this usually means that it needs to have JWT authentication
32+
*
33+
* @throws IOException
34+
* for any problem obtaining the token
35+
*/
36+
@Preview
37+
@Override
38+
@Deprecated
39+
public String getEncodedAuthorization() throws IOException {
40+
if (this.latestToken == null || this.validUntil == null || (new Date()).after(this.validUntil)) {
41+
this.refreshToken();
42+
}
43+
44+
return String.format("token %s", this.latestToken);
45+
}
46+
47+
@Preview
48+
@Deprecated
49+
private void refreshToken() throws IOException {
50+
GHAppInstallation installationByOrganization = this.gitHub.getApp()
51+
.getInstallationByOrganization(this.organizationName);
52+
GHAppInstallationToken ghAppInstallationToken = installationByOrganization.createToken().create();
53+
this.validUntil = ghAppInstallationToken.getExpiresAt();
54+
this.latestToken = ghAppInstallationToken.getToken();
55+
}
56+
57+
public static GitHub getAuthenticatedClient()
58+
throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
59+
// Build a client that will be used to get Oauth tokens with a JWT token
60+
GitHub jwtAuthenticatedClient = new GitHubBuilder()
61+
.withCredentialProvider(new JWTTokenProvider("12345", Paths.get("~/github-api-app.private-key.der")))
62+
.build();
63+
// Build another client (the final one) that will use the Oauth token, and automatically refresh it when
64+
// it is expired. This is the client that can either be further customized, or used directly.
65+
return new GitHubBuilder()
66+
.withCredentialProvider(new OrgInstallationCredentialProvider("myOrganization", jwtAuthenticatedClient))
67+
.build();
68+
}
69+
70+
}

0 commit comments

Comments
 (0)