Skip to content

Instantly share code, notes, and snippets.

@daper
Created August 25, 2020 18:02
Show Gist options
  • Save daper/492cefc39e95579b0fbd771a57775666 to your computer and use it in GitHub Desktop.
Save daper/492cefc39e95579b0fbd771a57775666 to your computer and use it in GitHub Desktop.
withGitHubAPI: Simple GitHub Api Builder Wrapper for Jenkins with GitHub App JWT Authentication
#!/usr/bin/env groovy
import java.util.Base64
import java.security.Key
import java.security.PrivateKey
import java.security.KeyFactory
import java.security.spec.KeySpec
import java.security.spec.PKCS8EncodedKeySpec
import org.kohsuke.github.GHApp
import org.kohsuke.github.GitHub
import org.kohsuke.github.GHRepository
import org.kohsuke.github.GitHubBuilder
import org.kohsuke.github.GHOrganization
import org.kohsuke.github.GHAppInstallation
import org.kohsuke.github.GHAppInstallationToken
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.JwtBuilder
import io.jsonwebtoken.SignatureAlgorithm
class GitHubAPIWrapper implements java.io.Serializable {
private String appId
private String privKeyB64
private GHAppInstallationToken installationToken
GitHubAPIWrapper(String appId, String privKeyB64) {
this.appId = appId
this.privKeyB64 = privKeyB64
}
private PrivateKey getKey() throws Exception {
Base64.Decoder b64Decoder = Base64.getDecoder()
byte[] decoded = b64Decoder.decode(this.privKeyB64)
KeySpec spec = new PKCS8EncodedKeySpec(decoded)
KeyFactory kf = KeyFactory.getInstance("RSA")
return kf.generatePrivate(spec)
}
private String createJWT(String githubAppId, long ttlMillis) throws Exception {
//The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256
long nowMillis = System.currentTimeMillis()
Date now = new Date(nowMillis)
//We will sign our JWT with our private key
Key signingKey = this.getKey()
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder()
.setIssuedAt(now)
.setIssuer(githubAppId)
.signWith(signingKey, signatureAlgorithm)
//if it has been specified, let's add the expiration
if (ttlMillis > 0) {
long expMillis = nowMillis + ttlMillis
Date exp = new Date(expMillis)
builder.setExpiration(exp)
}
//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact()
}
private GHAppInstallationToken createInstallationToken(String appId, long ttlMillis = 600000 - (6000 * 8)) {
// sometimes time differ, so let's give it a margin
String jwtToken = createJWT(appId, ttlMillis)
GitHub github = new GitHubBuilder().withJwtToken(jwtToken).build()
GHApp app = github.getApp()
List<GHAppInstallation> appInstallations = app.listInstallations().asList()
GHAppInstallation appInstallation = appInstallations.get(0)
GHAppInstallationToken appInstallationToken = appInstallation
.createToken(appInstallation.getPermissions())
.create()
return appInstallationToken
}
GitHub build() throws Exception {
if (this.installationToken == null) {
this.installationToken = createInstallationToken(this.appId)
}
GitHub github = new GitHubBuilder()
.withAppInstallationToken(this.installationToken.getToken())
.build()
try {
github.checkApiUrlValidity()
return github
} catch(Exception e) {
this.installationToken = createInstallationToken(this.appId)
}
github = new GitHubBuilder()
.withAppInstallationToken(this.installationToken.getToken())
.build()
return github
}
}
/*
* @description: Accepts a closure which delegates the GitHub API as github
* @param: Closure closure
*/
void withGitHubAPI(Map settings, Closure closure) throws Exception {
if (! settings.containsKey('appId') || ! settings.containsKey('privKeyB64')) {
throw new Exception('appId and/or privKey are missing for withGitHubAPI')
}
GitHubAPIWrapper gitHubAPIWrapper = new GitHubAPIWrapper(settings.appId, settings.privKeyB64)
GitHub github = gitHubAPIWrapper.build()
closure.delegate = [github: github]
closure.call()
}
return withGitHubAPI
//
// Example of usage
//
// node {
// stage('Stage') {
// withGitHubAPI = load 'path/to/this/file.groovy'
// withGitHubAPI([
// appId: GITHUB_APP_ID,
// privKeyB64: PRIV_KEY.trim().replace("\n", "")
// ]) {
// def GHOrganization org = github.getOrganization('org')
// def Map<GHRepository> repos = org.getRepositories()
// def repos = repos.findAll { _, repo ->
// def name = repo.getName()
// return name ==~ /^my-repo-slug-.*/
// }
// println('Repos that match my-repo-slug-*:')
// println(repos.collect{ _, repo -> repo.getName() })
// def repo = org.getRepository('repo')
// println('Repo branches:')
// println(repo.getBranches().collect{ _, branch -> branch.getName() })
// }
// }
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment